mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-12-15 08:32:48 +00:00
Compare commits
2427 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91601cce9e | ||
|
|
a4fa15a7de | ||
|
|
d4ed6a97a5 | ||
|
|
0db8482873 | ||
|
|
8bccaa6ee2 | ||
|
|
2ae3bec335 | ||
|
|
da5b9be5ca | ||
|
|
d1fbbd3213 | ||
|
|
688a836bbe | ||
|
|
83c49ae78f | ||
|
|
e1a8f4910f | ||
|
|
5ddcc994de | ||
|
|
5446196471 | ||
|
|
0f9ae9efbb | ||
|
|
4aef5b8229 | ||
|
|
6903e05af1 | ||
|
|
43a0c4fe51 | ||
|
|
8342ba68c0 | ||
|
|
48ef5c421b | ||
|
|
290872dcad | ||
|
|
d4fa082e17 | ||
|
|
e6537699fd | ||
|
|
1d950c4f49 | ||
|
|
5e197fb0db | ||
|
|
594ad0e14f | ||
|
|
d73d6e943b | ||
|
|
eb8b02d88b | ||
|
|
c8728e52d8 | ||
|
|
8537e3e135 | ||
|
|
cd1d690fd3 | ||
|
|
67e953902b | ||
|
|
5ffd1f99fd | ||
|
|
cc1a339142 | ||
|
|
1c78526f86 | ||
|
|
b0a5289a42 | ||
|
|
477d23ea37 | ||
|
|
72c2290300 | ||
|
|
61014c747e | ||
|
|
b2ba1f3ca5 | ||
|
|
7c154abf70 | ||
|
|
d012880a90 | ||
|
|
3ce07cf969 | ||
|
|
24dd7b6347 | ||
|
|
9237c9dcee | ||
|
|
143cabe5fc | ||
|
|
370cdc275a | ||
|
|
235cbe12ee | ||
|
|
26a169d938 | ||
|
|
4e67749eef | ||
|
|
e120fd56a6 | ||
|
|
51b9e3732f | ||
|
|
f3a50610af | ||
|
|
4141ae517f | ||
|
|
29095a869c | ||
|
|
cda22a6f0d | ||
|
|
8f4d8b1c02 | ||
|
|
b502b06e82 | ||
|
|
7d541ee916 | ||
|
|
9b14244363 | ||
|
|
b680a42425 | ||
|
|
cf07a6ebb7 | ||
|
|
dd6c82b168 | ||
|
|
18169251fa | ||
|
|
8ee1e468b8 | ||
|
|
1719eee264 | ||
|
|
ae94948246 | ||
|
|
f19bcc48f6 | ||
|
|
41633be1aa | ||
|
|
d2620a655c | ||
|
|
24c1a931a5 | ||
|
|
31025b8c52 | ||
|
|
25f3d5bf02 | ||
|
|
e21bea6ec0 | ||
|
|
0399074fcb | ||
|
|
edc631ad60 | ||
|
|
ff374b7141 | ||
|
|
458989328e | ||
|
|
3c8d6fbe1b | ||
|
|
a052ac8117 | ||
|
|
94a022e76b | ||
|
|
831ac60e25 | ||
|
|
56e648e924 | ||
|
|
61c5eede94 | ||
|
|
2ec0ab871a | ||
|
|
695e4da85e | ||
|
|
2aa9aafdf6 | ||
|
|
071d1922d0 | ||
|
|
95e64d7809 | ||
|
|
5418d0b44c | ||
|
|
bcc065eb8f | ||
|
|
a320b9e05e | ||
|
|
b913bce398 | ||
|
|
c11374ae39 | ||
|
|
eebd54a4e8 | ||
|
|
92ebe85a3f | ||
|
|
25b8108af0 | ||
|
|
8a2b008fbf | ||
|
|
753440faff | ||
|
|
be09db059d | ||
|
|
cbb14d140e | ||
|
|
f06cf887e1 | ||
|
|
194a698372 | ||
|
|
5a5d5bdd51 | ||
|
|
992af0f1cb | ||
|
|
ac3553babb | ||
|
|
97fb2a4fe6 | ||
|
|
709552f14c | ||
|
|
365750565d | ||
|
|
66a2262ee9 | ||
|
|
067426d5e0 | ||
|
|
87eb1914fb | ||
|
|
135899608e | ||
|
|
7720e9f3fd | ||
|
|
61a481f427 | ||
|
|
3fa5ce5404 | ||
|
|
0983f294b7 | ||
|
|
4e7b665ea8 | ||
|
|
b34c5dde40 | ||
|
|
9de2ff2052 | ||
|
|
a4ad9b49b9 | ||
|
|
90055f7680 | ||
|
|
2e31cf51f3 | ||
|
|
4714d5d42f | ||
|
|
5984b0b579 | ||
|
|
d700c448c6 | ||
|
|
ef8599b836 | ||
|
|
dd5b1097f2 | ||
|
|
f7c35fde2d | ||
|
|
4fbb4dfb55 | ||
|
|
84af0405d9 | ||
|
|
c0e5e5f896 | ||
|
|
12bc63fbc9 | ||
|
|
7bbb1d24ac | ||
|
|
49f6ed524d | ||
|
|
7ddfa2d25e | ||
|
|
d3cdfc1b9d | ||
|
|
1f9dbb6bdc | ||
|
|
10eb13920b | ||
|
|
a57dac0706 | ||
|
|
e0179ea332 | ||
|
|
c931d3179b | ||
|
|
c5666f1357 | ||
|
|
b60e5a7ee3 | ||
|
|
c940a4c0fb | ||
|
|
3f72ce4b1f | ||
|
|
5613dbb28b | ||
|
|
2ae57a7970 | ||
|
|
610c9c5149 | ||
|
|
caec9709ef | ||
|
|
a4504dc0c7 | ||
|
|
0fbd9843bd | ||
|
|
240c7db416 | ||
|
|
915873135e | ||
|
|
a822f667af | ||
|
|
c234b5b2d5 | ||
|
|
694899799b | ||
|
|
eb5a9dd20f | ||
|
|
6015f2423b | ||
|
|
01e50d592e | ||
|
|
0524e8dddf | ||
|
|
ab62bfca7e | ||
|
|
7833fc8c80 | ||
|
|
9c2c38c74a | ||
|
|
78700cd2f5 | ||
|
|
695dc96ce8 | ||
|
|
f1d4cae20b | ||
|
|
9518672ad2 | ||
|
|
b055144908 | ||
|
|
7825c107ab | ||
|
|
dd5dd9d7e1 | ||
|
|
63fd3cfff2 | ||
|
|
a6825ac91e | ||
|
|
8ee9d02ffc | ||
|
|
008be6a8e3 | ||
|
|
1f544b98ab | ||
|
|
42e4c64d06 | ||
|
|
548d7ef99a | ||
|
|
9c7bd7d285 | ||
|
|
a79c3dd156 | ||
|
|
fe01f92545 | ||
|
|
8a5d0b2d92 | ||
|
|
f3647ea46d | ||
|
|
31bded8953 | ||
|
|
3f7cb4a458 | ||
|
|
0869931e67 | ||
|
|
948214cacb | ||
|
|
df94d11f53 | ||
|
|
ffed28c9c7 | ||
|
|
25cb47d2f5 | ||
|
|
0979480103 | ||
|
|
dc4c37afb6 | ||
|
|
87bbb6afde | ||
|
|
8916221bba | ||
|
|
8658675f67 | ||
|
|
be3f94d86c | ||
|
|
17657deb0e | ||
|
|
49861b6a84 | ||
|
|
3da33a57e2 | ||
|
|
9c6c6d6b4c | ||
|
|
dbdb8a58fe | ||
|
|
b670259ab6 | ||
|
|
0907f5021e | ||
|
|
301e02bcd8 | ||
|
|
70da177ed7 | ||
|
|
92d854b971 | ||
|
|
d80fec6e60 | ||
|
|
4c06257070 | ||
|
|
d56f030dc4 | ||
|
|
775cd523eb | ||
|
|
a8fa4d2f0c | ||
|
|
2707012325 | ||
|
|
c5ab49c515 | ||
|
|
fe17bec752 | ||
|
|
5b4ce709af | ||
|
|
875aaa0029 | ||
|
|
589b6d7cfe | ||
|
|
649509dec1 | ||
|
|
4d96978b4f | ||
|
|
81ec121918 | ||
|
|
a448fd02e2 | ||
|
|
96d32f2e3e | ||
|
|
4d71c2d1ff | ||
|
|
6ef33eb0c3 | ||
|
|
046c7e21e9 | ||
|
|
47d87e38a6 | ||
|
|
2ab8e92bf4 | ||
|
|
150e1030c3 | ||
|
|
86c5f0d3d3 | ||
|
|
d964221689 | ||
|
|
87eed2e59b | ||
|
|
a737564a6c | ||
|
|
6374a9772c | ||
|
|
b348f7f1ce | ||
|
|
a0c9e3d117 | ||
|
|
3eb8a702c8 | ||
|
|
806d38bbb2 | ||
|
|
84613e51d8 | ||
|
|
0e4804b59f | ||
|
|
69767e978d | ||
|
|
baba65ad43 | ||
|
|
d0460d8691 | ||
|
|
a7476222a9 | ||
|
|
61ac9129b0 | ||
|
|
3aea994101 | ||
|
|
8e0afb2cc4 | ||
|
|
c1c9c7b68a | ||
|
|
6e843533cb | ||
|
|
d8a229c09b | ||
|
|
2494fa5846 | ||
|
|
984391e2b2 | ||
|
|
72ad4b44ce | ||
|
|
1bc88e5b11 | ||
|
|
c289d6a4c5 | ||
|
|
7c4aefd959 | ||
|
|
c1a74aebc5 | ||
|
|
eae580e51f | ||
|
|
e28f7a3bec | ||
|
|
be99eb82e8 | ||
|
|
5af97c969b | ||
|
|
7dfde3a3c5 | ||
|
|
cd22c39078 | ||
|
|
32a5aec34e | ||
|
|
fea76178ee | ||
|
|
3abe2196dd | ||
|
|
18e0fee1a7 | ||
|
|
954814da65 | ||
|
|
180af7e0bd | ||
|
|
79971d677d | ||
|
|
1e835d2fa9 | ||
|
|
76f72dfb58 | ||
|
|
5ae2711c6e | ||
|
|
39ae4a3a10 | ||
|
|
961ecb3ee8 | ||
|
|
205c11dfba | ||
|
|
c743715459 | ||
|
|
b4bd923304 | ||
|
|
e89d3b3807 | ||
|
|
6e69c018b4 | ||
|
|
aff37092bf | ||
|
|
5745c8cc4a | ||
|
|
a5e487441f | ||
|
|
c588436d55 | ||
|
|
82412831d5 | ||
|
|
aa08f0aa48 | ||
|
|
32d12b7f78 | ||
|
|
3b30eb3278 | ||
|
|
99c36f2a2c | ||
|
|
db8b0022fc | ||
|
|
bd882c8bef | ||
|
|
8d358a7a68 | ||
|
|
8731e0816d | ||
|
|
074cc2f26e | ||
|
|
6eaaddd8ed | ||
|
|
419876b575 | ||
|
|
2635217421 | ||
|
|
c6fc3dfe91 | ||
|
|
10aa8c40a7 | ||
|
|
f70abec5ef | ||
|
|
69f2bf664b | ||
|
|
dde9ffb2ae | ||
|
|
47090eb0f7 | ||
|
|
c5d625e261 | ||
|
|
8d7759d22f | ||
|
|
0d4d64c274 | ||
|
|
ea3f8af161 | ||
|
|
a75e1db970 | ||
|
|
072da114db | ||
|
|
968b1b4cb6 | ||
|
|
afb923737c | ||
|
|
79c8f2275c | ||
|
|
d9278c2c24 | ||
|
|
5b0f897118 | ||
|
|
41337d28c3 | ||
|
|
1d29c52a43 | ||
|
|
53e97dac40 | ||
|
|
8716d9c725 | ||
|
|
f278b735cc | ||
|
|
2a65e81316 | ||
|
|
f437d3f883 | ||
|
|
1fd0f8fdde | ||
|
|
61eadf6891 | ||
|
|
3448f3eb0a | ||
|
|
05a5e9cc69 | ||
|
|
34a0a37b63 | ||
|
|
1159d9494c | ||
|
|
92f396761c | ||
|
|
f1dfba6a93 | ||
|
|
b36d70987d | ||
|
|
315af35296 | ||
|
|
97b8bcd5ca | ||
|
|
6b6fdcd5fd | ||
|
|
3d4f79ca59 | ||
|
|
ccf3851d81 | ||
|
|
97d7e6cb9b | ||
|
|
fd945513ac | ||
|
|
92251f2a45 | ||
|
|
51c530c123 | ||
|
|
96bc1cd8f1 | ||
|
|
374dfbdac2 | ||
|
|
534321d1aa | ||
|
|
69c6f81c31 | ||
|
|
9e3d740b43 | ||
|
|
5cdc80ec11 | ||
|
|
217586b536 | ||
|
|
2abec15691 | ||
|
|
688bfa556c | ||
|
|
589d0d0fac | ||
|
|
3c00c578c3 | ||
|
|
eb5f0fcf68 | ||
|
|
c0de35a683 | ||
|
|
d19b47a427 | ||
|
|
573b3a8743 | ||
|
|
5d3f9b4a03 | ||
|
|
c2aab50c7b | ||
|
|
13f34a8bdd | ||
|
|
f88c149076 | ||
|
|
6a510dad6c | ||
|
|
cdb1602e06 | ||
|
|
80baecc8be | ||
|
|
73f0199dc0 | ||
|
|
6b161d5971 | ||
|
|
67ecc108bd | ||
|
|
df380c343e | ||
|
|
6164896793 | ||
|
|
45dcb26123 | ||
|
|
bb9a067293 | ||
|
|
961abad14b | ||
|
|
17ade287ab | ||
|
|
7513474366 | ||
|
|
303659cb0e | ||
|
|
4e1f9c97a5 | ||
|
|
4531157c72 | ||
|
|
26a8bce2c3 | ||
|
|
3383b2b535 | ||
|
|
9960a6cd21 | ||
|
|
0446f449e9 | ||
|
|
a62a2178d0 | ||
|
|
f038423ce2 | ||
|
|
a3683f184e | ||
|
|
abe09e2a85 | ||
|
|
3efad338eb | ||
|
|
29b3ef70ef | ||
|
|
73f5891f87 | ||
|
|
081af2f953 | ||
|
|
0c8922e30f | ||
|
|
82610163cb | ||
|
|
ba82c395f2 | ||
|
|
0954f6d7e8 | ||
|
|
b99b88a30f | ||
|
|
59727656c3 | ||
|
|
0c611b6429 | ||
|
|
2829445f4f | ||
|
|
0cda4e0905 | ||
|
|
aa02c211fe | ||
|
|
6b6105491c | ||
|
|
45df58114c | ||
|
|
a04d772501 | ||
|
|
820d608b18 | ||
|
|
b54c973d82 | ||
|
|
0234ff0252 | ||
|
|
6856fad0c0 | ||
|
|
d40aa49d8c | ||
|
|
05e961f29f | ||
|
|
474f7e0f68 | ||
|
|
fd52a85dbb | ||
|
|
d9866e1f38 | ||
|
|
0d25a8f5b7 | ||
|
|
b4c6499139 | ||
|
|
d8143a67cd | ||
|
|
e3c620e138 | ||
|
|
acf51238d0 | ||
|
|
e7fddf80ae | ||
|
|
f9b49605e4 | ||
|
|
0dec647116 | ||
|
|
3201853cdf | ||
|
|
e83ecb1254 | ||
|
|
2522f0d80f | ||
|
|
70a6ddc897 | ||
|
|
b17a12662c | ||
|
|
be1a374b14 | ||
|
|
49f007601f | ||
|
|
74cdd2d0f3 | ||
|
|
15dcc760b4 | ||
|
|
d2197d99c2 | ||
|
|
badd319bb4 | ||
|
|
e627b14e55 | ||
|
|
4b7419559c | ||
|
|
2c64b78487 | ||
|
|
5e33c2dc6b | ||
|
|
ebb30424fa | ||
|
|
216163f436 | ||
|
|
d3e9c8c9c0 | ||
|
|
cfef374454 | ||
|
|
0d4ca9717e | ||
|
|
7ab47916f2 | ||
|
|
283c53fddf | ||
|
|
c5c11864e0 | ||
|
|
da56310db9 | ||
|
|
7935c00338 | ||
|
|
1620f6a311 | ||
|
|
4925f3227a | ||
|
|
c158edb574 | ||
|
|
ab18fe466b | ||
|
|
88bb620af2 | ||
|
|
a65d5269fe | ||
|
|
1f3d763490 | ||
|
|
184ac728db | ||
|
|
5bf414ffb5 | ||
|
|
22163173fe | ||
|
|
de0f8c24f7 | ||
|
|
f946a4bfb3 | ||
|
|
18341717a1 | ||
|
|
d2e989403f | ||
|
|
af713672fb | ||
|
|
db2d00f828 | ||
|
|
515406c05d | ||
|
|
61d1d9ec90 | ||
|
|
e020aaa368 | ||
|
|
771cf39944 | ||
|
|
c7f3aaa654 | ||
|
|
220892824c | ||
|
|
ce5950ca73 | ||
|
|
24e31a69cb | ||
|
|
65461a09a7 | ||
|
|
6c7e51041f | ||
|
|
98757aa428 | ||
|
|
9a81057d95 | ||
|
|
f00da20764 | ||
|
|
d323c9df88 | ||
|
|
4e2a41f4cf | ||
|
|
8a5a4f3362 | ||
|
|
8cdc4674d7 | ||
|
|
acfe8950b8 | ||
|
|
502c7e756b | ||
|
|
7fd224e690 | ||
|
|
dc4d388d9a | ||
|
|
df281defd8 | ||
|
|
7d3474aeea | ||
|
|
343f139904 | ||
|
|
b95c1deee1 | ||
|
|
b5abb17568 | ||
|
|
ab2eeb0da3 | ||
|
|
e232962649 | ||
|
|
42012d7bfe | ||
|
|
06b2c623cb | ||
|
|
792908eb35 | ||
|
|
a091036744 | ||
|
|
d9812e2bdb | ||
|
|
74b8ee8c10 | ||
|
|
db5d04c37f | ||
|
|
7984806b38 | ||
|
|
766bd3b76d | ||
|
|
b2444c2aca | ||
|
|
bb8852d57e | ||
|
|
037d9323a4 | ||
|
|
f253d2ea69 | ||
|
|
a4dc27f073 | ||
|
|
812df7b07b | ||
|
|
c237f82e51 | ||
|
|
e7c20f0707 | ||
|
|
0f9bdab108 | ||
|
|
1005fbabbd | ||
|
|
8490583d73 | ||
|
|
5bca783e12 | ||
|
|
6751c5a63a | ||
|
|
00502ce33d | ||
|
|
4c4f598552 | ||
|
|
7cff5cbf61 | ||
|
|
12831a7d21 | ||
|
|
7b99a33a2f | ||
|
|
6d736d7309 | ||
|
|
f558ded5bb | ||
|
|
324bb68667 | ||
|
|
70cae93a4b | ||
|
|
627a5825f4 | ||
|
|
899233338d | ||
|
|
a7e3f9c465 | ||
|
|
6069b8946b | ||
|
|
c58725dbc0 | ||
|
|
292179d41d | ||
|
|
a89ae94d85 | ||
|
|
4fc9274e00 | ||
|
|
5259dd8054 | ||
|
|
d8fe59debb | ||
|
|
409fac3ef1 | ||
|
|
239dd0567f | ||
|
|
62cac20ba7 | ||
|
|
ff30435eb4 | ||
|
|
5a0b119410 | ||
|
|
ccfe9b9d08 | ||
|
|
6f4a832389 | ||
|
|
8acbcb2ed2 | ||
|
|
04151b9957 | ||
|
|
dfc628a397 | ||
|
|
4db352f55b | ||
|
|
d8e6433404 | ||
|
|
d0ba17374e | ||
|
|
7f09b486d9 | ||
|
|
30fd51c268 | ||
|
|
556fb4e09f | ||
|
|
a3096689b5 | ||
|
|
bc232c4f77 | ||
|
|
342e4bdee8 | ||
|
|
dc01833a5b | ||
|
|
170b49428c | ||
|
|
c6f875c517 | ||
|
|
07dd6d1192 | ||
|
|
25d9dbe93c | ||
|
|
8cc09f0e5a | ||
|
|
02b5483d81 | ||
|
|
b8c10a0350 | ||
|
|
126d7fd62c | ||
|
|
3c6f50b788 | ||
|
|
2cb6b15bc3 | ||
|
|
2f5b7ad654 | ||
|
|
6aedfb5219 | ||
|
|
397da7d676 | ||
|
|
e75d33439a | ||
|
|
7241f7509f | ||
|
|
8827fd2d74 | ||
|
|
6fc3bbb97d | ||
|
|
508bda37f5 | ||
|
|
a0ef3cfc34 | ||
|
|
1b9c9c48b6 | ||
|
|
22e20d29f5 | ||
|
|
0604f76f56 | ||
|
|
4855e86a3f | ||
|
|
69349368bf | ||
|
|
b531d9eeb2 | ||
|
|
246fcb8efa | ||
|
|
3972bd3ff3 | ||
|
|
d1c96cd4b2 | ||
|
|
154783a974 | ||
|
|
fa07f4ee8a | ||
|
|
0e8e88fac0 | ||
|
|
426c3c4062 | ||
|
|
8d0334e003 | ||
|
|
0ab015abfd | ||
|
|
7350e150c8 | ||
|
|
61f0205529 | ||
|
|
11d06b5e2b | ||
|
|
a9b5762fbc | ||
|
|
724b1c6fd4 | ||
|
|
acd98365c1 | ||
|
|
32c49c080c | ||
|
|
d371c0c5ae | ||
|
|
47171174b5 | ||
|
|
f274684473 | ||
|
|
d96ac56460 | ||
|
|
77f3a1f146 | ||
|
|
ba3f46fbd6 | ||
|
|
0b406b6988 | ||
|
|
6f0cfd23c1 | ||
|
|
10d51ada37 | ||
|
|
9fa70f6cb8 | ||
|
|
cec5a7f2c6 | ||
|
|
829df26b0f | ||
|
|
b8bebc9b64 | ||
|
|
90ca5a8bb7 | ||
|
|
2c11255828 | ||
|
|
9803dd9547 | ||
|
|
dcff84958f | ||
|
|
3a9cf6c360 | ||
|
|
8ff872f41d | ||
|
|
5940cec0e6 | ||
|
|
5359da3ce2 | ||
|
|
c9369db578 | ||
|
|
d70deaf1ac | ||
|
|
b31f8d5867 | ||
|
|
5350f83275 | ||
|
|
dd80b94b43 | ||
|
|
4cbadbd941 | ||
|
|
e61341df79 | ||
|
|
f7ab26a5da | ||
|
|
0d3bde1191 | ||
|
|
c143735393 | ||
|
|
43af5383e3 | ||
|
|
ffed46175d | ||
|
|
31a39be9ea | ||
|
|
2022ca8e10 | ||
|
|
d0d433db1a | ||
|
|
e8d8f7c406 | ||
|
|
f6b2abb1fb | ||
|
|
011c125564 | ||
|
|
1a9f90a08e | ||
|
|
3eb4897bd3 | ||
|
|
3c9dbaf860 | ||
|
|
d918d5b466 | ||
|
|
d95ffdfbf7 | ||
|
|
363ddb70e2 | ||
|
|
bb76f6c652 | ||
|
|
641e998504 | ||
|
|
0a9e4a5e85 | ||
|
|
8aa92bb688 | ||
|
|
1d5faa3101 | ||
|
|
b97b34961c | ||
|
|
be92ac58ae | ||
|
|
1df0def9ea | ||
|
|
a43ac90b21 | ||
|
|
bbecbc8578 | ||
|
|
6458231946 | ||
|
|
97a2e8bb50 | ||
|
|
400d744938 | ||
|
|
9a6d20b6ec | ||
|
|
7cebc4efe8 | ||
|
|
91f0800e26 | ||
|
|
a99a6b6946 | ||
|
|
c37dea2079 | ||
|
|
b55b516fc3 | ||
|
|
1f3c0d004f | ||
|
|
3c3fda8064 | ||
|
|
ae9bbb40fd | ||
|
|
8e5e788bcd | ||
|
|
c50cdd2976 | ||
|
|
f45690b34f | ||
|
|
f0b0e41d33 | ||
|
|
308aa2eca2 | ||
|
|
3771b2ff70 | ||
|
|
8b0f31c43a | ||
|
|
8ba7b078fd | ||
|
|
57f0b04387 | ||
|
|
24d7cb3d5d | ||
|
|
85cf2169d4 | ||
|
|
0427d406b9 | ||
|
|
96dd2f5c85 | ||
|
|
e355c7b8ef | ||
|
|
7789c8d13d | ||
|
|
6e46a17d98 | ||
|
|
e48f36397e | ||
|
|
cf15b7eaff | ||
|
|
651e89994e | ||
|
|
342298ad3e | ||
|
|
a76191ffdc | ||
|
|
279987925a | ||
|
|
824b1c7f6f | ||
|
|
bcae7beae6 | ||
|
|
9353022627 | ||
|
|
35abf16f7d | ||
|
|
c14eb42186 | ||
|
|
98d6043f2d | ||
|
|
7a8d4a3a59 | ||
|
|
3bf0d4aabb | ||
|
|
edd60cad11 | ||
|
|
e3223f745a | ||
|
|
fcd39370ed | ||
|
|
86ffcc973c | ||
|
|
9493fb07cb | ||
|
|
9303415b89 | ||
|
|
8b8e391feb | ||
|
|
234684c875 | ||
|
|
25f1c9ccf3 | ||
|
|
1dd17b1814 | ||
|
|
089b54986a | ||
|
|
224797e5e7 | ||
|
|
346fbcc286 | ||
|
|
1ee53c6877 | ||
|
|
7f4d737503 | ||
|
|
551d3df892 | ||
|
|
b2bfdb097b | ||
|
|
40572cdc00 | ||
|
|
46f0d17da7 | ||
|
|
bc474c6c06 | ||
|
|
31a41000bf | ||
|
|
a5621a4178 | ||
|
|
17869dc5d8 | ||
|
|
69c1639e46 | ||
|
|
ffcd669898 | ||
|
|
8121e48825 | ||
|
|
aed18698a3 | ||
|
|
e1d5cbd06e | ||
|
|
964247b1f2 | ||
|
|
ef604615ec | ||
|
|
bd3735e755 | ||
|
|
7bb6890370 | ||
|
|
734f6564fa | ||
|
|
b5c159c967 | ||
|
|
ba215335bf | ||
|
|
da4bd937a8 | ||
|
|
b5b14373d0 | ||
|
|
270499a4fd | ||
|
|
26ad623d0e | ||
|
|
f8eedc8650 | ||
|
|
89e32b327d | ||
|
|
27223a1883 | ||
|
|
e0ae7eeee7 | ||
|
|
b5fefb4687 | ||
|
|
ecfb2f3d40 | ||
|
|
1614dd5a4d | ||
|
|
cf3e89c374 | ||
|
|
0f0908d3f3 | ||
|
|
31c3def1a8 | ||
|
|
f75003461a | ||
|
|
49504e46ee | ||
|
|
699bdd1348 | ||
|
|
e608fbaad5 | ||
|
|
6a7d105484 | ||
|
|
6acda5fcd1 | ||
|
|
57ba8ed2fb | ||
|
|
d6c4017a2e | ||
|
|
b7be5d14e0 | ||
|
|
f130a78f0a | ||
|
|
a077053b68 | ||
|
|
c93c8de7fe | ||
|
|
3176156639 | ||
|
|
7072d16f00 | ||
|
|
f2dda35f28 | ||
|
|
0cc04ee20d | ||
|
|
7531a3ada7 | ||
|
|
08717d196f | ||
|
|
a1859676e4 | ||
|
|
e4a54ddbf8 | ||
|
|
5b9a9779c8 | ||
|
|
32ab8a1646 | ||
|
|
f0e943ebcc | ||
|
|
c0e91896df | ||
|
|
d60562a034 | ||
|
|
93e08a6e29 | ||
|
|
0f09172ed0 | ||
|
|
948763443e | ||
|
|
3ef3b452e2 | ||
|
|
39ebdb2f61 | ||
|
|
dff50305de | ||
|
|
f71c8551e8 | ||
|
|
d63d4eb019 | ||
|
|
d66ba9d6c6 | ||
|
|
8526437c88 | ||
|
|
987b1c2c36 | ||
|
|
18e159350b | ||
|
|
1338d25b4e | ||
|
|
f994c4d1da | ||
|
|
5fab276c26 | ||
|
|
9f171da570 | ||
|
|
fed00d04a6 | ||
|
|
ecfaf9f02d | ||
|
|
d05e9d0b45 | ||
|
|
d4385c7e43 | ||
|
|
04fc9962ff | ||
|
|
2fd68845ce | ||
|
|
bd69339e22 | ||
|
|
c6404f7ed6 | ||
|
|
6ce948366d | ||
|
|
9e78fd3651 | ||
|
|
5afd135967 | ||
|
|
d5aa9324fa | ||
|
|
9096a6e5b8 | ||
|
|
cb58012a82 | ||
|
|
58bb3cc84f | ||
|
|
c2ff05201a | ||
|
|
9be13cf08f | ||
|
|
eb4ec47f7a | ||
|
|
e2eb9b72f8 | ||
|
|
c9ff235089 | ||
|
|
9af809a4f0 | ||
|
|
288a42663a | ||
|
|
cca15d4211 | ||
|
|
fe2081b407 | ||
|
|
fa323d4987 | ||
|
|
eeef4a2f95 | ||
|
|
2e49f51093 | ||
|
|
0481e83ec4 | ||
|
|
ff8b5bd6c0 | ||
|
|
aabab653d3 | ||
|
|
efe0b3acc0 | ||
|
|
35cc966132 | ||
|
|
777997202b | ||
|
|
1f09a40c77 | ||
|
|
d20fecadac | ||
|
|
fa430bf104 | ||
|
|
8bb66ac254 | ||
|
|
6518aa3670 | ||
|
|
c76d9ebd88 | ||
|
|
7f4d3ffdbc | ||
|
|
b908fdafc6 | ||
|
|
ef59cff44b | ||
|
|
f511802db5 | ||
|
|
30c74b8427 | ||
|
|
2dd16b91ec | ||
|
|
bb7a3ea053 | ||
|
|
0499a7265a | ||
|
|
1959c685b9 | ||
|
|
cd80fbcdbb | ||
|
|
5814b833ed | ||
|
|
cda0b9c90a | ||
|
|
609bba569e | ||
|
|
77c7f8fb54 | ||
|
|
f65290ef38 | ||
|
|
de594aebd5 | ||
|
|
2be81c0322 | ||
|
|
e902fccd39 | ||
|
|
61d162312f | ||
|
|
30a3cd2911 | ||
|
|
2dfe9337a2 | ||
|
|
c188696328 | ||
|
|
f8fac06e1b | ||
|
|
edad26e05b | ||
|
|
6c8117045f | ||
|
|
b573170d69 | ||
|
|
1d14f08541 | ||
|
|
489d796d7b | ||
|
|
1efede4de8 | ||
|
|
a1187757bc | ||
|
|
263ff1ee08 | ||
|
|
2f2289a863 | ||
|
|
e07305ef46 | ||
|
|
1c61ed6a8b | ||
|
|
cf1da2a420 | ||
|
|
7de03c0fc7 | ||
|
|
9c38f39e5b | ||
|
|
0937e64c2e | ||
|
|
5a6e0283dd | ||
|
|
5489797900 | ||
|
|
6e48fe6357 | ||
|
|
485b45675d | ||
|
|
d31f882f1a | ||
|
|
209c078614 | ||
|
|
d2b4594bc2 | ||
|
|
6fcfb385cd | ||
|
|
b12b83cbb5 | ||
|
|
26804b4093 | ||
|
|
8ebcd47599 | ||
|
|
d810a082c5 | ||
|
|
2f9255a46f | ||
|
|
5d0171d5b7 | ||
|
|
c5382c88a2 | ||
|
|
98f9e632ac | ||
|
|
f912869de6 | ||
|
|
8cd6d23ff2 | ||
|
|
f3d863ea45 | ||
|
|
958909551d | ||
|
|
b132a0c0b5 | ||
|
|
6e7e4d4742 | ||
|
|
c95c76d0e0 | ||
|
|
8735af4cbb | ||
|
|
edfef5822f | ||
|
|
ebef54ca44 | ||
|
|
c937abe098 | ||
|
|
587c9b2c3f | ||
|
|
74ac1dc06b | ||
|
|
0707b68796 | ||
|
|
445a83f78a | ||
|
|
bc1d89da89 | ||
|
|
dc5751951e | ||
|
|
3ca6629175 | ||
|
|
1e1aa67b3e | ||
|
|
3dfd9cd512 | ||
|
|
6a51bd1a1c | ||
|
|
5e329f51a4 | ||
|
|
d86cebf99a | ||
|
|
9b43c6c238 | ||
|
|
1084be4712 | ||
|
|
44b2bcb759 | ||
|
|
2d1e001ddf | ||
|
|
8e8d8c9d6a | ||
|
|
a1862d912a | ||
|
|
fd713e0e5c | ||
|
|
d7442b4879 | ||
|
|
4f79e909e5 | ||
|
|
69061791ed | ||
|
|
38f15c976f | ||
|
|
b745ebdda1 | ||
|
|
ae58fd548f | ||
|
|
fbc3078c07 | ||
|
|
860c7f1508 | ||
|
|
3fcd9589a4 | ||
|
|
ba5e90abc3 | ||
|
|
5587ff59b0 | ||
|
|
6d329b130a | ||
|
|
357aea1693 | ||
|
|
3ab0c94496 | ||
|
|
b3f83c3362 | ||
|
|
17a7470a2a | ||
|
|
365a5ccd46 | ||
|
|
742a2b1b85 | ||
|
|
388ebe3bee | ||
|
|
6d302eb25a | ||
|
|
fa1f2404d9 | ||
|
|
52d8825d95 | ||
|
|
e743ae3dbe | ||
|
|
db72048c31 | ||
|
|
f1ff52eb6b | ||
|
|
0b05899818 | ||
|
|
bd59b6316e | ||
|
|
2c74c8a8df | ||
|
|
dc2ea0d6bf | ||
|
|
73382cbcfb | ||
|
|
e6254ddc2c | ||
|
|
30607730f2 | ||
|
|
b92c5188d1 | ||
|
|
524cd1d990 | ||
|
|
231f961945 | ||
|
|
267c1cd696 | ||
|
|
a1e947ae1e | ||
|
|
300e106143 | ||
|
|
57e85affde | ||
|
|
34b74b9e4e | ||
|
|
c218949556 | ||
|
|
4b90cd9b82 | ||
|
|
fcb3fd7186 | ||
|
|
4dbc6803fb | ||
|
|
b6af8368b6 | ||
|
|
6e4b291808 | ||
|
|
4c88ea3c05 | ||
|
|
0e5fd68e6c | ||
|
|
1103ee8f52 | ||
|
|
9efa9e979c | ||
|
|
f331b4471f | ||
|
|
f11016f57c | ||
|
|
4b53bae57b | ||
|
|
b2ccbc3f9e | ||
|
|
7dcf050dc4 | ||
|
|
717869fd30 | ||
|
|
598d6cffa4 | ||
|
|
c022c81100 | ||
|
|
5af9dd655e | ||
|
|
abdf9c72ca | ||
|
|
3778724b73 | ||
|
|
d76865c1ed | ||
|
|
3ccf7adac1 | ||
|
|
c0e8ff8620 | ||
|
|
ca75484eb8 | ||
|
|
8adf1c5d71 | ||
|
|
5f35b740df | ||
|
|
b0d1dd9485 | ||
|
|
ade8fb927c | ||
|
|
31708c0d24 | ||
|
|
9b3a51469e | ||
|
|
cdd31227b7 | ||
|
|
b02a45ee6c | ||
|
|
f796e8f673 | ||
|
|
930c57398a | ||
|
|
6b39c9946b | ||
|
|
3a71d7f1a8 | ||
|
|
d48020a919 | ||
|
|
210b3c8588 | ||
|
|
bdf6d97478 | ||
|
|
1ac9bebb8d | ||
|
|
ef3156e6f7 | ||
|
|
95a7557acf | ||
|
|
8e30ffb840 | ||
|
|
20b5facc38 | ||
|
|
f7eda41a54 | ||
|
|
6a0edb63f0 | ||
|
|
e7976e4235 | ||
|
|
2bd3fdb4e7 | ||
|
|
94a4d68be2 | ||
|
|
b879e724e2 | ||
|
|
234f79857c | ||
|
|
f73d34b9cc | ||
|
|
936faad1b9 | ||
|
|
649783d2c3 | ||
|
|
c0bacb7c7c | ||
|
|
6436fab837 | ||
|
|
e38620a2a2 | ||
|
|
702b1bd3b7 | ||
|
|
c2c833f279 | ||
|
|
a816a7b149 | ||
|
|
32519f8eae | ||
|
|
dbb4904513 | ||
|
|
0e7a8c1a62 | ||
|
|
68582ca466 | ||
|
|
b1f0297e82 | ||
|
|
eeff2ab215 | ||
|
|
62fb6429f9 | ||
|
|
90c3ed96e3 | ||
|
|
c79e625000 | ||
|
|
bce3def4c6 | ||
|
|
1437140f87 | ||
|
|
fd19bb6299 | ||
|
|
a401440004 | ||
|
|
06aef8fb1a | ||
|
|
c418f142ba | ||
|
|
88d21caa19 | ||
|
|
f6c58054a6 | ||
|
|
b4d74bc589 | ||
|
|
d7bb4c1005 | ||
|
|
3c610668b5 | ||
|
|
5d28a6e402 | ||
|
|
e886b55727 | ||
|
|
8c552ccc45 | ||
|
|
054edeefb6 | ||
|
|
6c599e0127 | ||
|
|
4f97987061 | ||
|
|
50e82c5b99 | ||
|
|
1f9337feca | ||
|
|
cba53bba55 | ||
|
|
427fda1015 | ||
|
|
a95e1bb835 | ||
|
|
5ab882ae66 | ||
|
|
e5ce5766be | ||
|
|
725405e4fd | ||
|
|
cad7a193b3 | ||
|
|
69ac8ae147 | ||
|
|
247e5e7f24 | ||
|
|
04821a00f8 | ||
|
|
ad3178fe94 | ||
|
|
ccd1a10892 | ||
|
|
b80ad40f54 | ||
|
|
7cf190e3f9 | ||
|
|
cac94245ea | ||
|
|
a729e54425 | ||
|
|
d4b57fc1b0 | ||
|
|
d63b5772e4 | ||
|
|
37e0f80fb8 | ||
|
|
532fbbd4f1 | ||
|
|
16f5906979 | ||
|
|
bf3fb24c3a | ||
|
|
a8eb9f3e79 | ||
|
|
a88d8ca410 | ||
|
|
ba8d808cd9 | ||
|
|
d67aab573d | ||
|
|
5098b1c696 | ||
|
|
0b9b94bc0b | ||
|
|
8aec0c1ac7 | ||
|
|
77f69fbc5e | ||
|
|
14a0c1871f | ||
|
|
49e7796df9 | ||
|
|
318a053dbf | ||
|
|
29953eb7c7 | ||
|
|
1816723f16 | ||
|
|
5fb5c1bb40 | ||
|
|
4d5d56fe79 | ||
|
|
7213c5c637 | ||
|
|
eb161b978a | ||
|
|
23d4c926b7 | ||
|
|
c354b64679 | ||
|
|
9c93d5a8d2 | ||
|
|
4f75f76db8 | ||
|
|
4dfef99216 | ||
|
|
47ce09393c | ||
|
|
92e9f988f3 | ||
|
|
037a88f0f5 | ||
|
|
e8db8addd7 | ||
|
|
d0ba0503e5 | ||
|
|
27be35ae77 | ||
|
|
835706f780 | ||
|
|
f79675b265 | ||
|
|
0a35f757e9 | ||
|
|
856852592d | ||
|
|
f60a896926 | ||
|
|
b6fc8b777f | ||
|
|
caefbdc917 | ||
|
|
c6823be302 | ||
|
|
7bfb1d19fe | ||
|
|
f24b34758c | ||
|
|
d4b1bdef8e | ||
|
|
dee1d31fc0 | ||
|
|
363bce82d8 | ||
|
|
6be7003ac2 | ||
|
|
586e43c8d0 | ||
|
|
18e66f52dd | ||
|
|
09e86f0a6b | ||
|
|
3b5daf19c3 | ||
|
|
a037108cf3 | ||
|
|
b8a8c3ebf3 | ||
|
|
9fa7f8762e | ||
|
|
9ec457bf5f | ||
|
|
2691cc0b6d | ||
|
|
4645029a27 | ||
|
|
b1768565c1 | ||
|
|
7323b72c4b | ||
|
|
2e5c01a4da | ||
|
|
19dd1af4dc | ||
|
|
0e9a962506 | ||
|
|
fa195c3808 | ||
|
|
cd6cfc6ae9 | ||
|
|
02e9ba54f9 | ||
|
|
b79056295b | ||
|
|
8653630d83 | ||
|
|
99f09709ec | ||
|
|
bcb914485d | ||
|
|
0b79f754f9 | ||
|
|
fe87e32e2b | ||
|
|
9c9390878e | ||
|
|
1c2cba64ec | ||
|
|
b69853a608 | ||
|
|
852de35e3e | ||
|
|
6fbb387488 | ||
|
|
36bfb50aad | ||
|
|
28e0ea3e81 | ||
|
|
6ba9191b30 | ||
|
|
b6f82ca020 | ||
|
|
81a6f4841f | ||
|
|
f2487a22cd | ||
|
|
4d0331e105 | ||
|
|
66e53c2701 | ||
|
|
2e6f3d3579 | ||
|
|
80fde52dc5 | ||
|
|
82a88e0b0a | ||
|
|
d3f9fc7a21 | ||
|
|
9d7c30336e | ||
|
|
ff5b339ce8 | ||
|
|
70c86d2a44 | ||
|
|
0924484bc4 | ||
|
|
dd36857cf8 | ||
|
|
2f54c369f7 | ||
|
|
9e5402e2c3 | ||
|
|
097d77755a | ||
|
|
4fbeb5c172 | ||
|
|
23b04fbbd7 | ||
|
|
d9455e3f9b | ||
|
|
beeb2442ad | ||
|
|
f6a228008b | ||
|
|
73522dc4c1 | ||
|
|
d9b4f5504c | ||
|
|
01511c0d5a | ||
|
|
01cf2d4e7b | ||
|
|
e9212d5ce6 | ||
|
|
bc2345ba14 | ||
|
|
c2818870d3 | ||
|
|
38b0f2f5f9 | ||
|
|
79300b752b | ||
|
|
ed6559bbe9 | ||
|
|
0821e7cf41 | ||
|
|
38f72a85e4 | ||
|
|
ccc2bcf066 | ||
|
|
d9d009ab1f | ||
|
|
f268af945f | ||
|
|
34e8b32180 | ||
|
|
c6626e83f2 | ||
|
|
4e5c501041 | ||
|
|
0832ea97b1 | ||
|
|
7bef517518 | ||
|
|
ea973bbb52 | ||
|
|
b673fec532 | ||
|
|
7e185197aa | ||
|
|
5e86b06db8 | ||
|
|
2e2b065c42 | ||
|
|
91e006cfb0 | ||
|
|
b1a36bbb11 | ||
|
|
ac40098ac5 | ||
|
|
6ff030847e | ||
|
|
a12e401f15 | ||
|
|
49f2f92a9c | ||
|
|
d1ac7ca647 | ||
|
|
f6c26a201e | ||
|
|
90b875adae | ||
|
|
7be7772af6 | ||
|
|
2fa1d7a95b | ||
|
|
6e747fa299 | ||
|
|
86cc963673 | ||
|
|
e776c3c5f9 | ||
|
|
33da9fd143 | ||
|
|
09416286bf | ||
|
|
c5f9db450d | ||
|
|
1e93b13b4c | ||
|
|
48cf64cd3a | ||
|
|
ff3666c6af | ||
|
|
3918025ffd | ||
|
|
1b7ee3b575 | ||
|
|
b370f01551 | ||
|
|
18da6f69a2 | ||
|
|
d040e2719f | ||
|
|
9eb64466bc | ||
|
|
9fc0c9da06 | ||
|
|
f63b75c01b | ||
|
|
aa1dea1b08 | ||
|
|
71116bc525 | ||
|
|
8f1e8af2e1 | ||
|
|
c691759af8 | ||
|
|
d604639b96 | ||
|
|
cde3185a20 | ||
|
|
40ae57d7ea | ||
|
|
9efd42d4ed | ||
|
|
3248ee1a3d | ||
|
|
13d325e259 | ||
|
|
79994e13c7 | ||
|
|
3f9a9157f4 | ||
|
|
7adcb11f1d | ||
|
|
42f6392cc4 | ||
|
|
835e32b1cc | ||
|
|
a7e2592fa8 | ||
|
|
f17001e86c | ||
|
|
7113852cd6 | ||
|
|
7d85c0393f | ||
|
|
48af55adab | ||
|
|
c36267dc17 | ||
|
|
b7eb95f85f | ||
|
|
08cbaa1622 | ||
|
|
a73fa7811c | ||
|
|
0d5fa418af | ||
|
|
cf59ebf1aa | ||
|
|
52566cb562 | ||
|
|
75b307105b | ||
|
|
998281d86d | ||
|
|
8494989715 | ||
|
|
b73527fb33 | ||
|
|
f18c68b8ba | ||
|
|
c8c0366b39 | ||
|
|
22c90eecfc | ||
|
|
3695989866 | ||
|
|
80c5acef81 | ||
|
|
2cb6a1aac0 | ||
|
|
39b7a7290f | ||
|
|
7b792907d3 | ||
|
|
c103738302 | ||
|
|
89901fa7fb | ||
|
|
3176608eaa | ||
|
|
94d0f7ebea | ||
|
|
04eb4484da | ||
|
|
9ffc887adf | ||
|
|
f5b97c57f8 | ||
|
|
46c17b055e | ||
|
|
b5bbbf7eb3 | ||
|
|
87f5a531bb | ||
|
|
3afeda8b19 | ||
|
|
832dc0b0e1 | ||
|
|
13cecd9b91 | ||
|
|
cba7195670 | ||
|
|
446a51122f | ||
|
|
e3c0757166 | ||
|
|
0715e11c05 | ||
|
|
1fc86733ba | ||
|
|
4c002afde1 | ||
|
|
caa0e29564 | ||
|
|
0896c9ab3f | ||
|
|
c56c44f8c0 | ||
|
|
655bfc855e | ||
|
|
439efa1c83 | ||
|
|
59591c2a13 | ||
|
|
e452c6d70a | ||
|
|
12967bb6f9 | ||
|
|
6798dd8686 | ||
|
|
d72a8ac5a9 | ||
|
|
f38f809337 | ||
|
|
c78ab8edf7 | ||
|
|
e24003df97 | ||
|
|
f8213a1a2a | ||
|
|
da7cc102a3 | ||
|
|
ea29305f8b | ||
|
|
9fb71ade71 | ||
|
|
5740dec7b4 | ||
|
|
7c742bf061 | ||
|
|
a30ec0c75e | ||
|
|
b8abedc5b9 | ||
|
|
ef7f42cf6d | ||
|
|
e817715da3 | ||
|
|
9c0825ce70 | ||
|
|
2e81f0cf95 | ||
|
|
c4b099d2a5 | ||
|
|
f3606dfd96 | ||
|
|
05f80f743b | ||
|
|
f249509817 | ||
|
|
c6e0b2007d | ||
|
|
2496f1c654 | ||
|
|
1a32fb5da1 | ||
|
|
be2f0b2172 | ||
|
|
5e5aab7962 | ||
|
|
a8bdc37780 | ||
|
|
3260584f90 | ||
|
|
b58ff14ed1 | ||
|
|
db13b7a3e9 | ||
|
|
3150461d12 | ||
|
|
97bfbe24b8 | ||
|
|
30b5f74c51 | ||
|
|
69aba4643e | ||
|
|
f849ffef3e | ||
|
|
6d5513c69e | ||
|
|
b27e2075ff | ||
|
|
9979fdf6de | ||
|
|
b655b064df | ||
|
|
974619d1ce | ||
|
|
280560f286 | ||
|
|
7ce12af146 | ||
|
|
b7bf6b4cc0 | ||
|
|
ba955df493 | ||
|
|
34cbb13b8a | ||
|
|
aa262d0430 | ||
|
|
4294ecaf26 | ||
|
|
014d0935ba | ||
|
|
e65cad5074 | ||
|
|
b0f2b346f9 | ||
|
|
affd9aadb5 | ||
|
|
23051a4a05 | ||
|
|
e9fe871af3 | ||
|
|
80b1bda51c | ||
|
|
e117cd003f | ||
|
|
e03b4722b0 | ||
|
|
b5a3fb44c3 | ||
|
|
4903a17104 | ||
|
|
a068498561 | ||
|
|
1a3541e575 | ||
|
|
49267b57e4 | ||
|
|
d337b929ef | ||
|
|
f74b15c1bf | ||
|
|
d3fd9e05ca | ||
|
|
16b94c1089 | ||
|
|
d6b4c7c485 | ||
|
|
0309213ccc | ||
|
|
e408414631 | ||
|
|
80ffb9d762 | ||
|
|
bd2a3e6119 | ||
|
|
95e8115045 | ||
|
|
b7cfac4d1f | ||
|
|
5be2633795 | ||
|
|
cb12b83e47 | ||
|
|
cd798daf0a | ||
|
|
7f6f46b662 | ||
|
|
e78d7b0219 | ||
|
|
58082179fe | ||
|
|
622611498c | ||
|
|
b346e1c480 | ||
|
|
bf23d6d7aa | ||
|
|
3d0b8917fa | ||
|
|
8723d673d7 | ||
|
|
35c818f4a0 | ||
|
|
532646a431 | ||
|
|
0fd7b980de | ||
|
|
2f0e91a538 | ||
|
|
0f6f3bdb9a | ||
|
|
2c3e681942 | ||
|
|
34ccaeb10f | ||
|
|
8f985ade97 | ||
|
|
9b7ef1139e | ||
|
|
2829a7ad31 | ||
|
|
b5325fd0ac | ||
|
|
69bbdab450 | ||
|
|
6975acfc8a | ||
|
|
9233fef63f | ||
|
|
33663783cc | ||
|
|
6f4fd18c47 | ||
|
|
7decfdc37f | ||
|
|
267bb02417 | ||
|
|
255e3a043a | ||
|
|
6080a861db | ||
|
|
4959073a33 | ||
|
|
f6dbec1436 | ||
|
|
88fbcabcbb | ||
|
|
1f26e49fb8 | ||
|
|
104ee779e7 | ||
|
|
77da01d1ee | ||
|
|
0e6fd89f0b | ||
|
|
dd912e8958 | ||
|
|
e95c47ff6a | ||
|
|
6b7937c639 | ||
|
|
f72c5753c9 | ||
|
|
bf98aa5464 | ||
|
|
f3102e3b5b | ||
|
|
e0aaba2cf5 | ||
|
|
4b36fc540d | ||
|
|
7cafbde5b1 | ||
|
|
7106e915ef | ||
|
|
f7c8ad6f38 | ||
|
|
3f451158f0 | ||
|
|
8dd4a9fce1 | ||
|
|
52c4395b68 | ||
|
|
49fbae4fad | ||
|
|
2d91f1ab38 | ||
|
|
4d844548c2 | ||
|
|
29e1090d2c | ||
|
|
98f0655da4 | ||
|
|
b241d6d148 | ||
|
|
b7d9e41b43 | ||
|
|
3f000b9d54 | ||
|
|
4672884255 | ||
|
|
a0ae7ff139 | ||
|
|
8ac369b925 | ||
|
|
117f01e71f | ||
|
|
38416fbebb | ||
|
|
39a0350e08 | ||
|
|
a099d2a25a | ||
|
|
bd885da179 | ||
|
|
35db337f79 | ||
|
|
c3f1e0e06e | ||
|
|
b64ab276fb | ||
|
|
66610fb3e7 | ||
|
|
09dcdfa318 | ||
|
|
c574bbcb96 | ||
|
|
61deed70ad | ||
|
|
601747ac49 | ||
|
|
2afce31e95 | ||
|
|
ba08602e20 | ||
|
|
19dcf171e6 | ||
|
|
1bbd685a23 | ||
|
|
02c3bc75f3 | ||
|
|
951467f8ca | ||
|
|
cdfea145d9 | ||
|
|
431ad2940b | ||
|
|
72b8ac2a50 | ||
|
|
d3153e9bb4 | ||
|
|
5045d66c1c | ||
|
|
e71bbd4122 | ||
|
|
c4160e1d50 | ||
|
|
46245ab4aa | ||
|
|
8880b5ff4f | ||
|
|
2c0d42e0da | ||
|
|
1d70a23859 | ||
|
|
0335bc26ca | ||
|
|
60166ac008 | ||
|
|
f760df1e34 | ||
|
|
6fe6342ca4 | ||
|
|
175f2702d1 | ||
|
|
a7db713b1e | ||
|
|
1a0c9cd4e6 | ||
|
|
742e56fc5f | ||
|
|
0262d94dd3 | ||
|
|
32d036c2b0 | ||
|
|
1a63d32fe3 | ||
|
|
ec9b00e255 | ||
|
|
d7cb549eac | ||
|
|
168157bfe1 | ||
|
|
7cbb7718eb | ||
|
|
2d65265fc6 | ||
|
|
30fe9f5236 | ||
|
|
49821062fb | ||
|
|
99439be04f | ||
|
|
17ccac92ee | ||
|
|
dd4c243d7c | ||
|
|
34031bfd6b | ||
|
|
a7a030fedd | ||
|
|
4c50551249 | ||
|
|
483ca9677c | ||
|
|
f00a2e003d | ||
|
|
0b878216ed | ||
|
|
f49a92e742 | ||
|
|
ee218a5fed | ||
|
|
a9196ef149 | ||
|
|
04bf37b52a | ||
|
|
5a3c11f619 | ||
|
|
c89d43d269 | ||
|
|
12f33176bf | ||
|
|
eb779e5bf6 | ||
|
|
47ace5d654 | ||
|
|
70db8c69d2 | ||
|
|
962d16172c | ||
|
|
5f9e675d00 | ||
|
|
4cdc7f33ce | ||
|
|
c359d1e264 | ||
|
|
0c42d039ff | ||
|
|
55503c89e9 | ||
|
|
b8cb6f4246 | ||
|
|
d25e23623a | ||
|
|
a143797ac9 | ||
|
|
09de12c02a | ||
|
|
246e8770ac | ||
|
|
ea059790a9 | ||
|
|
6ae4907520 | ||
|
|
5f768444ac | ||
|
|
93481a98db | ||
|
|
0ebb7c4f9a | ||
|
|
652ea98a2b | ||
|
|
877383679c | ||
|
|
e041a49ad7 | ||
|
|
dc06426ada | ||
|
|
1478ac18fa | ||
|
|
558309599c | ||
|
|
40d1eb37dc | ||
|
|
b8d6c5d007 | ||
|
|
85a9da0e8a | ||
|
|
faa0275700 | ||
|
|
eea56ea0e5 | ||
|
|
ab313aacd8 | ||
|
|
a88ee0725d | ||
|
|
e57c6a9d2e | ||
|
|
f863f4b7ae | ||
|
|
4bbae38150 | ||
|
|
31807b674b | ||
|
|
75fbd9e615 | ||
|
|
42ec624a91 | ||
|
|
b4c13069c0 | ||
|
|
6b1b33481c | ||
|
|
1471e0a247 | ||
|
|
a04bb100bf | ||
|
|
f9b1d3059a | ||
|
|
bddb689e86 | ||
|
|
757f7beeaf | ||
|
|
6582ee1624 | ||
|
|
04e7073aca | ||
|
|
2ccd072416 | ||
|
|
79ecbc83ae | ||
|
|
2a6051ae35 | ||
|
|
cf8e366ae1 | ||
|
|
d42197db3a | ||
|
|
8017b1b479 | ||
|
|
375a243131 | ||
|
|
70e6904eb7 | ||
|
|
42745c52df | ||
|
|
8479989ea8 | ||
|
|
ee7675f7ae | ||
|
|
37b91c40b6 | ||
|
|
4027a87e7f | ||
|
|
84992746de | ||
|
|
4397819b44 | ||
|
|
ee443361b9 | ||
|
|
c00a55619c | ||
|
|
e5c1071073 | ||
|
|
8d941dc028 | ||
|
|
11f6939b85 | ||
|
|
1c3b66c1a8 | ||
|
|
a99f024dfc | ||
|
|
d58ba82388 | ||
|
|
738c6353ef | ||
|
|
dc547de592 | ||
|
|
adf20f188e | ||
|
|
b9b965753b | ||
|
|
036873e129 | ||
|
|
9d36dc9e39 | ||
|
|
ce02d430ba | ||
|
|
4abf2b06d8 | ||
|
|
2d27e3fee0 | ||
|
|
67771105ae | ||
|
|
de9ce6dd19 | ||
|
|
71ef4b079a | ||
|
|
f0d901e136 | ||
|
|
d3dd9d94d3 | ||
|
|
2938f7e506 | ||
|
|
1f5fa9a9bd | ||
|
|
96ac65b346 | ||
|
|
c00e4c24a3 | ||
|
|
844fe2c250 | ||
|
|
6da97c5403 | ||
|
|
1e41b79611 | ||
|
|
5d33493233 | ||
|
|
38c65dbea8 | ||
|
|
218ba0d189 | ||
|
|
a36294a529 | ||
|
|
333ee713d4 | ||
|
|
b8ae0ed565 | ||
|
|
3b07198b8a | ||
|
|
8b94833ea0 | ||
|
|
0862ffef98 | ||
|
|
95dc12a71b | ||
|
|
0f29745bfa | ||
|
|
012950cb68 | ||
|
|
f9c06c22ae | ||
|
|
83c2704d53 | ||
|
|
74b6b4dea2 | ||
|
|
f6bc03324f | ||
|
|
3d4443e1b0 | ||
|
|
366a5d5737 | ||
|
|
6d5d279f61 | ||
|
|
20970db404 | ||
|
|
1eff4ab4ff | ||
|
|
1aed3b4d91 | ||
|
|
9379bc1f6e | ||
|
|
9ec2b9ff1d | ||
|
|
4bfe326868 | ||
|
|
6074bb033d | ||
|
|
ec7b543bf2 | ||
|
|
b7c3cc5532 | ||
|
|
1c3d642be2 | ||
|
|
a2f84e943a | ||
|
|
4ae36d319c | ||
|
|
7fe0bb21b2 | ||
|
|
0174da6c77 | ||
|
|
3d6d714495 | ||
|
|
adc3703fba | ||
|
|
6de253df58 | ||
|
|
f31bea2c70 | ||
|
|
b61286891f | ||
|
|
6db7642331 | ||
|
|
91b5d039cc | ||
|
|
5692e9dc86 | ||
|
|
7849ff2b92 | ||
|
|
e77ec102d9 | ||
|
|
7ea23e0fcc | ||
|
|
e60a3a697e | ||
|
|
d899360c34 | ||
|
|
98c4d9bdba | ||
|
|
24730ebd0f | ||
|
|
cab346686d | ||
|
|
d369d4b50f | ||
|
|
6897d063c2 | ||
|
|
4ddddc134a | ||
|
|
44a330ac02 | ||
|
|
f220ea367e | ||
|
|
43c0770770 | ||
|
|
82286ea7ed | ||
|
|
01d6f751b1 | ||
|
|
7a25f348e4 | ||
|
|
12a0a35adf | ||
|
|
0f8abb6026 | ||
|
|
8e2f3fdecd | ||
|
|
26e3634814 | ||
|
|
9a897e0cf4 | ||
|
|
1bc15e57a6 | ||
|
|
09087a4d8f | ||
|
|
6934380b9c | ||
|
|
618aed3278 | ||
|
|
c8c0061244 | ||
|
|
ad4b68a390 | ||
|
|
a95e313733 | ||
|
|
8b25d3e32e | ||
|
|
a0a7623743 | ||
|
|
5e20ac0454 | ||
|
|
d182736bd7 | ||
|
|
159398b391 | ||
|
|
e4ccb1c43f | ||
|
|
9d75ad6cea | ||
|
|
b30697ea20 | ||
|
|
0c0f05b6d9 | ||
|
|
50d21fcdca | ||
|
|
c49a02d1c5 | ||
|
|
c26594a3ec | ||
|
|
6572e6f10e | ||
|
|
5144907ec0 | ||
|
|
ba8ed23292 | ||
|
|
6583b0f530 | ||
|
|
a3bea76994 | ||
|
|
ebe8ce7109 | ||
|
|
df9d879f22 | ||
|
|
46b1aca5f1 | ||
|
|
dd4ac4e6d6 | ||
|
|
fe23e61036 | ||
|
|
b88c4211b2 | ||
|
|
9e0d1a7285 | ||
|
|
73f94fecb5 | ||
|
|
0e24ebdb26 | ||
|
|
d5bd2143e2 | ||
|
|
7d412e4c18 | ||
|
|
ac5ac5e06b | ||
|
|
53e4dc7acb | ||
|
|
4d3e7d694b | ||
|
|
ca271900e6 | ||
|
|
f8c8c3deff | ||
|
|
76ad67307b | ||
|
|
ffab7ae697 | ||
|
|
8909386d0c | ||
|
|
8e9f22537f | ||
|
|
9366003f7b | ||
|
|
9018536651 | ||
|
|
ec8ef4d1d7 | ||
|
|
72a18d6abf | ||
|
|
79e81340dc | ||
|
|
4baffed481 | ||
|
|
77131a39d4 | ||
|
|
40d00f7cbd | ||
|
|
fe1f825fdf | ||
|
|
64973e43de | ||
|
|
e284584506 | ||
|
|
0667067c0c | ||
|
|
50d2bfb272 | ||
|
|
06eedff49f | ||
|
|
afbbad1604 | ||
|
|
9d5c4dbb33 | ||
|
|
ce90472624 | ||
|
|
85dcc8ba63 | ||
|
|
c9e192fae4 | ||
|
|
cee8b2d787 | ||
|
|
d3465be672 | ||
|
|
e99b33c51e | ||
|
|
43fe985143 | ||
|
|
699046dad5 | ||
|
|
6482a5e214 | ||
|
|
fef3879c85 | ||
|
|
9f7b066ca6 | ||
|
|
02218e624a | ||
|
|
5a5928483f | ||
|
|
11b3cee346 | ||
|
|
949065be2c | ||
|
|
6168ab270f | ||
|
|
667862223c | ||
|
|
2f5cf429ed | ||
|
|
7a94724dbc | ||
|
|
79c79432f5 | ||
|
|
d32ea9f9a1 | ||
|
|
907c2c7e97 | ||
|
|
960e45d0fa | ||
|
|
8bf9103fcf | ||
|
|
4eb78e151e | ||
|
|
3efd810fe1 | ||
|
|
c21cdc2131 | ||
|
|
a4ac98332b | ||
|
|
073e7b36fa | ||
|
|
44c69ded78 | ||
|
|
1e170714c0 | ||
|
|
f47cddbd5c | ||
|
|
1387e29591 | ||
|
|
10d96a9767 | ||
|
|
fd5041965b | ||
|
|
33c624c76f | ||
|
|
913f93b91f | ||
|
|
d34ec00a3a | ||
|
|
e9c6924530 | ||
|
|
c1facb939e | ||
|
|
ad16d8b532 | ||
|
|
9f0620f97e | ||
|
|
a040b1c181 | ||
|
|
8121e4942b | ||
|
|
f5e57da106 | ||
|
|
b8dfb26a3a | ||
|
|
079338f89d | ||
|
|
627d627995 | ||
|
|
19343540b9 | ||
|
|
915adb2fd3 | ||
|
|
c25cd3c005 | ||
|
|
32ab051bbc | ||
|
|
f7e70e56e3 | ||
|
|
9e52e6a320 | ||
|
|
f3a0c390b1 | ||
|
|
bb32c0480c | ||
|
|
8d167baf46 | ||
|
|
acfa0171ca | ||
|
|
fdcaa358e5 | ||
|
|
ce8e1e0ae3 | ||
|
|
7cb4ff97ea | ||
|
|
5d16836b05 | ||
|
|
fc22677f3f | ||
|
|
787a2591c4 | ||
|
|
95cc2830c8 | ||
|
|
5bae8245bd | ||
|
|
e979c2753b | ||
|
|
dcfd4e05ec | ||
|
|
1662269153 | ||
|
|
ce99bf2266 | ||
|
|
1952d1ca23 | ||
|
|
bf80799fb6 | ||
|
|
38f89042ac | ||
|
|
67b2467bc9 | ||
|
|
345be469fa | ||
|
|
04522c0b80 | ||
|
|
4a9e002440 | ||
|
|
6998036bdc | ||
|
|
e09cb10439 | ||
|
|
caa318b808 | ||
|
|
58fd4374c7 | ||
|
|
f89d444ddf | ||
|
|
49bee0c3fd | ||
|
|
00f7346578 | ||
|
|
b1add204b6 | ||
|
|
6d34bd45d2 | ||
|
|
992947ac99 | ||
|
|
ba55cdde87 | ||
|
|
c01a489dce | ||
|
|
0beb683c40 | ||
|
|
b134c90bfd | ||
|
|
6c10cf1a5a | ||
|
|
311b65da00 | ||
|
|
8a8f68d908 | ||
|
|
b7aa1ab3dc | ||
|
|
27d6df7a38 | ||
|
|
8f7e1bfc38 | ||
|
|
7accf70e17 | ||
|
|
e2ac212fc3 | ||
|
|
09d3763a02 | ||
|
|
ea37374ea5 | ||
|
|
a203b89ad4 | ||
|
|
0a8198b2ca | ||
|
|
d84ff97019 | ||
|
|
412dadce76 | ||
|
|
b65f620ff7 | ||
|
|
8b50e5252f | ||
|
|
76b1bf2c39 | ||
|
|
86e334e4fa | ||
|
|
17e89bb55b | ||
|
|
d5fba23d73 | ||
|
|
e96053ba20 | ||
|
|
c8d91884c8 | ||
|
|
bd2d7ce007 | ||
|
|
6d6e9d97b5 | ||
|
|
9ab855fd92 | ||
|
|
c1d8de4558 | ||
|
|
f19b274fcc | ||
|
|
0873211d13 | ||
|
|
bcc5774365 | ||
|
|
84929840d0 | ||
|
|
d6171f7fe7 | ||
|
|
9acba2ddf0 | ||
|
|
75e1ea7f7d | ||
|
|
4f1b4f1947 | ||
|
|
a269c43d1c | ||
|
|
83938e10b2 | ||
|
|
0d1d64e83c | ||
|
|
75c8e44575 | ||
|
|
29d196410e | ||
|
|
e2e131146d | ||
|
|
60cb5561f0 | ||
|
|
55c5230eb2 | ||
|
|
da4d6b85fc | ||
|
|
c08cee8052 | ||
|
|
3424bef5d0 | ||
|
|
e72e30bc4e | ||
|
|
99293cc019 | ||
|
|
3bfe0e3c16 | ||
|
|
4abf25ef13 | ||
|
|
e385f3e09d | ||
|
|
a4bd352486 | ||
|
|
6a565d0a45 | ||
|
|
2e254af935 | ||
|
|
12df8cf060 | ||
|
|
3c1af0e266 | ||
|
|
3b00cfa97d | ||
|
|
9cfe6c569d | ||
|
|
78f00e5c5f | ||
|
|
30fd160079 | ||
|
|
3842f7795b | ||
|
|
419ee2660a | ||
|
|
ff77cc28fa | ||
|
|
d28d72c42d | ||
|
|
2b14fefb5c | ||
|
|
25ce24a17d | ||
|
|
b840db12a6 | ||
|
|
2035a49c40 | ||
|
|
bb94cfc7a1 | ||
|
|
ef63fce7c4 | ||
|
|
bd0fd90e2d | ||
|
|
7de943c6e4 | ||
|
|
17be0bb39e | ||
|
|
7e53228359 | ||
|
|
afcc784797 | ||
|
|
6e976b0583 | ||
|
|
8f66de365e | ||
|
|
b35f1e6174 | ||
|
|
a998de5973 | ||
|
|
e1b9134d7f | ||
|
|
544bc10997 | ||
|
|
2e5b22417d | ||
|
|
38445a1dd2 | ||
|
|
340aaf42de | ||
|
|
359f70b9f9 | ||
|
|
408e4a54d8 | ||
|
|
59414dadfc | ||
|
|
e3b2be0261 | ||
|
|
6816f941d9 | ||
|
|
1dddd98741 | ||
|
|
0c31968e3c | ||
|
|
2f7e7b0072 | ||
|
|
d8a6eba2f3 | ||
|
|
18b6e25cf3 | ||
|
|
df2c2fc7d5 | ||
|
|
e717fad762 | ||
|
|
fef224459c | ||
|
|
9348a0deb7 | ||
|
|
fa0ec1e7d7 | ||
|
|
98dfebc3aa | ||
|
|
92ccd3dc78 | ||
|
|
a9a0b4ee49 | ||
|
|
5668794171 | ||
|
|
6759146426 | ||
|
|
d24cfeeabc | ||
|
|
a36c8599e7 | ||
|
|
9e1dd270ae | ||
|
|
386d6a1cb2 | ||
|
|
a2ede31ae6 | ||
|
|
f3ae7f00a0 | ||
|
|
56df1f6963 | ||
|
|
78dc6e5154 | ||
|
|
026836ebc5 | ||
|
|
d926cbdef2 | ||
|
|
d1f19a5cad | ||
|
|
ce6494ba64 | ||
|
|
b2c72221f7 | ||
|
|
5749276812 | ||
|
|
a7800b9a7a | ||
|
|
691271c74f | ||
|
|
475501595e | ||
|
|
fd490bd685 | ||
|
|
546393d9c3 | ||
|
|
75f06f9b62 | ||
|
|
80d4272ee5 | ||
|
|
ae0c28ef8f | ||
|
|
5c54128f8d | ||
|
|
10bcae7cf0 | ||
|
|
d72d55c965 | ||
|
|
6c10b06d5a | ||
|
|
95bb96ebbf | ||
|
|
8f163eb634 | ||
|
|
de358099cf | ||
|
|
cbf8dac5cc | ||
|
|
e81eefcf62 | ||
|
|
c0eec734a1 | ||
|
|
6c9c560b86 | ||
|
|
c9928fc1f9 | ||
|
|
5bf66ca92e | ||
|
|
9a638bc856 | ||
|
|
67f9aad1b9 | ||
|
|
60729c49df | ||
|
|
ac92498832 | ||
|
|
a94c050fd9 | ||
|
|
07b0e2980e | ||
|
|
d0600662ba | ||
|
|
8bc40dbea1 | ||
|
|
e086a03692 | ||
|
|
bbe5b588c0 | ||
|
|
d32026013f | ||
|
|
50fcac67c3 | ||
|
|
fc007e96d7 | ||
|
|
ad2a4d2d5d | ||
|
|
d90b59191f | ||
|
|
458ca42f22 | ||
|
|
a50d3ba692 | ||
|
|
fb1c17dc51 | ||
|
|
da1beef94d | ||
|
|
32ab6311ae | ||
|
|
08410820ab | ||
|
|
3c70512d8e | ||
|
|
1e26543f99 | ||
|
|
1c96443e73 | ||
|
|
9bab7bf1e7 | ||
|
|
cf8af114fe | ||
|
|
f0cd064195 | ||
|
|
9d44bc1593 | ||
|
|
67c1f12086 | ||
|
|
56f8f5d09b | ||
|
|
b7acef8cb0 | ||
|
|
a746cab21c | ||
|
|
b1e6699330 | ||
|
|
4190b19450 | ||
|
|
f050866d94 | ||
|
|
db86daf86d | ||
|
|
97038aab37 | ||
|
|
a42d053cbb | ||
|
|
50208c0088 | ||
|
|
8a5e494cfe | ||
|
|
5f2e2a808e | ||
|
|
c84f124511 | ||
|
|
d7de3edcf4 | ||
|
|
b9e2c9aa95 | ||
|
|
75979e3999 | ||
|
|
c113035d3d | ||
|
|
433974d77d | ||
|
|
121b76284a | ||
|
|
46cc593a9e | ||
|
|
de58e92df9 | ||
|
|
0acc90a691 | ||
|
|
70fe42f359 | ||
|
|
3d1df73a99 | ||
|
|
0336bfa906 | ||
|
|
6c70d5bf03 | ||
|
|
0560332768 | ||
|
|
7adbf52aeb | ||
|
|
795183c7ab | ||
|
|
4e0cd4cddf | ||
|
|
f187a368e4 | ||
|
|
2a645747de | ||
|
|
deb55d3bb2 | ||
|
|
a8991bfb14 | ||
|
|
c1fb72f393 | ||
|
|
35d9be5c10 | ||
|
|
c1686d52e9 | ||
|
|
c09d1d25b2 | ||
|
|
d25a3ce17c | ||
|
|
263aeeee9b | ||
|
|
b4006d3d36 | ||
|
|
8c9b53419d | ||
|
|
b62bc9d161 | ||
|
|
6a8db89614 | ||
|
|
5cd09a6503 | ||
|
|
5d38a1996c | ||
|
|
2254e82bf0 | ||
|
|
308379406a | ||
|
|
c04237ac76 | ||
|
|
047dda5ab3 | ||
|
|
3c8a9bab0c | ||
|
|
c782b46f32 | ||
|
|
2acc715ebe | ||
|
|
5ddcdf99c1 | ||
|
|
c4196e7d5a | ||
|
|
173f520929 | ||
|
|
ca3d4de819 | ||
|
|
c460323c5b | ||
|
|
d1f6ed4a55 | ||
|
|
569e12e83c | ||
|
|
caff3e5d0c | ||
|
|
20fb5d6699 | ||
|
|
00ec6c9034 | ||
|
|
0b79f8e345 | ||
|
|
373e417729 | ||
|
|
3903eedc1a | ||
|
|
de175eb3cd | ||
|
|
8738b517cc | ||
|
|
34b78825d2 | ||
|
|
d7d988b067 | ||
|
|
ecbba5ea61 | ||
|
|
90f078a0f8 | ||
|
|
3f0e8fcc2d | ||
|
|
aa7e8c0ed4 | ||
|
|
df82c17c1c | ||
|
|
96738976fb | ||
|
|
1ea63bf3d0 | ||
|
|
3a9f0566b6 | ||
|
|
81f80b4b0c | ||
|
|
1a266f0b19 | ||
|
|
95fbdce957 | ||
|
|
7b0cea02bb | ||
|
|
d2669c2de0 | ||
|
|
08d42f1c2c | ||
|
|
b836a28e77 | ||
|
|
6d545565f6 | ||
|
|
449bde4b03 | ||
|
|
dc8f6d4d01 | ||
|
|
ab4dab85cd | ||
|
|
c55c77e349 | ||
|
|
54d659edb6 | ||
|
|
2de8930034 | ||
|
|
16a051f4a0 | ||
|
|
3dca51d23c | ||
|
|
d5cf3935d8 | ||
|
|
327f3d6db1 | ||
|
|
49dcdf3be0 | ||
|
|
f951471f1d | ||
|
|
6b2310ff38 | ||
|
|
7e41f28864 | ||
|
|
68f67aecce | ||
|
|
c54bc85b5c | ||
|
|
43cea075dd | ||
|
|
be051afce9 | ||
|
|
c25e05b746 | ||
|
|
8543e885b7 | ||
|
|
f335e35ba4 | ||
|
|
3cf352ee56 | ||
|
|
4f25124d92 | ||
|
|
cc72d59965 | ||
|
|
9dc7e953c9 | ||
|
|
984a04972f | ||
|
|
cc425116a1 | ||
|
|
98061f9dce | ||
|
|
81f7507bf2 | ||
|
|
736c58ce05 | ||
|
|
a8ce9e9952 | ||
|
|
8bc7485c88 | ||
|
|
393871e214 | ||
|
|
53c3767b1b | ||
|
|
234f378e3f | ||
|
|
5957286d57 | ||
|
|
f73f349153 | ||
|
|
59d034ce6e | ||
|
|
244d0d2ba4 | ||
|
|
d878bb197f | ||
|
|
6bd0ed4344 | ||
|
|
6635ccb603 | ||
|
|
a0688274b6 | ||
|
|
c569afb5da | ||
|
|
b01964618d | ||
|
|
9b194946c3 | ||
|
|
c3bbefc259 | ||
|
|
7863a6853c | ||
|
|
cff27200fa | ||
|
|
46720dcf0f | ||
|
|
f592e46442 | ||
|
|
2200783de7 | ||
|
|
8a1e5376f7 | ||
|
|
1925cf4033 | ||
|
|
6b95c7ed6f | ||
|
|
7435d602dd | ||
|
|
2b6c2cd6fc | ||
|
|
84a48d3aec | ||
|
|
14da0f18ab | ||
|
|
bc5b32bcea | ||
|
|
3f7f1a8cdb | ||
|
|
acf508f074 | ||
|
|
f45398e20b | ||
|
|
9304a911ea | ||
|
|
fbde8d42fe | ||
|
|
8875cb2f9e | ||
|
|
8c9e763f74 | ||
|
|
97dcb4ca91 | ||
|
|
95fb1bfd1c | ||
|
|
8b91d5774f | ||
|
|
ae227aad77 | ||
|
|
3a3c5701b7 | ||
|
|
56fe39d346 | ||
|
|
b3444d2398 | ||
|
|
f7a54e13c3 | ||
|
|
796533b7a9 | ||
|
|
f9abf7af1d | ||
|
|
e5fe13337b | ||
|
|
94beae5282 | ||
|
|
65b29cb68d | ||
|
|
c6e0429584 | ||
|
|
30cfa8a5f4 | ||
|
|
67074fa895 | ||
|
|
609b0160f7 | ||
|
|
0c30e8fd53 | ||
|
|
662c48ed37 | ||
|
|
a63f2304fe | ||
|
|
4af80d2b61 | ||
|
|
113054c92c | ||
|
|
53d8f7fad7 | ||
|
|
fb525728c0 | ||
|
|
9e32964382 | ||
|
|
f524d2605a | ||
|
|
fac7fe3281 | ||
|
|
2aea864fbe | ||
|
|
9652460ba5 | ||
|
|
fde3d19a9d | ||
|
|
0ed0853489 | ||
|
|
bafbacc18a | ||
|
|
cd7aa37a72 | ||
|
|
e79aaff750 | ||
|
|
de226a9249 | ||
|
|
fc162f1429 | ||
|
|
6175f4eb29 | ||
|
|
e74438f54d | ||
|
|
64c155bede | ||
|
|
c8828f40ba | ||
|
|
ee3336de9e | ||
|
|
f9a5907762 | ||
|
|
67c94265b1 | ||
|
|
ba704d61b6 | ||
|
|
a1973c2a89 | ||
|
|
3735136009 | ||
|
|
3ed3f3e830 | ||
|
|
3f54828114 | ||
|
|
1c648d6396 | ||
|
|
296302a236 | ||
|
|
f497003948 | ||
|
|
ba5757e377 | ||
|
|
02ef9c687c | ||
|
|
a184ae9888 | ||
|
|
7a86ba0b51 | ||
|
|
8ad37c6ce3 | ||
|
|
ccb4876d88 | ||
|
|
a4b3a07885 | ||
|
|
9087b41352 | ||
|
|
8682d7c6ec | ||
|
|
dde0b75d17 | ||
|
|
a6642ab004 | ||
|
|
caabc04ffe | ||
|
|
f552babde6 | ||
|
|
d51287fa8b | ||
|
|
2cf845ceac | ||
|
|
55c3b06d0f | ||
|
|
2a2e169da5 | ||
|
|
6d32e6a1a7 | ||
|
|
542873159b | ||
|
|
19a9e5aa2c | ||
|
|
9c1e313472 | ||
|
|
d17c0bceb2 | ||
|
|
8f95f11f35 | ||
|
|
cd9a62f40b | ||
|
|
bbc407660d | ||
|
|
d1358b3d4f | ||
|
|
2eaf6d7d81 | ||
|
|
73da287c6c | ||
|
|
9ba7ad147a | ||
|
|
b1e0a24184 | ||
|
|
894911126b | ||
|
|
0b97c19862 | ||
|
|
89e2133341 | ||
|
|
3f4a7225b1 | ||
|
|
bd9049deb6 | ||
|
|
d316b08b6d | ||
|
|
6c2b782964 | ||
|
|
cefc175790 | ||
|
|
d7df0fb48b | ||
|
|
c0d1ae6afc | ||
|
|
c8c34dca29 | ||
|
|
588545c3f9 | ||
|
|
7bfad09c49 | ||
|
|
2860f4a343 | ||
|
|
731196094b | ||
|
|
c71e685913 | ||
|
|
fead462124 | ||
|
|
42d14ee1d6 | ||
|
|
1e78b65cd7 | ||
|
|
37893bb394 | ||
|
|
0744007701 | ||
|
|
8677b0d18d | ||
|
|
7a73bd98d3 | ||
|
|
aa4c02cfcd | ||
|
|
19bd6652af | ||
|
|
0d48884e19 | ||
|
|
5e8605981b | ||
|
|
bcf3281a10 | ||
|
|
37fe5dc4e3 | ||
|
|
9609bad31c | ||
|
|
f73ba9cc4a | ||
|
|
4eec31e42d | ||
|
|
8d7ab7b1b0 | ||
|
|
284d5c79b0 | ||
|
|
bc78331d5b | ||
|
|
ab87420c9d | ||
|
|
1f15f13aac | ||
|
|
fe70ca80e6 | ||
|
|
17ef381d7c | ||
|
|
ac875d3961 | ||
|
|
3c42240227 | ||
|
|
d7b2631410 | ||
|
|
e66b1b4192 | ||
|
|
803dc9c576 | ||
|
|
5426116ed4 | ||
|
|
c715c8ec02 | ||
|
|
a05e4147a3 | ||
|
|
0e76d52322 | ||
|
|
353bae0f13 | ||
|
|
39b4266458 | ||
|
|
bc5422821d | ||
|
|
b24ad2854c | ||
|
|
a9af0598e7 | ||
|
|
ef0c9fdd16 | ||
|
|
99f95af74e | ||
|
|
6a2e6709b9 | ||
|
|
1e8d930ef0 | ||
|
|
e4c6823ee2 | ||
|
|
e2e32b4b37 | ||
|
|
7806db1d3f | ||
|
|
3382b67bb8 | ||
|
|
b6866ac518 | ||
|
|
f06422d5fa | ||
|
|
e0a0c1c641 | ||
|
|
8baff2c72d | ||
|
|
4805d18fe9 | ||
|
|
988991a0ad | ||
|
|
917a24e858 | ||
|
|
0b2a77fce4 | ||
|
|
23d2b2a1cf | ||
|
|
aba3a2222d | ||
|
|
a49b944e29 | ||
|
|
333e2a20ff | ||
|
|
59c770a3bf | ||
|
|
df5227d48a | ||
|
|
8d0bf4c625 | ||
|
|
d1238efb6f | ||
|
|
8fcccbb8bc | ||
|
|
313a20d189 | ||
|
|
9c4fb88b7a | ||
|
|
3c1b1b18e4 | ||
|
|
0d7f65db1c | ||
|
|
ea39dfb415 | ||
|
|
9f9b08f6bc | ||
|
|
52a98b11e7 | ||
|
|
f6d414d903 | ||
|
|
eae5535b42 | ||
|
|
d9d3f25dfd | ||
|
|
d6b6e745b2 | ||
|
|
f903750d58 | ||
|
|
61ac985e09 | ||
|
|
22c7cb6c7d | ||
|
|
24415f5b24 | ||
|
|
931ac3dd1f | ||
|
|
99313cd2db | ||
|
|
7e6a4621c5 | ||
|
|
fa2358d22b | ||
|
|
0f63eadfc1 | ||
|
|
4844f7861b | ||
|
|
9ad9a8812f | ||
|
|
d036ef7f0a | ||
|
|
b211856394 | ||
|
|
71299df131 | ||
|
|
6bacda6a9a | ||
|
|
dc782ca5b5 | ||
|
|
4a9923db4b | ||
|
|
9d7641ad8a | ||
|
|
a7904a8d57 | ||
|
|
a4e6fef2a6 | ||
|
|
6c762b865a | ||
|
|
c424cad244 | ||
|
|
16ec34c463 | ||
|
|
44ef066ede | ||
|
|
f947a753db | ||
|
|
532cefe338 | ||
|
|
62bc814a44 | ||
|
|
dc11f34585 | ||
|
|
a5c3048a89 | ||
|
|
41b7110554 | ||
|
|
d38a963dbd | ||
|
|
b1aff5c997 | ||
|
|
f5b02265a4 | ||
|
|
21ab6cbe1e | ||
|
|
0560ccaf17 | ||
|
|
0e3c79fb79 | ||
|
|
1c9359bb4c | ||
|
|
1c4adc1e38 | ||
|
|
a5abd9d620 | ||
|
|
2ca299a31f | ||
|
|
1c930b31d9 | ||
|
|
331f029096 | ||
|
|
586e2602db | ||
|
|
83ac653637 | ||
|
|
d1ea2a6dd1 | ||
|
|
4f2bc9d402 | ||
|
|
3bee29338c | ||
|
|
26c31feb4a | ||
|
|
d8e4efe7cc | ||
|
|
fc3d13a4b1 | ||
|
|
d134b2279b | ||
|
|
f3efa9b914 | ||
|
|
9cdd62b62d | ||
|
|
271b80991a | ||
|
|
5f302866ca | ||
|
|
cce2bb5590 | ||
|
|
a01c1a40b9 | ||
|
|
c82b8f1128 | ||
|
|
862cec316f | ||
|
|
5445e5eaa1 | ||
|
|
22bcda23dc | ||
|
|
fcabb66f2e | ||
|
|
5b2911a5ed | ||
|
|
969d54050f | ||
|
|
92188f1c77 | ||
|
|
c18290c667 | ||
|
|
19b94655fb | ||
|
|
825adba39c | ||
|
|
2d28a7a0c4 | ||
|
|
4a09ce32e6 | ||
|
|
0ccb808b3f | ||
|
|
08c30e462c | ||
|
|
654c27490d | ||
|
|
06e788782b | ||
|
|
7d868c5195 | ||
|
|
2212cf24b4 | ||
|
|
d728c06851 | ||
|
|
b52420708e | ||
|
|
5bc67ebad9 | ||
|
|
a57adf9b2e | ||
|
|
aa9139c642 | ||
|
|
efbcacbec6 | ||
|
|
e5121fe7ea | ||
|
|
ac926b5fb4 | ||
|
|
44166e2653 | ||
|
|
e38fb5992f | ||
|
|
765ac5e714 | ||
|
|
f766ba340f | ||
|
|
5b060a768a | ||
|
|
54da30d93b | ||
|
|
838b10d3b6 | ||
|
|
cd84f0d5c6 | ||
|
|
641b72f3d1 | ||
|
|
63065f5c33 | ||
|
|
de2837229c | ||
|
|
596573293f | ||
|
|
93bd02017c | ||
|
|
2e1c7d2fb8 | ||
|
|
78b55f981c | ||
|
|
fb8b800fd7 | ||
|
|
67b0cd5522 | ||
|
|
68e8c4ba0f | ||
|
|
1b08c6ea88 | ||
|
|
6ae84b3334 | ||
|
|
6418033e51 | ||
|
|
f48dd46898 | ||
|
|
9020c11bd2 | ||
|
|
50b7d00683 | ||
|
|
e3078d2ac6 | ||
|
|
582418797d | ||
|
|
031b92bf03 | ||
|
|
1a529a7d7f | ||
|
|
f4591e3aad | ||
|
|
14c48bc1bf | ||
|
|
30cbf15b57 | ||
|
|
41dbfbb335 | ||
|
|
9191d3b6e5 | ||
|
|
d0a5274214 | ||
|
|
7f132e100f | ||
|
|
61fcfb80a8 | ||
|
|
046d4da45c | ||
|
|
94830a79a3 | ||
|
|
bb0a023380 | ||
|
|
c8444ef73a | ||
|
|
7c5f2d216f | ||
|
|
201ef814f9 | ||
|
|
00f6984929 | ||
|
|
f2e39f24c8 | ||
|
|
cd3f787aef | ||
|
|
86a9c46643 | ||
|
|
d639801f3c | ||
|
|
4517ec0629 | ||
|
|
9601476077 | ||
|
|
e2e067139c | ||
|
|
42d130d5c2 | ||
|
|
632b863cf7 | ||
|
|
83f8f5a150 | ||
|
|
1f272aa03c | ||
|
|
222162eaa7 | ||
|
|
102e832a58 | ||
|
|
e8ab3e45f3 | ||
|
|
ec4a9e92bf | ||
|
|
16a2d3803a | ||
|
|
4d58d9ef0e | ||
|
|
a263e50b3e | ||
|
|
6e2ebd5c79 | ||
|
|
86b8124bec | ||
|
|
c588aaea3a | ||
|
|
52e567b157 | ||
|
|
5fd15478a8 | ||
|
|
5ccffe6c1d | ||
|
|
7d32a9a9e9 | ||
|
|
0d5ff30acf | ||
|
|
a68454f7a4 | ||
|
|
d4f40feebf | ||
|
|
a25b2df453 | ||
|
|
a6f53fc99d | ||
|
|
35e74f3c10 | ||
|
|
1ea85c3b19 | ||
|
|
8756172b9b | ||
|
|
d4ef81edc6 | ||
|
|
0f18e702e8 | ||
|
|
63cdef4218 | ||
|
|
8596bc4997 | ||
|
|
e72aba98e6 | ||
|
|
8b13967570 | ||
|
|
0d842fe3ec | ||
|
|
73d61e09ed | ||
|
|
45b874fcd8 | ||
|
|
538b8c8a35 | ||
|
|
78d7255190 | ||
|
|
07ee63f04c |
16
.github/ISSUE_TEMPLATE.md
vendored
Normal file
16
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
[简述你的问题]
|
||||
|
||||
##### 使用版本
|
||||
[请提供你使用的Jumpserver版本 0.3.2 或 0.4.0]
|
||||
|
||||
##### 问题复现步骤
|
||||
1. [步骤1]
|
||||
2. [步骤2]
|
||||
|
||||
##### 具体表现[截图可能会更好些,最好能截全]
|
||||
|
||||
|
||||
##### 其他
|
||||
|
||||
|
||||
[注:] 完成后请关闭 issue
|
||||
26
.gitignore
vendored
Normal file
26
.gitignore
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
.DS_Store
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.swp
|
||||
.env
|
||||
env
|
||||
env*
|
||||
dist
|
||||
build
|
||||
*.egg
|
||||
*.egg-info
|
||||
_mailinglist
|
||||
dump.rdb
|
||||
.tox
|
||||
.cache/
|
||||
.idea/
|
||||
db.sqlite3
|
||||
config.py
|
||||
migrations/
|
||||
*.log
|
||||
host_rsa_key
|
||||
*.bat
|
||||
tags
|
||||
jumpserver.iml
|
||||
.python-version
|
||||
tmp/*
|
||||
1
.python-version
Normal file
1
.python-version
Normal file
@@ -0,0 +1 @@
|
||||
system
|
||||
22
Dockerfile
Normal file
22
Dockerfile
Normal file
@@ -0,0 +1,22 @@
|
||||
FROM jumpserver/python:v3.6.1
|
||||
LABEL MAINTAINER Jumpserver Team <ibuler@qq.com>
|
||||
|
||||
|
||||
COPY . /opt/jumpserver
|
||||
WORKDIR /opt/jumpserver
|
||||
|
||||
RUN yum -y install epel-release && yum clean all -y
|
||||
RUN cd requirements && yum -y install $(cat rpm_requirements.txt) && yum clean all -y
|
||||
RUN cd requirements && pip install -r requirements.txt
|
||||
|
||||
RUN rm -f data/db.sqlite3
|
||||
RUN rm -r .git
|
||||
RUN rm -f config.py
|
||||
|
||||
VOLUME /opt/jumpserver/data
|
||||
VOLUME /opt/jumpserver/logs
|
||||
|
||||
RUN cp config_docker.py config.py
|
||||
|
||||
EXPOSE 8080
|
||||
CMD cd utils && sh make_migrations.sh && sh init_db.sh && cd .. && python run_server.py
|
||||
16
Dockerfile-py3
Normal file
16
Dockerfile-py3
Normal file
@@ -0,0 +1,16 @@
|
||||
FROM centos:centos6
|
||||
LABEL MAINTAINER Jumpserver Team <ibuler@qq.com>
|
||||
|
||||
WORKDIR /tmp
|
||||
|
||||
RUN yum -y install wget sqlite-devel xz gcc automake zlib-devel openssl-devel; yum clean all
|
||||
|
||||
# Install Python
|
||||
RUN wget https://www.python.org/ftp/python/3.6.1/Python-3.6.1.tar.xz && \
|
||||
tar xvf Python-3.6.1.tar.xz && cd Python-3.6.1 && ./configure && make && make install && \
|
||||
rm -rf /tmp/{Python-3.6.1.tar.xz,Python-3.6.1}
|
||||
|
||||
RUN mv /usr/bin/python /usr/bin/python2
|
||||
RUN ln -s /usr/local/bin/python3 /usr/bin/python && ln -s /usr/local/bin/pip3 /usr/bin/pip
|
||||
RUN sed -i 's@/usr/bin/python@/usr/bin/python2@g' /usr/bin/yum
|
||||
|
||||
339
LICENSE
Normal file
339
LICENSE
Normal file
@@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
{description}
|
||||
Copyright (C) {year} {fullname}
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
{signature of Ty Coon}, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
117
README.md
117
README.md
@@ -1,36 +1,113 @@
|
||||
开源运维堡垒机(跳板机)系统
|
||||
==============================
|
||||
## Jumpserver
|
||||
|
||||
介绍和文档:
|
||||
--------------
|
||||
介绍和部署: [我的博客](http://laoguang.blog.51cto.com/6013350/1540080)
|
||||
[](https://www.python.org/)
|
||||
[](https://www.djangoproject.com/)
|
||||
[](https://www.ansible.com/)
|
||||
[](http://www.paramiko.org/)
|
||||
|
||||
Jumpserver is a open source proxy server, developed by `Python` and `Django`, aim to help
|
||||
companies to efficiently user, assets, authority and audit management
|
||||
|
||||
Jumpserver是一款使用Python, Django开发的开源跳板机系统, 助力互联网企业高效 用户、资产、权限、审计 管理
|
||||
|
||||
### Feature 功能
|
||||
- Auth 统一认证
|
||||
- CMDB 资产管理
|
||||
- Perm 统一授权
|
||||
- Audit 审计
|
||||
- LDAP AUTH 支持LDAP认证
|
||||
- Web terminal
|
||||
- SSH Server
|
||||
|
||||
|
||||
截图:
|
||||
-------------
|
||||

|
||||
### Environment 环境
|
||||
* Python 3.6
|
||||
* Django 1.11
|
||||
|
||||

|
||||
### Install 安装
|
||||
Using docker compose to setup it
|
||||
|
||||

|
||||
使用docker compose 安装,一键完成,docker compose 安装见 docker官方
|
||||
|
||||

|
||||
$ docker-compose up
|
||||
|
||||

|
||||
### Usage 使用
|
||||
1. Visit http://$HOST:8080 (访问 http://你的主机IP:8080 来访问 Jumpserver)
|
||||
|
||||
2. Click left navigation visit Applications-Terminal and accept coco and luna register
|
||||
(点击左侧 应用程序接受 Coco和Luna的注册)
|
||||
|
||||
3. Click Assets-Admin user, Create admin user
|
||||
(添加 管理用户)
|
||||
|
||||
4. Click Assets-System user, Create system user
|
||||
(添加 系统用户)
|
||||
|
||||
5. Click Assets-Asset, Add a asset
|
||||
(添加 资产)
|
||||
|
||||
6. Click Perms-Asset permission, Add a perm rule
|
||||
(添加授权规则,授权给admin)
|
||||
|
||||
7. Connect ssh server coco (连接 ssh server coco)
|
||||
|
||||
ssh -p2222 $USER@$Host
|
||||
|
||||
8. Visit web terminal server Luna, click server test connection
|
||||
(访问 访问Luna,点击左侧服务器连接测试)
|
||||
|
||||
http://$HOST:5000
|
||||
|
||||
|
||||
### Snapshot 截图
|
||||
|
||||

|
||||
https://github.com/jumpserver/jumpserver/issues/438
|
||||
|
||||

|
||||
|
||||

|
||||
### Demo
|
||||
|
||||

|
||||
demo使用了开发者模式,并发只能为1
|
||||
|
||||

|
||||
- Jumpserver: [访问](http://demo.jumpserver.org:8080) 账号: admin 密码: admin
|
||||
|
||||

|
||||
- Luna: [访问](http://demo.jumpserver.org:5000) 同Jumpserver认证
|
||||
|
||||

|
||||
- Coco: ssh -p 2222 admin@demo.jumpserver.org 密码: admin
|
||||
|
||||

|
||||
### ROADMAP
|
||||
|
||||
参见 https://github.com/jumpserver/jumpserver/milestone/2
|
||||
|
||||
### Docs 开发者文档
|
||||
|
||||
|
||||
* [Project structure 项目结构描述](https://github.com/jumpserver/jumpserver/blob/dev/docs/project_structure.md)
|
||||
* [Code style Python代码规范](https://github.com/jumpserver/jumpserver/blob/dev/docs/python_style_guide.md)
|
||||
* [Api style API设计规范](https://github.com/jumpserver/jumpserver/blob/dev/docs/api_style_guide.md)
|
||||
|
||||
### Contributor 贡献者
|
||||
#### 0.4.0
|
||||
- ibuler <广宏伟>
|
||||
- 小彧 <李磊> Django资深开发者,为users模块贡献了很多代码
|
||||
- sofia <周小侠> 资深前端工程师, luna前端代码贡献者和现在维护者
|
||||
- liuz <刘正> 全栈工程师, 编写了luna大部分代码
|
||||
- jiaxiangkong <陈尚委> Jumpserver测试运营
|
||||
|
||||
#### 0.3.2
|
||||
- halcyon <王墉> DevOps 资深开发者, 0.3.2 核心开发者之一
|
||||
- yumaojun03 <喻茂峻> DevOps 资深开发者,jperm开发者,擅长Python, Go以及PAAS平台开发
|
||||
- kelianchun <柯连春> DevOps 资产开发者,fix了很多connect.py bug
|
||||
|
||||
### 开发者群
|
||||
如果你为Jumpserver贡献过代码,请加一下群 (需要验证一下你的github id)
|
||||
|
||||
群号: 489385245
|
||||
|
||||
### License & Copyright
|
||||
Copyright (c) 2014-2017 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.
|
||||
|
||||
7
apps/__init__.py
Normal file
7
apps/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
103
apps/applications/api.py
Normal file
103
apps/applications/api.py
Normal file
@@ -0,0 +1,103 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from collections import OrderedDict
|
||||
import copy
|
||||
from rest_framework.generics import ListCreateAPIView
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.views import APIView, Response
|
||||
from rest_framework.permissions import AllowAny
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework.decorators import api_view
|
||||
|
||||
from .models import Terminal, TerminalHeatbeat
|
||||
from .serializers import TerminalSerializer, TerminalHeatbeatSerializer
|
||||
from .hands import IsSuperUserOrAppUser, IsAppUser, ProxyLog, \
|
||||
IsSuperUserOrAppUserOrUserReadonly
|
||||
from common.utils import get_object_or_none
|
||||
|
||||
|
||||
class TerminalRegisterView(ListCreateAPIView):
|
||||
queryset = Terminal.objects.all()
|
||||
serializer_class = TerminalSerializer
|
||||
permission_classes = (AllowAny,)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
name = request.data.get('name', '')
|
||||
remote_addr = request.META.get('X-Real-IP') or \
|
||||
request.META.get('REMOTE_ADDR')
|
||||
serializer = self.serializer_class(
|
||||
data={'name': name, 'remote_addr': remote_addr})
|
||||
|
||||
if get_object_or_none(Terminal, name=name):
|
||||
return Response({'msg': 'Already register, Need '
|
||||
'administrator active it'}, status=200)
|
||||
|
||||
if serializer.is_valid():
|
||||
terminal = serializer.save()
|
||||
app_user, access_key = terminal.create_related_app_user()
|
||||
data = OrderedDict()
|
||||
data['terminal'] = copy.deepcopy(serializer.data)
|
||||
data['user'] = app_user.to_json()
|
||||
data['access_key_id'] = access_key.id
|
||||
data['access_key_secret'] = access_key.secret
|
||||
return Response(data, status=201)
|
||||
else:
|
||||
data = {'msg': 'Not valid', 'detail': ';'.join(serializer.errors)}
|
||||
return Response(data, status=400)
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
return Response('', status=404)
|
||||
|
||||
|
||||
class TerminalViewSet(viewsets.ModelViewSet):
|
||||
queryset = Terminal.objects.all()
|
||||
serializer_class = TerminalSerializer
|
||||
permission_classes = (IsSuperUserOrAppUserOrUserReadonly,)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
return Response({'msg': 'Use register view except that'}, status=404)
|
||||
|
||||
# def destroy(self, request, *args, **kwargs):
|
||||
# instance = self.get_object()
|
||||
# if instance.user is not None:
|
||||
# instance.user.delete()
|
||||
# return super(TerminalViewSet, self).destroy(request, *args, **kwargs)
|
||||
|
||||
tasks = OrderedDict()
|
||||
# tasks = {1: [{'name': 'kill_proxy', 'proxy_log_id': 23}]}
|
||||
|
||||
|
||||
class TerminalHeatbeatViewSet(viewsets.ModelViewSet):
|
||||
queryset = TerminalHeatbeat.objects.all()
|
||||
serializer_class = TerminalHeatbeatSerializer
|
||||
permission_classes = (IsAppUser,)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
terminal = request.user.terminal
|
||||
TerminalHeatbeat.objects.create(terminal=terminal)
|
||||
task = tasks.get(terminal.name)
|
||||
tasks[terminal.name] = []
|
||||
return Response({'msg': 'Success',
|
||||
'tasks': task},
|
||||
status=201)
|
||||
|
||||
|
||||
class TerminateConnectionView(APIView):
|
||||
def post(self, request, *args, **kwargs):
|
||||
if isinstance(request.data, dict):
|
||||
data = [request.data]
|
||||
else:
|
||||
data = request.data
|
||||
for d in data:
|
||||
proxy_log_id = d.get('proxy_log_id')
|
||||
proxy_log = get_object_or_404(ProxyLog, id=proxy_log_id)
|
||||
terminal_id = proxy_log.terminal
|
||||
if terminal_id in tasks:
|
||||
tasks[terminal_id].append({'name': 'kill_proxy',
|
||||
'proxy_log_id': proxy_log_id})
|
||||
else:
|
||||
tasks[terminal_id] = [{'name': 'kill_proxy',
|
||||
'proxy_log_id': proxy_log_id}]
|
||||
|
||||
return Response({'msg': 'get it'})
|
||||
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'
|
||||
18
apps/applications/forms.py
Normal file
18
apps/applications/forms.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
#
|
||||
|
||||
from django import forms
|
||||
|
||||
from .models import Terminal
|
||||
|
||||
|
||||
class TerminalForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Terminal
|
||||
fields = ['name', 'remote_addr', 'type', 'url', 'comment']
|
||||
help_texts = {
|
||||
'url': 'Example: ssh://192.168.1.1:22 or http://jms.jumpserver.org, that user login'
|
||||
}
|
||||
widgets = {
|
||||
'name': forms.TextInput(attrs={'readonly': 'readonly'})
|
||||
}
|
||||
8
apps/applications/hands.py
Normal file
8
apps/applications/hands.py
Normal file
@@ -0,0 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from users.models import User
|
||||
from users.permissions import IsSuperUserOrAppUser, IsAppUser, \
|
||||
IsSuperUserOrAppUserOrUserReadonly
|
||||
from audits.models import ProxyLog
|
||||
from users.utils import AdminUserRequiredMixin
|
||||
62
apps/applications/models.py
Normal file
62
apps/applications/models.py
Normal file
@@ -0,0 +1,62 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from users.models import User
|
||||
|
||||
|
||||
class Terminal(models.Model):
|
||||
TYPE_CHOICES = (
|
||||
('SSH', 'SSH Terminal'),
|
||||
('Web', 'Web Terminal')
|
||||
)
|
||||
name = models.CharField(max_length=30, unique=True, verbose_name=_('Name'))
|
||||
remote_addr = models.GenericIPAddressField(verbose_name=_('Remote address'), blank=True, null=True)
|
||||
type = models.CharField(choices=TYPE_CHOICES, max_length=3, blank=True, verbose_name=_('Terminal type'))
|
||||
user = models.OneToOneField(User, related_name='terminal', verbose_name='Application user',
|
||||
null=True, on_delete=models.CASCADE)
|
||||
url = models.CharField(max_length=100, blank=True, verbose_name=_('URL to login'))
|
||||
is_accepted = models.BooleanField(default=False, verbose_name='Is Accepted')
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
if self.user and self.user.is_active:
|
||||
return True
|
||||
return False
|
||||
|
||||
@is_active.setter
|
||||
def is_active(self, active):
|
||||
if self.user:
|
||||
self.user.is_active = active
|
||||
self.user.save()
|
||||
|
||||
def create_related_app_user(self):
|
||||
user, access_key = User.create_app_user(name=self.name, comment=self.comment)
|
||||
self.user = user
|
||||
self.save()
|
||||
return user, access_key
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
if self.user:
|
||||
self.user.delete()
|
||||
return super(Terminal, self).delete(using=using, keep_parents=keep_parents)
|
||||
|
||||
def __unicode__(self):
|
||||
active = 'Active' if self.user and self.user.is_active else 'Disabled'
|
||||
return '%s: %s' % (self.name, active)
|
||||
|
||||
__str__ = __unicode__
|
||||
|
||||
class Meta:
|
||||
ordering = ('is_accepted',)
|
||||
|
||||
|
||||
class TerminalHeatbeat(models.Model):
|
||||
terminal = models.ForeignKey(Terminal, on_delete=models.CASCADE)
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'terminal_heatbeat'
|
||||
41
apps/applications/serializers.py
Normal file
41
apps/applications/serializers.py
Normal file
@@ -0,0 +1,41 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.utils import timezone
|
||||
from rest_framework import serializers
|
||||
|
||||
from .models import Terminal, TerminalHeatbeat
|
||||
from .hands import ProxyLog
|
||||
|
||||
|
||||
class TerminalSerializer(serializers.ModelSerializer):
|
||||
proxy_online = serializers.SerializerMethodField()
|
||||
is_alive = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Terminal
|
||||
fields = ['id', 'name', 'remote_addr', 'type', 'url', 'comment',
|
||||
'is_accepted', 'is_active', 'get_type_display',
|
||||
'proxy_online', 'is_alive']
|
||||
|
||||
@staticmethod
|
||||
def get_proxy_online(obj):
|
||||
return ProxyLog.objects.filter(terminal=obj.name, is_finished=False).count()
|
||||
|
||||
@staticmethod
|
||||
def get_is_alive(obj):
|
||||
log = obj.terminalheatbeat_set.last()
|
||||
if log and timezone.now() - log.date_created < timezone.timedelta(seconds=600):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class TerminalHeatbeatSerializer(serializers.ModelSerializer):
|
||||
date_start = serializers.DateTimeField
|
||||
class Meta:
|
||||
model = TerminalHeatbeat
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
5
apps/applications/tasks.py
Normal file
5
apps/applications/tasks.py
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% 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="" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Terminal detail' %} </a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-default" href="{% url 'applications:terminal-update' pk=terminal.id %}"><i class="fa fa-edit"></i>Update</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-7" style="padding-left: 0">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span class="label"><b>{{ terminal.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 width="20%">{% trans 'Name' %}:</td>
|
||||
<td><b>{{ terminal.name }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Remote address' %}:</td>
|
||||
<td><b>{{ terminal.remote_addr }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'URL to login' %}:</td>
|
||||
<td><b>{{ terminal.url }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Terminal type' %}:</td>
|
||||
<td><b>{{ terminal.get_type_display }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Date created' %}:</td>
|
||||
<td><b>{{ terminal.date_created }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Comment' %}:</td>
|
||||
<td><b>{{ asset.comment }}</b></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
149
apps/applications/templates/applications/terminal_list.html
Normal file
149
apps/applications/templates/applications/terminal_list.html
Normal file
@@ -0,0 +1,149 @@
|
||||
{% extends '_base_list.html' %}
|
||||
{% load i18n static %}
|
||||
{% block custom_head_css_js %}
|
||||
{{ block.super }}
|
||||
|
||||
<style>
|
||||
div.dataTables_wrapper div.dataTables_filter,
|
||||
.dataTables_length {
|
||||
float: right !important;
|
||||
}
|
||||
div.dataTables_wrapper div.dataTables_filter {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
#modal .modal-body { max-height: 200px; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block table_search %}{% endblock %}
|
||||
{% block table_container %}
|
||||
{#<div class="uc pull-left m-l-5 m-r-5"><a href="{% url "users:user-create" %}" class="btn btn-sm btn-primary"> {% trans "Create user" %} </a></div>#}
|
||||
<table class="table table-striped table-bordered table-hover " id="terminal_list_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<div class="checkbox checkbox-default">
|
||||
<input type="checkbox" class="ipt_check_all">
|
||||
</div>
|
||||
</th>
|
||||
<th class="text-center">{% trans 'Name' %}</th>
|
||||
<th class="text-center">{% trans 'IP' %}</th>
|
||||
<th class="text-center">{% trans 'Type' %}</th>
|
||||
<th class="text-center">{% trans 'Session online' %}</th>
|
||||
<th class="text-center">{% trans 'Active' %}</th>
|
||||
<th class="text-center">{% trans 'Alive' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
{% include 'applications/terminal_modal_accept.html' %}
|
||||
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
var options = {
|
||||
ele: $('#terminal_list_table'),
|
||||
buttons: [],
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url "applications:terminal-detail" pk=99991937 %}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('99991937', rowData.id));
|
||||
}},
|
||||
{targets: 5, createdCell: function (td, cellData) {
|
||||
if (!cellData) {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 6, createdCell: function (td, cellData) {
|
||||
if (!cellData) {
|
||||
$(td).html('<i class="fa fa-circle text-danger"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-circle text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 7, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "applications:terminal-update" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'
|
||||
.replace('99991937', cellData);
|
||||
var delete_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-del" data-id="99991937" data-name="99991938">{% trans "Delete" %}</a>'
|
||||
.replace('99991937', cellData)
|
||||
.replace('99991938', rowData.name);
|
||||
var accept_btn = '<a class="btn btn-xs btn-primary btn-accept" data-id="99991937">{% trans "Accept" %}</a> '
|
||||
.replace('99991937', cellData);
|
||||
var reject_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-del" data-id="99991937" data-name="99991938">{% trans "Reject" %}</a>'
|
||||
.replace('99991937', cellData)
|
||||
.replace('99991938', rowData.name);
|
||||
var connect_btn = '<a href="{% url "applications:terminal-connect" pk=99991937 %}"" class="btn btn-xs btn-warning btn-connect" >{% trans "Connect" %}</a> '
|
||||
.replace('99991937', cellData);
|
||||
if (rowData.is_accepted) {
|
||||
{% if user.is_superuser %}
|
||||
$(td).html(connect_btn + update_btn + delete_btn);
|
||||
{% else %}
|
||||
$(td).html(connect_btn);
|
||||
{% endif %}
|
||||
} else {
|
||||
{% if user.is_superuser %}
|
||||
$(td).html(accept_btn + reject_btn);
|
||||
{% endif %}
|
||||
}
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-applications:terminal-list" %}',
|
||||
columns: [{data: function(){return ""}}, {data: "name" }, {data: "remote_addr" }, {data: "get_type_display" },
|
||||
{data: "proxy_online"}, {data: "is_active" }, {data: 'is_active'}, {data: "id"}],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
|
||||
$('#btn_terminal_accept').click(function () {
|
||||
var $form = $('#form_terminal_accept');
|
||||
function success(data, textStatus, jqXHR) {
|
||||
if (data.success === true) {
|
||||
window.location.reload()
|
||||
} else {
|
||||
$('#modal-error').html(data.msg).css('display', 'block');
|
||||
}
|
||||
}
|
||||
$form.ajaxSubmit({success: success});
|
||||
})
|
||||
|
||||
}).on('click', '.btn-del', function(){
|
||||
var $this = $(this);
|
||||
var id = $this.data('id');
|
||||
var name = $(this).data('name');
|
||||
var the_url = '{% url "api-applications:terminal-detail" pk=99991937 %}'.replace('99991937', id);
|
||||
objectDelete($this, name, the_url)
|
||||
|
||||
}).on('click', '.btn-accept', function () {
|
||||
var $this = $(this);
|
||||
var terminal_id = $this.data('id');
|
||||
var the_url = "{% url 'api-applications:terminal-detail' pk=99991937 %}".replace('99991937', terminal_id);
|
||||
var post_url = $('#form_terminal_accept').attr('action').replace('99991937', terminal_id);
|
||||
console.log(post_url);
|
||||
$.ajax({
|
||||
url: the_url,
|
||||
method: 'GET',
|
||||
success: function (data) {
|
||||
$('#id_name').val(data.name);
|
||||
$('#id_remote_addr').val(data.remote_addr);
|
||||
$('#id_type').val(data.type);
|
||||
$('#id_url').val(data.url);
|
||||
$('#id_comment').val(data.comment);
|
||||
$('#form_terminal_accept').attr('action', post_url)
|
||||
}
|
||||
});
|
||||
$('#modal_terminal_accept').modal({
|
||||
show: true
|
||||
});
|
||||
}).on('click', '.btn-connect', function () {
|
||||
var $this = $(this);
|
||||
var id = $this.data('id');
|
||||
console.log(id)
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,19 @@
|
||||
{% extends '_modal.html' %}
|
||||
{% load i18n %}
|
||||
{% block modal_id %}modal_terminal_accept{% endblock %}
|
||||
{% block modal_class %}modal-lg{% endblock %}
|
||||
{% block modal_title%}{% trans "Accept terminal registration" %}{% endblock %}
|
||||
{% block modal_body %}
|
||||
{% load bootstrap3 %}
|
||||
<form action="{% url 'applications:terminal-modal-accept' pk="99991937" %}" method="post" class="form-horizontal" id="form_terminal_accept" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<p class="alert alert-danger" id="modal-error" style="display: none"></p>
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
{% bootstrap_field form.remote_addr layout="horizontal" %}
|
||||
{% bootstrap_field form.type layout="horizontal" %}
|
||||
{% bootstrap_field form.url layout="horizontal" %}
|
||||
{% bootstrap_field form.comment layout="horizontal" %}
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
{% block modal_confirm_id %}btn_terminal_accept{% endblock %}
|
||||
@@ -0,0 +1,5 @@
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
@@ -0,0 +1,72 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load bootstrap3 %}
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet">
|
||||
<script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>
|
||||
<link href="{% static "css/plugins/datepicker/datepicker3.css" %}" rel="stylesheet">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<h5>{{ action }}</h5>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<form method="post" class="form-horizontal" action="" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<h3>{% trans 'Info' %}</h3>
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
{% bootstrap_field form.remote_addr layout="horizontal" %}
|
||||
{% bootstrap_field form.type layout="horizontal" %}
|
||||
{% bootstrap_field form.url layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Other' %}</h3>
|
||||
{% 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-white" type="reset">{% trans 'Reset' %}</button>
|
||||
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
|
||||
$('.input-group.date').datepicker({
|
||||
format: "yyyy-mm-dd",
|
||||
todayBtn: "linked",
|
||||
keyboardNavigation: false,
|
||||
forceParse: false,
|
||||
calendarWeeks: true,
|
||||
autoclose: true
|
||||
});
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
1
apps/applications/urls/__init__.py
Normal file
1
apps/applications/urls/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
24
apps/applications/urls/api_urls.py
Normal file
24
apps/applications/urls/api_urls.py
Normal file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.conf.urls import url
|
||||
from rest_framework import routers
|
||||
|
||||
from .. import api
|
||||
|
||||
app_name = 'applications'
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
router.register(r'v1/terminal/heatbeat', api.TerminalHeatbeatViewSet, 'terminal-heatbeat')
|
||||
router.register(r'v1/terminal', api.TerminalViewSet, 'terminal')
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^v1/terminal/register/$', api.TerminalRegisterView.as_view(),
|
||||
name='terminal-register'),
|
||||
url(r'^v1/terminate/connection/$', api.TerminateConnectionView.as_view(),
|
||||
name='terminate-connection')
|
||||
# url(r'^v1/terminal/heatbeat/$', api.TestHeatbeat.as_view())
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
||||
21
apps/applications/urls/views_urls.py
Normal file
21
apps/applications/urls/views_urls.py
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from .. import views
|
||||
|
||||
app_name = 'applications'
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^terminal/$', views.TerminalListView.as_view(), name='terminal-list'),
|
||||
url(r'^terminal/(?P<pk>\d+)/$', views.TerminalDetailView.as_view(),
|
||||
name='terminal-detail'),
|
||||
url(r'^terminal/(?P<pk>\d+)/connect/$', views.TerminalConnectView.as_view(),
|
||||
name='terminal-connect'),
|
||||
url(r'^terminal/(?P<pk>\d+)/update$', views.TerminalUpdateView.as_view(),
|
||||
name='terminal-update'),
|
||||
url(r'^terminal/(?P<pk>\d+)/modal/accept$', views.TerminalModelAccept.as_view(),
|
||||
name='terminal-modal-accept'),
|
||||
]
|
||||
115
apps/applications/views.py
Normal file
115
apps/applications/views.py
Normal file
@@ -0,0 +1,115 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
#
|
||||
|
||||
from django.views.generic import ListView, UpdateView, DeleteView, \
|
||||
DetailView, TemplateView
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.urls import reverse_lazy, reverse
|
||||
|
||||
from common.mixins import JSONResponseMixin
|
||||
from .models import Terminal
|
||||
from .forms import TerminalForm
|
||||
from .hands import AdminUserRequiredMixin
|
||||
|
||||
|
||||
class TerminalListView(LoginRequiredMixin, ListView):
|
||||
model = Terminal
|
||||
template_name = 'applications/terminal_list.html'
|
||||
form_class = TerminalForm
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(TerminalListView, self).get_context_data(**kwargs)
|
||||
context.update({
|
||||
'app': _('Terminal'),
|
||||
'action': _('Terminal list'),
|
||||
'form': self.form_class()
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
class TerminalUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
model = Terminal
|
||||
form_class = TerminalForm
|
||||
template_name = 'applications/terminal_update.html'
|
||||
success_url = reverse_lazy('applications:terminal-list')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(TerminalUpdateView, self).get_context_data(**kwargs)
|
||||
context.update({'app': _('Applications'), 'action': _('Update terminal')})
|
||||
return context
|
||||
|
||||
|
||||
class TerminalDetailView(LoginRequiredMixin, DetailView):
|
||||
model = Terminal
|
||||
template_name = 'applications/terminal_detail.html'
|
||||
context_object_name = 'terminal'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(TerminalDetailView, self).get_context_data(**kwargs)
|
||||
context.update({
|
||||
'app': _('Applications'),
|
||||
'action': _('Terminal detail')
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
class TerminalDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||
model = Terminal
|
||||
template_name = 'assets/delete_confirm.html'
|
||||
success_url = reverse_lazy('applications:applications-list')
|
||||
|
||||
|
||||
class TerminalModelAccept(AdminUserRequiredMixin, JSONResponseMixin, UpdateView):
|
||||
model = Terminal
|
||||
form_class = TerminalForm
|
||||
template_name = 'applications/terminal_modal_test.html'
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
print(request.POST)
|
||||
return super(TerminalModelAccept, self).post(request, *args, **kwargs)
|
||||
|
||||
def form_valid(self, form):
|
||||
terminal = form.save()
|
||||
terminal.is_accepted = True
|
||||
terminal.is_active = True
|
||||
terminal.save()
|
||||
data = {
|
||||
'success': True,
|
||||
'msg': 'success'
|
||||
}
|
||||
return self.render_json_response(data)
|
||||
|
||||
def form_invalid(self, form):
|
||||
print('form.data')
|
||||
data = {
|
||||
'success': False,
|
||||
'msg': str(form.errors),
|
||||
}
|
||||
return self.render_json_response(data)
|
||||
|
||||
|
||||
class TerminalConnectView(LoginRequiredMixin, DetailView):
|
||||
template_name = 'flash_message_standalone.html'
|
||||
model = Terminal
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
if self.object.type == 'Web':
|
||||
context = {
|
||||
'title': _('Redirect to web terminal'),
|
||||
'messages': _('Redirect to web terminal') + self.object.url,
|
||||
'auto_redirect': True,
|
||||
'interval': 3,
|
||||
'redirect_url': self.object.url
|
||||
}
|
||||
else:
|
||||
context = {
|
||||
'title': _('Connect ssh terminal'),
|
||||
'messages': _('You should use your ssh client tools '
|
||||
'connect terminal: {} <br /> <br />'
|
||||
'{}'.format(self.object.name, self.object.url)),
|
||||
'redirect_url': reverse('applications:terminal-list')
|
||||
}
|
||||
|
||||
kwargs.update(context)
|
||||
return super(TerminalConnectView, self).get_context_data(**kwargs)
|
||||
220
apps/assets/api.py
Normal file
220
apps/assets/api.py
Normal file
@@ -0,0 +1,220 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
|
||||
# Copyright (C) 2014-2017 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the GNU General Public License v2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://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.
|
||||
|
||||
from rest_framework import generics
|
||||
from rest_framework.response import Response
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from common.mixins import IDInFilterMixin
|
||||
from common.utils import get_object_or_none
|
||||
from .hands import IsSuperUser, IsAppUser, IsValidUser, \
|
||||
get_user_granted_assets, push_users
|
||||
from .models import AssetGroup, Asset, IDC, SystemUser, AdminUser
|
||||
from . import serializers
|
||||
from .tasks import update_assets_hardware_info
|
||||
from .utils import test_admin_user_connective_manual
|
||||
|
||||
|
||||
class AssetViewSet(IDInFilterMixin, BulkModelViewSet):
|
||||
"""API endpoint that allows Asset to be viewed or edited."""
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetSerializer
|
||||
permission_classes = (IsValidUser,)
|
||||
|
||||
def get_queryset(self):
|
||||
if self.request.user.is_superuser:
|
||||
queryset = super(AssetViewSet, self).get_queryset()
|
||||
else:
|
||||
queryset = get_user_granted_assets(self.request.user)
|
||||
idc_id = self.request.query_params.get('idc_id', '')
|
||||
system_users_id = self.request.query_params.get('system_user_id', '')
|
||||
asset_group_id = self.request.query_params.get('asset_group_id', '')
|
||||
admin_user_id = self.request.query_params.get('admin_user_id', '')
|
||||
if idc_id:
|
||||
queryset = queryset.filter(idc__id=idc_id)
|
||||
if system_users_id:
|
||||
queryset = queryset.filter(system_users__id=system_users_id)
|
||||
if admin_user_id:
|
||||
queryset = queryset.filter(admin_user__id=admin_user_id)
|
||||
if asset_group_id:
|
||||
queryset = queryset.filter(groups__id=asset_group_id)
|
||||
return queryset
|
||||
|
||||
|
||||
class AssetGroupViewSet(IDInFilterMixin, BulkModelViewSet):
|
||||
"""Asset group api set, for add,delete,update,list,retrieve resource"""
|
||||
queryset = AssetGroup.objects.all()
|
||||
serializer_class = serializers.AssetGroupSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
|
||||
class AssetUpdateGroupApi(generics.RetrieveUpdateAPIView):
|
||||
"""Asset update it's group api"""
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetUpdateGroupSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
|
||||
class AssetGroupUpdateApi(generics.RetrieveUpdateAPIView):
|
||||
"""Asset group, update it's asset member"""
|
||||
queryset = AssetGroup.objects.all()
|
||||
serializer_class = serializers.AssetGroupUpdateSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
|
||||
class AssetGroupUpdateSystemUserApi(generics.RetrieveUpdateAPIView):
|
||||
"""Asset group push system user"""
|
||||
queryset = AssetGroup.objects.all()
|
||||
serializer_class = serializers.AssetGroupUpdateSystemUserSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
|
||||
class IDCUpdateAssetsApi(generics.RetrieveUpdateAPIView):
|
||||
"""IDC update asset member"""
|
||||
queryset = IDC.objects.all()
|
||||
serializer_class = serializers.IDCUpdateAssetsSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
|
||||
class IDCViewSet(IDInFilterMixin, BulkModelViewSet):
|
||||
"""IDC api set, for add,delete,update,list,retrieve resource"""
|
||||
queryset = IDC.objects.all()
|
||||
serializer_class = serializers.IDCSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
|
||||
class AdminUserViewSet(IDInFilterMixin, BulkModelViewSet):
|
||||
"""Admin user api set, for add,delete,update,list,retrieve resource"""
|
||||
queryset = AdminUser.objects.all()
|
||||
serializer_class = serializers.AdminUserSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
|
||||
class SystemUserViewSet(IDInFilterMixin, BulkModelViewSet):
|
||||
"""System user api set, for add,delete,update,list,retrieve resource"""
|
||||
queryset = SystemUser.objects.all()
|
||||
serializer_class = serializers.SystemUserSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
|
||||
class SystemUserUpdateApi(generics.RetrieveUpdateAPIView):
|
||||
"""Asset update it's system user
|
||||
|
||||
when update then push system user to asset.
|
||||
"""
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetUpdateSystemUserSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
def patch(self, request, *args, **kwargs):
|
||||
asset = self.get_object()
|
||||
old_system_users = set(asset.system_users.all())
|
||||
response = super(SystemUserUpdateApi, self).patch(request, *args, **kwargs)
|
||||
system_users_new = set(asset.system_users.all())
|
||||
system_users = system_users_new - old_system_users
|
||||
system_users = [system_user._to_secret_json() for system_user in system_users]
|
||||
push_users.delay([asset._to_secret_json()], system_users)
|
||||
return response
|
||||
|
||||
|
||||
class SystemUserUpdateAssetsApi(generics.RetrieveUpdateAPIView):
|
||||
"""System user update it's assets"""
|
||||
queryset = SystemUser.objects.all()
|
||||
serializer_class = serializers.SystemUserUpdateAssetsSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
|
||||
class SystemUserUpdateAssetGroupApi(generics.RetrieveUpdateAPIView):
|
||||
"""System user update asset group"""
|
||||
queryset = SystemUser.objects.all()
|
||||
serializer_class = serializers.SystemUserUpdateAssetGroupSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
|
||||
class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||
"""Asset bulk update api"""
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
|
||||
class SystemUserAuthInfoApi(generics.RetrieveAPIView):
|
||||
"""Get system user auth info"""
|
||||
queryset = SystemUser.objects.all()
|
||||
permission_classes = (IsAppUser,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
system_user = self.get_object()
|
||||
data = {
|
||||
'id': system_user.id,
|
||||
'name': system_user.name,
|
||||
'username': system_user.username,
|
||||
'password': system_user.password,
|
||||
'private_key': system_user.private_key,
|
||||
'auth_method': system_user.auth_method,
|
||||
}
|
||||
return Response(data)
|
||||
|
||||
|
||||
class AssetRefreshHardwareView(generics.RetrieveAPIView):
|
||||
"""Refresh asset hardware info"""
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
asset_id = kwargs.get('pk')
|
||||
asset = get_object_or_404(Asset, pk=asset_id)
|
||||
summary = update_assets_hardware_info([asset])
|
||||
if len(summary['failed']) == 0:
|
||||
return super(AssetRefreshHardwareView, self).retrieve(request, *args, **kwargs)
|
||||
else:
|
||||
return Response('', status=502)
|
||||
|
||||
|
||||
class AssetAdminUserTestView(AssetRefreshHardwareView):
|
||||
"""Test asset admin user connectivity"""
|
||||
queryset = Asset.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
asset_id = kwargs.get('pk')
|
||||
asset = get_object_or_404(Asset, pk=asset_id)
|
||||
result = test_admin_user_connective_manual([asset])
|
||||
if result:
|
||||
return Response('1')
|
||||
else:
|
||||
return Response('0', status=502)
|
||||
|
||||
|
||||
class AssetGroupPushSystemUserView(generics.UpdateAPIView):
|
||||
"""Asset group push system user api"""
|
||||
queryset = AssetGroup.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
serializer_class = serializers.AssetSerializer
|
||||
|
||||
def patch(self, request, *args, **kwargs):
|
||||
asset_group = self.get_object()
|
||||
assets = asset_group.assets.all()
|
||||
system_user_id = self.request.data['system_user']
|
||||
system_user = get_object_or_none(SystemUser, id=system_user_id)
|
||||
if not assets or not system_user:
|
||||
return Response('Invalid system user id or asset group id', status=404)
|
||||
task = push_users.delay([asset._to_secret_json() for asset in assets],
|
||||
system_user._to_secret_json())
|
||||
return Response(task.id)
|
||||
7
apps/assets/apps.py
Normal file
7
apps/assets/apps.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AssetsConfig(AppConfig):
|
||||
name = 'assets'
|
||||
361
apps/assets/forms.py
Normal file
361
apps/assets/forms.py
Normal file
@@ -0,0 +1,361 @@
|
||||
# coding:utf-8
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .models import IDC, Asset, AssetGroup, AdminUser, SystemUser
|
||||
from common.utils import validate_ssh_private_key, ssh_pubkey_gen, ssh_key_gen, get_logger
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
class AssetCreateForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = [
|
||||
'hostname', 'ip', 'public_ip', 'port', 'type', 'comment',
|
||||
'admin_user', 'idc', 'groups', 'status', 'env', 'is_active'
|
||||
]
|
||||
widgets = {
|
||||
'groups': forms.SelectMultiple(
|
||||
attrs={'class': 'select2',
|
||||
'data-placeholder': _('Select asset groups')}),
|
||||
'admin_user': forms.Select(
|
||||
attrs={'class': 'select2',
|
||||
'data-placeholder': _('Select asset admin user')}),
|
||||
}
|
||||
help_texts = {
|
||||
'hostname': '* required',
|
||||
'ip': '* required',
|
||||
'system_users': _('System user will be granted for user to login '
|
||||
'assets (using ansible create automatic)'),
|
||||
'admin_user': _('Admin user should be exist on asset already, '
|
||||
'And have sudo ALL permission'),
|
||||
}
|
||||
|
||||
def clean_admin_user(self):
|
||||
if not self.cleaned_data['admin_user']:
|
||||
raise forms.ValidationError(_('Select admin user'))
|
||||
return self.cleaned_data['admin_user']
|
||||
|
||||
|
||||
class AssetUpdateForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = [
|
||||
'hostname', 'ip', 'port', 'groups', 'admin_user', 'idc', 'is_active',
|
||||
'type', 'env', 'status', 'public_ip', 'remote_card_ip', 'cabinet_no',
|
||||
'cabinet_pos', 'number', 'comment'
|
||||
]
|
||||
widgets = {
|
||||
'groups': forms.SelectMultiple(
|
||||
attrs={'class': 'select2',
|
||||
'data-placeholder': _('Select asset groups')}),
|
||||
'admin_user': forms.Select(
|
||||
attrs={'class': 'select2',
|
||||
'data-placeholder': _('Select asset admin user')}),
|
||||
}
|
||||
help_texts = {
|
||||
'hostname': '* required',
|
||||
'ip': '* required',
|
||||
'system_users': _('System user will be granted for user '
|
||||
'to login assets (using ansible create automatic)'),
|
||||
'admin_user': _('Admin user should be exist on asset '
|
||||
'already, And have sudo ALL permission'),
|
||||
}
|
||||
|
||||
|
||||
class AssetBulkUpdateForm(forms.ModelForm):
|
||||
assets = forms.MultipleChoiceField(
|
||||
required=True,
|
||||
help_text='* required',
|
||||
label=_('Select assets'),
|
||||
widget=forms.SelectMultiple(
|
||||
attrs={
|
||||
'class': 'select2',
|
||||
'data-placeholder': _('Select assets')
|
||||
}
|
||||
)
|
||||
)
|
||||
port = forms.IntegerField(min_value=1, max_value=65535,
|
||||
required=False, label=_('Port'))
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = [
|
||||
'assets', 'port', 'groups', 'admin_user', 'idc',
|
||||
'type', 'env', 'status',
|
||||
]
|
||||
widgets = {
|
||||
'groups': forms.SelectMultiple(
|
||||
attrs={'class': 'select2',
|
||||
'data-placeholder': _('Select asset groups')}),
|
||||
'admin_user': forms.Select(
|
||||
attrs={'class': 'select2',
|
||||
'data-placeholder': _('Select asset admin user')}),
|
||||
}
|
||||
|
||||
def save(self, commit=True):
|
||||
cleaned_data = {k: v for k, v in self.cleaned_data.items() if v is not None}
|
||||
assets_id = cleaned_data.pop('assets')
|
||||
groups = cleaned_data.pop('groups')
|
||||
assets = Asset.objects.filter(id__in=assets_id)
|
||||
assets.update(**cleaned_data)
|
||||
if groups:
|
||||
for asset in assets:
|
||||
asset.groups.set(groups)
|
||||
return assets
|
||||
|
||||
|
||||
class AssetGroupForm(forms.ModelForm):
|
||||
# See AdminUserForm comment same it
|
||||
assets = forms.ModelMultipleChoiceField(
|
||||
queryset=Asset.objects.all(),
|
||||
label=_('Asset'),
|
||||
required=False,
|
||||
widget=forms.SelectMultiple(
|
||||
attrs={'class': 'select2', 'data-placeholder': _('Select assets')})
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if kwargs.get('instance', None):
|
||||
initial = kwargs.get('initial', {})
|
||||
initial['assets'] = kwargs['instance'].assets.all()
|
||||
super(AssetGroupForm, self).__init__(*args, **kwargs)
|
||||
|
||||
def _save_m2m(self):
|
||||
super(AssetGroupForm, self)._save_m2m()
|
||||
assets = self.cleaned_data['assets']
|
||||
self.instance.assets.clear()
|
||||
self.instance.assets.add(*tuple(assets))
|
||||
|
||||
class Meta:
|
||||
model = AssetGroup
|
||||
fields = [
|
||||
"name", "comment",
|
||||
]
|
||||
help_texts = {
|
||||
'name': '* required',
|
||||
}
|
||||
|
||||
|
||||
class IDCForm(forms.ModelForm):
|
||||
# See AdminUserForm comment same it
|
||||
assets = forms.ModelMultipleChoiceField(
|
||||
queryset=Asset.objects.all(),
|
||||
label=_('Asset'),
|
||||
required=False,
|
||||
widget=forms.SelectMultiple(
|
||||
attrs={'class': 'select2', 'data-placeholder': _('Select assets')})
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if kwargs.get('instance'):
|
||||
initial = kwargs.get('initial', {})
|
||||
initial['assets'] = kwargs['instance'].assets.all()
|
||||
super(IDCForm, self).__init__(*args, **kwargs)
|
||||
|
||||
def _save_m2m(self):
|
||||
super(IDCForm, self)._save_m2m()
|
||||
assets = self.cleaned_data['assets']
|
||||
self.instance.assets.clear()
|
||||
self.instance.assets.add(*tuple(assets))
|
||||
|
||||
class Meta:
|
||||
model = IDC
|
||||
fields = ['name', "bandwidth", "operator", 'contact',
|
||||
'phone', 'address', 'intranet', 'extranet', 'comment']
|
||||
widgets = {
|
||||
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
|
||||
'intranet': forms.Textarea(
|
||||
attrs={'placeholder': 'IP段之间用逗号隔开,如:192.168.1.0/24,192.168.1.0/24'}),
|
||||
'extranet': forms.Textarea(
|
||||
attrs={'placeholder': 'IP段之间用逗号隔开,如:201.1.32.1/24,202.2.32.1/24'})
|
||||
}
|
||||
help_texts = {
|
||||
'name': '* required'
|
||||
}
|
||||
|
||||
|
||||
class AdminUserForm(forms.ModelForm):
|
||||
# Form field name can not start with `_`, so redefine it,
|
||||
password = forms.CharField(
|
||||
widget=forms.PasswordInput, max_length=100,
|
||||
strip=True, required=False,
|
||||
help_text=_('If also set private key, use that first'),
|
||||
)
|
||||
# Need use upload private key file except paste private key content
|
||||
private_key_file = forms.FileField(required=False)
|
||||
|
||||
def save(self, commit=True):
|
||||
# Because we define custom field, so we need rewrite :method: `save`
|
||||
admin_user = super(AdminUserForm, self).save(commit=commit)
|
||||
password = self.cleaned_data['password']
|
||||
private_key = self.cleaned_data['private_key_file']
|
||||
|
||||
if password:
|
||||
admin_user.password = password
|
||||
if private_key:
|
||||
public_key = ssh_pubkey_gen(private_key)
|
||||
admin_user.private_key = private_key
|
||||
admin_user.public_key = public_key
|
||||
admin_user.save()
|
||||
return admin_user
|
||||
|
||||
def clean_private_key_file(self):
|
||||
private_key_file = self.cleaned_data['private_key_file']
|
||||
if private_key_file:
|
||||
private_key = private_key_file.read()
|
||||
if not validate_ssh_private_key(private_key):
|
||||
raise forms.ValidationError(_('Invalid private key'))
|
||||
return private_key
|
||||
return private_key_file
|
||||
|
||||
def clean(self):
|
||||
password = self.cleaned_data['password']
|
||||
private_key_file = self.cleaned_data.get('private_key_file', '')
|
||||
|
||||
if not self.instance and not (password or private_key_file):
|
||||
raise forms.ValidationError(
|
||||
_('Password and private key file must be input one'))
|
||||
|
||||
class Meta:
|
||||
model = AdminUser
|
||||
fields = ['name', 'username', 'password',
|
||||
'private_key_file', 'comment']
|
||||
widgets = {
|
||||
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
|
||||
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
|
||||
}
|
||||
help_texts = {
|
||||
'name': '* required',
|
||||
'username': '* required',
|
||||
}
|
||||
|
||||
|
||||
class SystemUserForm(forms.ModelForm):
|
||||
# Admin user assets define, let user select, save it in form not in view
|
||||
auto_generate_key = forms.BooleanField(initial=True, required=False)
|
||||
# Form field name can not start with `_`, so redefine it,
|
||||
password = forms.CharField(widget=forms.PasswordInput, required=False,
|
||||
max_length=100, strip=True)
|
||||
# Need use upload private key file except paste private key content
|
||||
private_key_file = forms.FileField(required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SystemUserForm, self).__init__(*args, **kwargs)
|
||||
|
||||
def save(self, commit=True):
|
||||
# Because we define custom field, so we need rewrite :method: `save`
|
||||
system_user = super(SystemUserForm, self).save(commit=commit)
|
||||
password = self.cleaned_data['password']
|
||||
private_key_file = self.cleaned_data.get('private_key_file')
|
||||
|
||||
if system_user.auth_method == 'P':
|
||||
if password:
|
||||
system_user.password = password
|
||||
elif system_user.auth_method == 'K':
|
||||
if self.cleaned_data['auto_generate_key']:
|
||||
private_key, public_key = ssh_key_gen(username=system_user.name)
|
||||
logger.info('Generate private key and public key')
|
||||
else:
|
||||
private_key = private_key_file.read().strip()
|
||||
public_key = ssh_pubkey_gen(private_key=private_key)
|
||||
system_user.private_key = private_key
|
||||
system_user.public_key = public_key
|
||||
system_user.save()
|
||||
return self.instance
|
||||
|
||||
def clean_private_key_file(self):
|
||||
if self.data['auth_method'] == 'K' and \
|
||||
not self.cleaned_data['auto_generate_key']:
|
||||
if not self.cleaned_data['private_key_file']:
|
||||
raise forms.ValidationError(_('Private key required'))
|
||||
else:
|
||||
key_string = self.cleaned_data['private_key_file'].read()
|
||||
self.cleaned_data['private_key_file'].seek(0)
|
||||
if not validate_ssh_private_key(key_string):
|
||||
raise forms.ValidationError(_('Invalid private key'))
|
||||
return self.cleaned_data['private_key_file']
|
||||
|
||||
def clean_password(self):
|
||||
if self.data['auth_method'] == 'P':
|
||||
if not self.cleaned_data.get('password'):
|
||||
raise forms.ValidationError(_('Password required'))
|
||||
return self.cleaned_data['password']
|
||||
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
fields = [
|
||||
'name', 'username', 'protocol', 'auto_generate_key', 'password',
|
||||
'private_key_file', 'auth_method', 'auto_push', 'sudo',
|
||||
'comment', 'shell'
|
||||
]
|
||||
widgets = {
|
||||
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
|
||||
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
|
||||
}
|
||||
help_texts = {
|
||||
'name': '* required',
|
||||
'username': '* required',
|
||||
'auto_push': 'Auto push system user to asset',
|
||||
}
|
||||
|
||||
|
||||
class SystemUserUpdateForm(forms.ModelForm):
|
||||
# Admin user assets define, let user select, save it in form not in view
|
||||
auto_generate_key = forms.BooleanField(initial=False, required=False)
|
||||
# Form field name can not start with `_`, so redefine it,
|
||||
password = forms.CharField(widget=forms.PasswordInput, required=False,
|
||||
max_length=100, strip=True)
|
||||
# Need use upload private key file except paste private key content
|
||||
private_key_file = forms.FileField(required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SystemUserUpdateForm, self).__init__(*args, **kwargs)
|
||||
|
||||
def save(self, commit=True):
|
||||
# Because we define custom field, so we need rewrite :method: `save`
|
||||
system_user = super(SystemUserUpdateForm, self).save(commit=commit)
|
||||
password = self.cleaned_data['password']
|
||||
private_key_file = self.cleaned_data.get('private_key_file')
|
||||
|
||||
if system_user.auth_method == 'P' and password:
|
||||
system_user.password = password
|
||||
elif system_user.auth_method == 'K' and private_key_file:
|
||||
private_key = private_key_file.read().strip()
|
||||
public_key = ssh_pubkey_gen(private_key=private_key)
|
||||
system_user.private_key = private_key
|
||||
system_user.public_key = public_key
|
||||
system_user.save()
|
||||
return self.instance
|
||||
|
||||
def clean_private_key_file(self):
|
||||
if self.data['auth_method'] == 'K' and self.cleaned_data['private_key_file']:
|
||||
key_string = self.cleaned_data['private_key_file'].read()
|
||||
self.cleaned_data['private_key_file'].seek(0)
|
||||
if not validate_ssh_private_key(key_string):
|
||||
raise forms.ValidationError(_('Invalid private key'))
|
||||
return self.cleaned_data['private_key_file']
|
||||
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
fields = [
|
||||
'name', 'username', 'protocol', 'auto_generate_key', 'password',
|
||||
'private_key_file', 'auth_method', 'auto_push', 'sudo',
|
||||
'comment', 'shell'
|
||||
]
|
||||
widgets = {
|
||||
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
|
||||
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
|
||||
}
|
||||
help_texts = {
|
||||
'name': '* required',
|
||||
'username': '* required',
|
||||
'auto_push': 'Auto push system user to asset',
|
||||
}
|
||||
|
||||
|
||||
|
||||
class FileForm(forms.Form):
|
||||
file = forms.FileField()
|
||||
18
apps/assets/hands.py
Normal file
18
apps/assets/hands.py
Normal file
@@ -0,0 +1,18 @@
|
||||
"""
|
||||
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-2017 by Jumpserver Team.
|
||||
:license: GPL v2, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
|
||||
from users.utils import AdminUserRequiredMixin
|
||||
from users.permissions import IsAppUser, IsSuperUser, IsValidUser
|
||||
from users.models import User, UserGroup
|
||||
from perms.utils import get_user_granted_assets
|
||||
from perms.tasks import push_users
|
||||
9
apps/assets/models/__init__.py
Normal file
9
apps/assets/models/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from .idc import *
|
||||
from .user import *
|
||||
from .group import *
|
||||
from .asset import *
|
||||
from .utils import *
|
||||
156
apps/assets/models/asset.py
Normal file
156
apps/assets/models/asset.py
Normal file
@@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models
|
||||
import logging
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from . import IDC, AssetGroup, AdminUser, SystemUser
|
||||
|
||||
__all__ = ['Asset']
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_default_idc():
|
||||
return IDC.initial()
|
||||
|
||||
|
||||
class Asset(models.Model):
|
||||
STATUS_CHOICES = (
|
||||
('In use', _('In use')),
|
||||
('Out of use', _('Out of use')),
|
||||
)
|
||||
TYPE_CHOICES = (
|
||||
('Server', _('Server')),
|
||||
('VM', _('VM')),
|
||||
('Switch', _('Switch')),
|
||||
('Router', _('Router')),
|
||||
('Firewall', _('Firewall')),
|
||||
('Storage', _("Storage")),
|
||||
)
|
||||
ENV_CHOICES = (
|
||||
('Prod', 'Production'),
|
||||
('Dev', 'Development'),
|
||||
('Test', 'Testing'),
|
||||
)
|
||||
|
||||
# Important
|
||||
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
|
||||
hostname = models.CharField(max_length=128, unique=True, verbose_name=_('Hostname'))
|
||||
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
||||
groups = models.ManyToManyField(AssetGroup, blank=True, related_name='assets',
|
||||
verbose_name=_('Asset groups'))
|
||||
admin_user = models.ForeignKey(AdminUser, null=True, blank=True, related_name='assets',
|
||||
on_delete=models.SET_NULL, verbose_name=_("Admin user"))
|
||||
system_users = models.ManyToManyField(SystemUser, blank=True,
|
||||
related_name='assets',
|
||||
verbose_name=_("System User"))
|
||||
idc = models.ForeignKey(IDC, blank=True, null=True, related_name='assets',
|
||||
on_delete=models.SET_NULL, verbose_name=_('IDC'),)
|
||||
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
|
||||
type = models.CharField(choices=TYPE_CHOICES, max_length=16, blank=True, null=True,
|
||||
default='Server', verbose_name=_('Asset type'),)
|
||||
env = models.CharField(choices=ENV_CHOICES, max_length=8, blank=True, null=True,
|
||||
default='Prod', verbose_name=_('Asset environment'),)
|
||||
status = models.CharField(choices=STATUS_CHOICES, max_length=12, null=True, blank=True,
|
||||
default='In use', verbose_name=_('Asset status'))
|
||||
|
||||
# Some information
|
||||
public_ip = models.GenericIPAddressField(max_length=32, blank=True,
|
||||
null=True, verbose_name=_('Public IP'))
|
||||
remote_card_ip = models.CharField(max_length=16, null=True, blank=True,
|
||||
verbose_name=_('Remote control card IP'))
|
||||
cabinet_no = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Cabinet number'))
|
||||
cabinet_pos = models.IntegerField(null=True, blank=True, verbose_name=_('Cabinet position'))
|
||||
number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number'))
|
||||
|
||||
# Collect
|
||||
vendor = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Vendor'))
|
||||
model = models.CharField(max_length=54, null=True, blank=True, verbose_name=_('Model'))
|
||||
sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number'))
|
||||
|
||||
cpu_model = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('CPU model'))
|
||||
cpu_count = models.IntegerField(null=True, verbose_name=_('CPU count'))
|
||||
cpu_cores = models.IntegerField(null=True, verbose_name=_('CPU cores'))
|
||||
memory = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Memory'))
|
||||
disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total'))
|
||||
disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info'))
|
||||
|
||||
platform = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Platform'))
|
||||
os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS'))
|
||||
os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version'))
|
||||
os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch'))
|
||||
hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw'))
|
||||
|
||||
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'))
|
||||
|
||||
def __unicode__(self):
|
||||
return '%s <%s: %s>' % (self.hostname, self.ip, self.port)
|
||||
__str__ = __unicode__
|
||||
|
||||
@property
|
||||
def is_valid(self):
|
||||
warning = ''
|
||||
if not self.is_active:
|
||||
warning += ' inactive'
|
||||
else:
|
||||
return True, ''
|
||||
return False, warning
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'hostname': self.hostname,
|
||||
'ip': self.ip,
|
||||
'port': self.port,
|
||||
}
|
||||
|
||||
def _to_secret_json(self):
|
||||
"""Ansible use it create inventory"""
|
||||
return {
|
||||
'id': self.id,
|
||||
'hostname': self.hostname,
|
||||
'ip': self.ip,
|
||||
'port': self.port,
|
||||
'groups': [group.name for group in self.groups.all()],
|
||||
'username': self.admin_user.username if self.admin_user else '',
|
||||
'password': self.admin_user.password if self.admin_user else '',
|
||||
'private_key': self.admin_user.private_key_file if self.admin_user else None,
|
||||
'become': {
|
||||
'method': self.admin_user.become_method,
|
||||
'user': self.admin_user.become_user,
|
||||
'pass': self.admin_user.become_pass,
|
||||
} if self.admin_user and self.admin_user.become else {},
|
||||
}
|
||||
|
||||
class Meta:
|
||||
unique_together = ('ip', 'port')
|
||||
|
||||
@classmethod
|
||||
def generate_fake(cls, count=100):
|
||||
from random import seed, choice
|
||||
import forgery_py
|
||||
from django.db import IntegrityError
|
||||
|
||||
seed()
|
||||
for i in range(count):
|
||||
asset = cls(ip='%s.%s.%s.%s' % (i, i, i, i),
|
||||
hostname=forgery_py.internet.user_name(True),
|
||||
admin_user=choice(AdminUser.objects.all()),
|
||||
idc=choice(IDC.objects.all()),
|
||||
port=22,
|
||||
created_by='Fake')
|
||||
try:
|
||||
asset.save()
|
||||
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
|
||||
|
||||
52
apps/assets/models/group.py
Normal file
52
apps/assets/models/group.py
Normal file
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models
|
||||
import logging
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from . import SystemUser
|
||||
|
||||
__all__ = ['AssetGroup']
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AssetGroup(models.Model):
|
||||
name = models.CharField(max_length=64, unique=True, verbose_name=_('Name'))
|
||||
system_users = models.ManyToManyField(SystemUser, related_name='asset_groups', blank=True)
|
||||
created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by'))
|
||||
date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date created'))
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
__str__ = __unicode__
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
|
||||
@classmethod
|
||||
def initial(cls):
|
||||
asset_group = cls(name=_('Default'), comment=_('Default asset group'))
|
||||
asset_group.save()
|
||||
|
||||
@classmethod
|
||||
def generate_fake(cls, count=100):
|
||||
from random import seed
|
||||
import forgery_py
|
||||
from django.db import IntegrityError
|
||||
|
||||
seed()
|
||||
for i in range(count):
|
||||
group = cls(name=forgery_py.name.full_name(),
|
||||
comment=forgery_py.lorem_ipsum.sentence(),
|
||||
created_by='Fake')
|
||||
try:
|
||||
group.save()
|
||||
logger.debug('Generate fake asset group: %s' % group.name)
|
||||
except IntegrityError:
|
||||
print('Error continue')
|
||||
continue
|
||||
69
apps/assets/models/idc.py
Normal file
69
apps/assets/models/idc.py
Normal file
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
__all__ = ['IDC']
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IDC(models.Model):
|
||||
name = models.CharField(max_length=32, verbose_name=_('Name'))
|
||||
bandwidth = models.CharField(
|
||||
max_length=32, blank=True, verbose_name=_('Bandwidth'))
|
||||
contact = models.CharField(
|
||||
max_length=128, blank=True, verbose_name=_('Contact'))
|
||||
phone = models.CharField(max_length=32, blank=True,
|
||||
verbose_name=_('Phone'))
|
||||
address = models.CharField(
|
||||
max_length=128, blank=True, verbose_name=_("Address"))
|
||||
intranet = models.TextField(blank=True, verbose_name=_('Intranet'))
|
||||
extranet = models.TextField(blank=True, verbose_name=_('Extranet'))
|
||||
date_created = models.DateTimeField(
|
||||
auto_now_add=True, null=True, verbose_name=_('Date created'))
|
||||
operator = models.CharField(
|
||||
max_length=32, blank=True, verbose_name=_('Operator'))
|
||||
created_by = models.CharField(
|
||||
max_length=32, blank=True, verbose_name=_('Created by'))
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
__str__ = __unicode__
|
||||
|
||||
@classmethod
|
||||
def initial(cls):
|
||||
return cls.objects.get_or_create(name=_('Default'), created_by=_('System'), comment=_('Default IDC'))[0]
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
|
||||
@classmethod
|
||||
def generate_fake(cls, count=5):
|
||||
from random import seed, choice
|
||||
import forgery_py
|
||||
from django.db import IntegrityError
|
||||
|
||||
seed()
|
||||
for i in range(count):
|
||||
idc = cls(name=forgery_py.name.full_name(),
|
||||
bandwidth='200M',
|
||||
contact=forgery_py.name.full_name(),
|
||||
phone=forgery_py.address.phone(),
|
||||
address=forgery_py.address.city() + forgery_py.address.street_address(),
|
||||
operator=choice(['北京联通', '北京电信', 'BGP全网通']),
|
||||
comment=forgery_py.lorem_ipsum.sentence(),
|
||||
created_by='Fake')
|
||||
try:
|
||||
idc.save()
|
||||
logger.debug('Generate fake asset group: %s' % idc.name)
|
||||
except IntegrityError:
|
||||
print('Error continue')
|
||||
continue
|
||||
259
apps/assets/models/user.py
Normal file
259
apps/assets/models/user.py
Normal file
@@ -0,0 +1,259 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import os
|
||||
import logging
|
||||
from hashlib import md5
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf import settings
|
||||
|
||||
from common.utils import signer, validate_ssh_private_key, ssh_key_string_to_obj
|
||||
|
||||
__all__ = ['AdminUser', 'SystemUser', 'private_key_validator']
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def private_key_validator(value):
|
||||
if not validate_ssh_private_key(value):
|
||||
raise ValidationError(
|
||||
_('%(value)s is not an even number'),
|
||||
params={'value': value},
|
||||
)
|
||||
|
||||
|
||||
class AdminUser(models.Model):
|
||||
BECOME_METHOD_CHOICES = (
|
||||
('sudo', 'sudo'),
|
||||
('su', 'su'),
|
||||
)
|
||||
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
|
||||
username = models.CharField(max_length=16, 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,])
|
||||
become = models.BooleanField(default=True)
|
||||
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)
|
||||
_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, null=True)
|
||||
created_by = models.CharField(
|
||||
max_length=32, null=True, verbose_name=_('Created by'))
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
__str__ = __unicode__
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
if self._password:
|
||||
return signer.unsign(self._password)
|
||||
else:
|
||||
return ''
|
||||
|
||||
@password.setter
|
||||
def password(self, password_raw):
|
||||
self._password = signer.sign(password_raw)
|
||||
|
||||
@property
|
||||
def private_key(self):
|
||||
if self._private_key:
|
||||
key_str = signer.unsign(self._private_key)
|
||||
return ssh_key_string_to_obj(key_str)
|
||||
else:
|
||||
return None
|
||||
|
||||
@private_key.setter
|
||||
def private_key(self, private_key_raw):
|
||||
self._private_key = signer.sign(private_key_raw)
|
||||
|
||||
@property
|
||||
def private_key_file(self):
|
||||
if not self.private_key:
|
||||
return None
|
||||
project_dir = settings.PROJECT_DIR
|
||||
tmp_dir = os.path.join(project_dir, 'tmp')
|
||||
key_name = md5(self._private_key.encode()).hexdigest()
|
||||
key_path = os.path.join(tmp_dir, key_name)
|
||||
if not os.path.exists(key_path):
|
||||
self.private_key.write_private_key_file(key_path)
|
||||
return key_path
|
||||
|
||||
@property
|
||||
def public_key(self):
|
||||
return signer.unsign(self._public_key)
|
||||
|
||||
@public_key.setter
|
||||
def public_key(self, public_key_raw):
|
||||
self._public_key = signer.sign(public_key_raw)
|
||||
|
||||
@property
|
||||
def assets_amount(self):
|
||||
return self.assets.count()
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
|
||||
@classmethod
|
||||
def generate_fake(cls, count=10):
|
||||
from random import seed
|
||||
import forgery_py
|
||||
from django.db import IntegrityError
|
||||
|
||||
seed()
|
||||
for i in range(count):
|
||||
obj = cls(name=forgery_py.name.full_name(),
|
||||
username=forgery_py.internet.user_name(),
|
||||
password=forgery_py.lorem_ipsum.word(),
|
||||
comment=forgery_py.lorem_ipsum.sentence(),
|
||||
created_by='Fake')
|
||||
try:
|
||||
obj.save()
|
||||
logger.debug('Generate fake asset group: %s' % obj.name)
|
||||
except IntegrityError:
|
||||
print('Error continue')
|
||||
continue
|
||||
|
||||
|
||||
class SystemUser(models.Model):
|
||||
PROTOCOL_CHOICES = (
|
||||
('ssh', 'ssh'),
|
||||
)
|
||||
AUTH_METHOD_CHOICES = (
|
||||
('P', 'Password'),
|
||||
('K', 'Public key'),
|
||||
)
|
||||
name = models.CharField(max_length=128, unique=True,
|
||||
verbose_name=_('Name'))
|
||||
username = models.CharField(max_length=16, verbose_name=_('Username'))
|
||||
_password = models.CharField(
|
||||
max_length=256, blank=True, verbose_name=_('Password'))
|
||||
protocol = models.CharField(
|
||||
max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol'))
|
||||
_private_key = models.TextField(
|
||||
max_length=8192, blank=True, verbose_name=_('SSH private key'))
|
||||
_public_key = models.TextField(
|
||||
max_length=8192, blank=True, verbose_name=_('SSH public key'))
|
||||
auth_method = models.CharField(choices=AUTH_METHOD_CHOICES, default='K',
|
||||
max_length=1, verbose_name=_('Auth method'))
|
||||
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
|
||||
sudo = models.TextField(
|
||||
max_length=4096, default='/sbin/ifconfig', verbose_name=_('Sudo'))
|
||||
shell = models.CharField(
|
||||
max_length=64, default='/bin/bash', verbose_name=_('Shell'))
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
created_by = models.CharField(
|
||||
max_length=32, blank=True, verbose_name=_('Created by'))
|
||||
comment = models.TextField(
|
||||
max_length=128, blank=True, verbose_name=_('Comment'))
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
__str__ = __unicode__
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
if self._password:
|
||||
return signer.unsign(self._password)
|
||||
return None
|
||||
|
||||
@password.setter
|
||||
def password(self, password_raw):
|
||||
self._password = signer.sign(password_raw)
|
||||
|
||||
@property
|
||||
def private_key(self):
|
||||
if self._private_key:
|
||||
return signer.unsign(self._private_key)
|
||||
return None
|
||||
|
||||
@private_key.setter
|
||||
def private_key(self, private_key_raw):
|
||||
self._private_key = signer.sign(private_key_raw)
|
||||
|
||||
@property
|
||||
def public_key(self):
|
||||
return signer.unsign(self._public_key)
|
||||
|
||||
@public_key.setter
|
||||
def public_key(self, public_key_raw):
|
||||
self._public_key = signer.sign(public_key_raw)
|
||||
|
||||
def get_assets_inherit_from_asset_groups(self):
|
||||
assets = set()
|
||||
asset_groups = self.asset_groups.all()
|
||||
for asset_group in asset_groups:
|
||||
for asset in asset_group.assets.all():
|
||||
setattr(asset, 'is_inherit_from_asset_groups', True)
|
||||
setattr(asset, 'inherit_from_asset_groups',
|
||||
getattr(asset, 'inherit_from_asset_groups', set()).add(asset_group))
|
||||
assets.add(asset)
|
||||
return assets
|
||||
|
||||
def get_assets(self):
|
||||
assets = set(self.assets.all()
|
||||
) | self.get_assets_inherit_from_asset_groups()
|
||||
return list(assets)
|
||||
|
||||
def _to_secret_json(self):
|
||||
"""Push system user use it"""
|
||||
return {
|
||||
'name': self.name,
|
||||
'username': self.username,
|
||||
'shell': self.shell,
|
||||
'sudo': self.sudo,
|
||||
'password': self.password,
|
||||
'public_key': self.public_key
|
||||
}
|
||||
|
||||
@property
|
||||
def assets_amount(self):
|
||||
return self.assets.count()
|
||||
|
||||
@property
|
||||
def asset_group_amount(self):
|
||||
return self.asset_groups.count()
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'name': self.name,
|
||||
'username': self.username,
|
||||
'protocol': self.protocol,
|
||||
'auth_method': self.auth_method,
|
||||
'auto_push': self.auto_push,
|
||||
}
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
|
||||
@classmethod
|
||||
def generate_fake(cls, count=10):
|
||||
from random import seed
|
||||
import forgery_py
|
||||
from django.db import IntegrityError
|
||||
|
||||
seed()
|
||||
for i in range(count):
|
||||
obj = cls(name=forgery_py.name.full_name(),
|
||||
username=forgery_py.internet.user_name(),
|
||||
password=forgery_py.lorem_ipsum.word(),
|
||||
comment=forgery_py.lorem_ipsum.sentence(),
|
||||
created_by='Fake')
|
||||
try:
|
||||
obj.save()
|
||||
logger.debug('Generate fake asset group: %s' % obj.name)
|
||||
except IntegrityError:
|
||||
print('Error continue')
|
||||
continue
|
||||
|
||||
|
||||
|
||||
23
apps/assets/models/utils.py
Normal file
23
apps/assets/models/utils.py
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from . import IDC, SystemUser, AdminUser, AssetGroup, Asset
|
||||
|
||||
__all__ = ['init_model', 'generate_fake']
|
||||
|
||||
|
||||
def init_model():
|
||||
for cls in [IDC, SystemUser, AdminUser, AssetGroup, Asset]:
|
||||
if hasattr(cls, 'initial'):
|
||||
cls.initial()
|
||||
|
||||
|
||||
def generate_fake():
|
||||
for cls in [IDC, SystemUser, AdminUser, AssetGroup, Asset]:
|
||||
if hasattr(cls, 'generate_fake'):
|
||||
cls.generate_fake()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
189
apps/assets/serializers.py
Normal file
189
apps/assets/serializers.py
Normal file
@@ -0,0 +1,189 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.cache import cache
|
||||
from rest_framework import viewsets, serializers, generics
|
||||
from .models import AssetGroup, Asset, IDC, AdminUser, SystemUser
|
||||
from common.mixins import IDInFilterMixin
|
||||
from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin
|
||||
|
||||
|
||||
class AssetGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
assets_amount = serializers.SerializerMethodField()
|
||||
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
|
||||
|
||||
class Meta:
|
||||
model = AssetGroup
|
||||
list_serializer_class = BulkListSerializer
|
||||
fields = ['id', 'name', 'comment', 'assets_amount', 'assets']
|
||||
|
||||
@staticmethod
|
||||
def get_assets_amount(obj):
|
||||
return obj.assets.count()
|
||||
|
||||
|
||||
class AssetUpdateGroupSerializer(serializers.ModelSerializer):
|
||||
groups = serializers.PrimaryKeyRelatedField(many=True, queryset=AssetGroup.objects.all())
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = ['id', 'groups']
|
||||
|
||||
|
||||
class AssetUpdateSystemUserSerializer(serializers.ModelSerializer):
|
||||
system_users = serializers.PrimaryKeyRelatedField(many=True, queryset=SystemUser.objects.all())
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = ['id', 'system_users']
|
||||
|
||||
|
||||
class AssetGroupUpdateSerializer(serializers.ModelSerializer):
|
||||
"""update the asset group, and add or delete the asset to the group"""
|
||||
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
|
||||
|
||||
class Meta:
|
||||
model = AssetGroup
|
||||
fields = ['id', 'assets']
|
||||
|
||||
|
||||
class AssetGroupUpdateSystemUserSerializer(serializers.ModelSerializer):
|
||||
system_users = serializers.PrimaryKeyRelatedField(many=True, queryset=SystemUser.objects.all())
|
||||
|
||||
class Meta:
|
||||
model = AssetGroup
|
||||
fields = ['id', 'system_users']
|
||||
|
||||
|
||||
class IDCUpdateAssetsSerializer(serializers.ModelSerializer):
|
||||
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
|
||||
|
||||
class Meta:
|
||||
model = IDC
|
||||
fields = ['id', 'assets']
|
||||
|
||||
|
||||
class AdminUserSerializer(serializers.ModelSerializer):
|
||||
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
|
||||
|
||||
class Meta:
|
||||
model = AdminUser
|
||||
fields = '__all__'
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super(AdminUserSerializer, self).get_field_names(declared_fields, info)
|
||||
fields.append('assets_amount')
|
||||
return fields
|
||||
|
||||
|
||||
class SystemUserSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
exclude = ('_password', '_private_key', '_public_key')
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info)
|
||||
fields.extend(['assets_amount'])
|
||||
return fields
|
||||
|
||||
|
||||
class AssetSystemUserSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
fields = ('id', 'name', 'username', 'protocol', 'auth_method', 'comment')
|
||||
|
||||
|
||||
class SystemUserUpdateAssetsSerializer(serializers.ModelSerializer):
|
||||
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
|
||||
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
fields = ['id', 'assets']
|
||||
|
||||
|
||||
class SystemUserUpdateAssetGroupSerializer(serializers.ModelSerializer):
|
||||
asset_groups = serializers.PrimaryKeyRelatedField(many=True, queryset=AssetGroup.objects.all())
|
||||
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
fields = ['id', 'asset_groups']
|
||||
|
||||
|
||||
class SystemUserSimpleSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
fields = ('id', 'name', 'username')
|
||||
|
||||
|
||||
class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
# system_users = SystemUserSerializer(many=True, read_only=True)
|
||||
# admin_user = AdminUserSerializer(many=False, read_only=True)
|
||||
hardware = serializers.SerializerMethodField()
|
||||
is_online = serializers.SerializerMethodField()
|
||||
|
||||
class Meta(object):
|
||||
model = Asset
|
||||
list_serializer_class = BulkListSerializer
|
||||
fields = '__all__'
|
||||
|
||||
@staticmethod
|
||||
def get_hardware(obj):
|
||||
if obj.cpu_count:
|
||||
return '{} Core {} {}'.format(obj.cpu_count*obj.cpu_cores, obj.memory, obj.disk_total)
|
||||
else:
|
||||
return ''
|
||||
|
||||
@staticmethod
|
||||
def get_is_online(obj):
|
||||
hostname = obj.hostname
|
||||
if cache.get(hostname) == '1':
|
||||
return True
|
||||
elif cache.get(hostname) == '0':
|
||||
return False
|
||||
else:
|
||||
return 'Unknown'
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super(AssetSerializer, self).get_field_names(declared_fields, info)
|
||||
fields.extend(['get_type_display', 'get_env_display'])
|
||||
return fields
|
||||
|
||||
|
||||
class AssetGrantedSerializer(serializers.ModelSerializer):
|
||||
system_users_granted = AssetSystemUserSerializer(many=True, read_only=True)
|
||||
is_inherited = serializers.SerializerMethodField()
|
||||
system_users_join = serializers.SerializerMethodField()
|
||||
|
||||
class Meta(object):
|
||||
model = Asset
|
||||
fields = ("id", "hostname", "ip", "port", "system_users_granted", "is_inherited",
|
||||
"is_active", "system_users_join", "comment")
|
||||
|
||||
@staticmethod
|
||||
def get_is_inherited(obj):
|
||||
if getattr(obj, 'inherited', ''):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def get_system_users_join(obj):
|
||||
return ', '.join([system_user.username for system_user in obj.system_users_granted])
|
||||
|
||||
|
||||
class IDCSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
assets_amount = serializers.SerializerMethodField()
|
||||
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
|
||||
|
||||
class Meta:
|
||||
model = IDC
|
||||
fields = '__all__'
|
||||
|
||||
@staticmethod
|
||||
def get_assets_amount(obj):
|
||||
return obj.assets.count()
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super(IDCSerializer, self).get_field_names(declared_fields, info)
|
||||
fields.append('assets_amount')
|
||||
return fields
|
||||
|
||||
80
apps/assets/tasks.py
Normal file
80
apps/assets/tasks.py
Normal file
@@ -0,0 +1,80 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
from celery import shared_task
|
||||
import json
|
||||
|
||||
from django.core.cache import cache
|
||||
|
||||
from ops.tasks import run_AdHoc
|
||||
from common.utils import get_object_or_none, capacity_convert, sum_capacity
|
||||
from .models import Asset
|
||||
|
||||
|
||||
@shared_task
|
||||
def update_assets_hardware_info(assets):
|
||||
task_tuple = (
|
||||
('setup', ''),
|
||||
)
|
||||
summary, result = run_AdHoc(task_tuple, assets, record=False)
|
||||
for hostname, info in result['contacted'].items():
|
||||
if info:
|
||||
info = info[0]['ansible_facts']
|
||||
else:
|
||||
continue
|
||||
asset = get_object_or_none(Asset, hostname=hostname)
|
||||
if not asset:
|
||||
continue
|
||||
|
||||
___vendor = info['ansible_system_vendor']
|
||||
___model = info['ansible_product_version']
|
||||
___sn = info['ansible_product_serial']
|
||||
|
||||
for ___cpu_model in info['ansible_processor']:
|
||||
if ___cpu_model.endswith('GHz'):
|
||||
break
|
||||
else:
|
||||
___cpu_model = 'Unknown'
|
||||
___cpu_count = info['ansible_processor_count']
|
||||
___cpu_cores = info['ansible_processor_cores']
|
||||
___memory = '%s %s' % capacity_convert('{} MB'.format(info['ansible_memtotal_mb']))
|
||||
disk_info = {}
|
||||
for dev, dev_info in info['ansible_devices'].items():
|
||||
if dev_info['removable'] == '0':
|
||||
disk_info[dev] = dev_info['size']
|
||||
___disk_total = '%s %s' % sum_capacity(disk_info.values())
|
||||
___disk_info = json.dumps(disk_info)
|
||||
|
||||
___platform = info['ansible_system']
|
||||
___os = info['ansible_distribution']
|
||||
___os_version = info['ansible_distribution_version']
|
||||
___os_arch = info['ansible_architecture']
|
||||
___hostname_raw = info['ansible_hostname']
|
||||
|
||||
for k, v in locals().items():
|
||||
if k.startswith('___'):
|
||||
setattr(asset, k.strip('_'), v)
|
||||
asset.save()
|
||||
return summary
|
||||
|
||||
|
||||
@shared_task
|
||||
def update_assets_hardware_period():
|
||||
assets = Asset.objects.filter(type__in=['Server', 'VM'])
|
||||
update_assets_hardware_info(assets)
|
||||
|
||||
|
||||
@shared_task
|
||||
def test_admin_user_connective_period():
|
||||
assets = Asset.objects.filter(type__in=['Server', 'VM'])
|
||||
task_tuple = (
|
||||
('ping', ''),
|
||||
)
|
||||
summary, _ = run_AdHoc(task_tuple, assets, record=False)
|
||||
for i in summary['success']:
|
||||
cache.set(i, '1', 2*60*60*60)
|
||||
|
||||
for i, msg in summary['failed']:
|
||||
cache.set(i, '0', 60*60*60)
|
||||
return summary
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
{% extends '_modal.html' %}
|
||||
{% load i18n %}
|
||||
{% block modal_id %}asset_group_bulk_update_modal{% endblock %}
|
||||
{% block modal_class %}modal-lg{% endblock %}
|
||||
{% block modal_title%}{% trans "Update Asset Group" %}{% endblock %}
|
||||
{% block modal_body %}
|
||||
{% load bootstrap3 %}
|
||||
<p class="text-success text-center">{% trans "Hint: only change the field you want to update." %}</p>
|
||||
<form method="post" class="form-horizontal" action="" id="fm_asset_group_bulk_update">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="assets" class="col-sm-2 control-label">{% trans 'Assets' %}</label>
|
||||
<div class="col-sm-9" id="select2-container">
|
||||
<select name="assets" id="select2_groups" data-placeholder="{% trans 'Select Asset' %}" class="select2 form-control m-b" multiple>
|
||||
{% for asset in assets %}
|
||||
<option value="{{ asset.id }}">{{ asset.ip }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="system_users" class="col-sm-2 control-label">{% trans 'System users' %}</label>
|
||||
<div class="col-sm-9" id="select2-container">
|
||||
<select name="system_users" id="select2_groups" data-placeholder="{% trans 'Select System Users' %}" class="select2 form-control m-b" multiple>
|
||||
{% for system_user in system_users %}
|
||||
<option value="{{ system_user.id }}">{{ system_user.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-9 col-lg-9 col-sm-offset-2">
|
||||
<div class="checkbox checkbox-success">
|
||||
<input type="checkbox" name="enable_otp" checked id="id_enable_otp"><label for="id_enable_otp">{% trans 'Enable-OTP' %}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
{% endblock %}
|
||||
{% block modal_confirm_id %}btn_asset_group_bulk_update{% endblock %}
|
||||
29
apps/assets/templates/assets/_asset_import_modal.html
Normal file
29
apps/assets/templates/assets/_asset_import_modal.html
Normal file
@@ -0,0 +1,29 @@
|
||||
{% extends '_modal.html' %}
|
||||
{% load i18n %}
|
||||
{% block modal_id %}asset_import_modal{% endblock %}
|
||||
{% block modal_title%}{% trans "Import asset" %}{% endblock %}
|
||||
{% block modal_body %}
|
||||
<form method="post" action="{% url 'assets:asset-import' %}" id="fm_asset_import" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="id_assets">{% trans "Template" %}</label>
|
||||
<a href="{% url 'assets:asset-export' %}" style="display: block">{% trans 'Download' %}</a>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="id_users">{% trans "Asset csv file" %}</label>
|
||||
<input id="id_assets" type="file" name="file" />
|
||||
<span class="help-block red-fonts">
|
||||
{% trans 'If set id, will use this id update asset existed' %}
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
<p>
|
||||
<p class="text-success" id="id_created"></p>
|
||||
<p id="id_created_detail"></p>
|
||||
<p class="text-warning" id="id_updated"></p>
|
||||
<p id="id_updated_detail"></p>
|
||||
<p class="text-danger" id="id_failed"></p>
|
||||
<p id="id_failed_detail"></p>
|
||||
</p>
|
||||
{% endblock %}
|
||||
{% block modal_confirm_id %}btn_asset_import{% endblock %}
|
||||
121
apps/assets/templates/assets/_system_user.html
Normal file
121
apps/assets/templates/assets/_system_user.html
Normal file
@@ -0,0 +1,121 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load bootstrap3 %}
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet">
|
||||
<script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<h5>{% trans 'Create system user' %}</h5>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<form enctype="multipart/form-data" method="post" class="form-horizontal" action="" >
|
||||
{% csrf_token %}
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger">
|
||||
{{ form.non_field_errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<h3>{% trans 'Basic' %}</h3>
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
{% bootstrap_field form.username layout="horizontal" %}
|
||||
{% bootstrap_field form.protocol layout="horizontal" %}
|
||||
<h3>{% trans 'Auth' %}</h3>
|
||||
{% bootstrap_field form.auth_method layout="horizontal" %}
|
||||
{% block auth %}
|
||||
<div class="password-auth hidden">
|
||||
{% bootstrap_field form.password layout="horizontal" %}
|
||||
</div>
|
||||
<div class="public-key-auth">
|
||||
<div class="form-group">
|
||||
<label for="{{ form.auto_generate_key.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto generate key' %}</label>
|
||||
<div class="col-sm-8">
|
||||
{{ form.auto_generate_key}}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{% bootstrap_field form.private_key_file layout="horizontal" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
<div class="form-group">
|
||||
<label for="{{ form.as_push.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto push' %}</label>
|
||||
<div class="col-sm-8">
|
||||
{{ form.auto_push}}
|
||||
</div>
|
||||
</div>
|
||||
<h3>{% trans 'Other' %}</h3>
|
||||
{% bootstrap_field form.sudo layout="horizontal" %}
|
||||
{% bootstrap_field form.shell layout="horizontal" %}
|
||||
{% bootstrap_field form.comment layout="horizontal" %}
|
||||
<div class="form-group">
|
||||
<div class="col-sm-4 col-sm-offset-2">
|
||||
<button class="btn btn-white" type="reset">{% trans 'Reset' %}</button>
|
||||
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
var auth_method = '#'+'{{ form.auth_method.id_for_label }}';
|
||||
var auto_generate_key = '#'+'{{ form.auto_generate_key.id_for_label }}';
|
||||
function authMethodDisplay() {
|
||||
if ($(auth_method).val() == 'P') {
|
||||
$('.password-auth').removeClass('hidden');
|
||||
$('.public-key-auth').addClass('hidden');
|
||||
$('#'+'{{ form.password.id_for_label }}').attr('required', 'required');
|
||||
$('#'+'{{ form.password.id_for_label }}').removeAttr('disabled');
|
||||
$('#'+'{{ form.private_key_file.id_for_label }}').removeAttr('required');
|
||||
|
||||
} else if ($(auth_method).val() == 'K') {
|
||||
$('.password-auth').addClass('hidden');
|
||||
$('.public-key-auth').removeClass('hidden');
|
||||
$('#'+'{{ form.password.id_for_label }}').removeAttr('required');
|
||||
$('#'+'{{ form.password.id_for_label }}').attr('disabled', 'disabled');
|
||||
|
||||
if ($(auto_generate_key).prop('checked')){
|
||||
$('#'+'{{ form.private_key_file.id_for_label }}').closest('.form-group').addClass('hidden');
|
||||
} else {
|
||||
$('#'+'{{ form.private_key_file.id_for_label }}').closest('.form-group').removeClass('hidden');
|
||||
$('#'+'{{ form.private_key_file.id_for_label }}').closest('.form-group input').attr('required', 'required');
|
||||
}
|
||||
}
|
||||
}
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
authMethodDisplay();
|
||||
$(auth_method).change(function () {
|
||||
authMethodDisplay();
|
||||
});
|
||||
$(auto_generate_key).change(function () {
|
||||
authMethodDisplay();
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
62
apps/assets/templates/assets/admin_user_create_update.html
Normal file
62
apps/assets/templates/assets/admin_user_create_update.html
Normal file
@@ -0,0 +1,62 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load bootstrap3 %}
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet">
|
||||
<script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<h5>{% trans 'Create admin user' %}</h5>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger">
|
||||
{{ form.non_field_errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<form enctype="multipart/form-data" method="post" class="form-horizontal" action="" >
|
||||
{% csrf_token %}
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
{% bootstrap_field form.username layout="horizontal" %}
|
||||
{% bootstrap_field form.password layout="horizontal" %}
|
||||
{% bootstrap_field form.private_key_file layout="horizontal" %}
|
||||
{% bootstrap_field form.comment layout="horizontal" %}
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-4 col-sm-offset-2">
|
||||
<button class="btn btn-white" type="reset">{% trans 'Reset' %}</button>
|
||||
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
441
apps/assets/templates/assets/admin_user_detail.html
Normal file
441
apps/assets/templates/assets/admin_user_detail.html
Normal file
@@ -0,0 +1,441 @@
|
||||
{% 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="" 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 'assets:admin-user-update' pk=admin_user.id %}"><i class="fa fa-edit"></i>Update</a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-danger btn-delete-admin-user">
|
||||
<i class="fa fa-edit"></i>Delete
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-7" style="padding-left: 0;">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span class="label"><b>{{ admin_user.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>{{ admin_user.name }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Username' %}:</td>
|
||||
<td><b>{{ admin_user.username }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Date created' %}:</td>
|
||||
<td><b>{{ admin_user.date_created }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Created by' %}:</td>
|
||||
<td><b>{{ asset_group.created_by }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Comment' %}:</td>
|
||||
<td><b>{{ admin_user.comment }}</b></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span style="float: left">{% trans 'Asset list of ' %} <b>{{ admin_user.name }}</b> <span class="badge"> {{ paginator.count }}</span></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 table-hover" id="system_user_assets_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans 'Hostname' %}</th>
|
||||
<th>{% trans 'IP' %}</th>
|
||||
<th>{% trans 'Port' %}</th>
|
||||
<th>{% trans 'Alive' %}</th>
|
||||
<th>{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{# {% for asset in page_obj %}#}
|
||||
{# <tr>#}
|
||||
{# <td>{{ asset.hostname }}</td>#}
|
||||
{# <td>{{ asset.ip }}</td>#}
|
||||
{# <td>{{ asset.port }}</td>#}
|
||||
{# <td>Alive</td>#}
|
||||
{# </tr>#}
|
||||
{# {% endfor %}#}
|
||||
</tbody>
|
||||
</table>
|
||||
{# <div class="row">#}
|
||||
{# {% include '_pagination.html' %}#}
|
||||
{# </div>#}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Quick update' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr class="no-borders-tr">
|
||||
<td width="50%">{% trans 'Get install script' %}:</td>
|
||||
<td>
|
||||
<span style="float: right">
|
||||
<button type="button" class="btn btn-primary btn-xs" style="width: 54px">{% trans 'Get' %}</button>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td width="50%">{% trans 'Retest asset connectivity' %}:</td>
|
||||
<td>
|
||||
<span style="float: right">
|
||||
<button type="button" class="btn btn-primary btn-xs" style="width: 54px">{% trans 'Start' %}</button>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td width="50%">{% trans 'Reset private key' %}:</td>
|
||||
<td>
|
||||
<span style="float: right">
|
||||
<button type="button" class="btn btn-primary btn-xs" style="width: 54px">{% trans 'Reset' %}</button>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Replace asset admin user with this' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<form>
|
||||
<tr class="no-borders-tr">
|
||||
<td colspan="2">
|
||||
<select data-placeholder="{% trans 'Select asset' %}" class="select2" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for asset in assets_remain %}
|
||||
<option value="{{ asset.id }}">{{ asset.ip }}:{{ asset.port }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="no-borders-tr">
|
||||
<td colspan="2">
|
||||
<button type="button" class="btn btn-info btn-sm btn-replace-asset-admin_user">{% trans 'Replace' %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-warning">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Replace asset admin user with this admin user' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<form>
|
||||
<tr class="no-borders-tr">
|
||||
<td colspan="2">
|
||||
<select data-placeholder="{% trans 'Select asset groups' %}" class="select2" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for asset_group in asset_groups %}
|
||||
<option value="{{ asset_group.id }}">{{ asset_group.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="no-borders-tr">
|
||||
<td colspan="2">
|
||||
<button type="button" class="btn btn-warning btn-sm btn-replace-asset_groups-admin_user">{% trans 'Replace' %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
Array.prototype.remove = function(val) {
|
||||
var index = this.indexOf(val);
|
||||
if (index > -1) {
|
||||
this.splice(index, 1);
|
||||
}
|
||||
};
|
||||
Array.prototype.unique = function(){
|
||||
var res = [];
|
||||
var json = {};
|
||||
for(var i = 0; i < this.length; i++){
|
||||
if(!json[this[i]]){
|
||||
res.push(this[i]);
|
||||
json[this[i]] = 1;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
};
|
||||
function objectRemove(obj, name, url, data) {
|
||||
function doRemove() {
|
||||
var body = data;
|
||||
var success = function() {
|
||||
swal('Remove!', "[ "+name+"]"+" has been deleted ", "success");
|
||||
$(obj).parent().parent().remove();
|
||||
};
|
||||
var fail = function() {
|
||||
swal("Failed", "Remove"+"[ "+name+" ]"+"failed", "error");
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: url,
|
||||
body: JSON.stringify(body),
|
||||
method: 'PATCH',
|
||||
success: success,
|
||||
error: fail
|
||||
});
|
||||
}
|
||||
swal({
|
||||
title: 'Are you sure remove ?',
|
||||
text: " [" + name + "] ",
|
||||
type: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonText: 'Cancel',
|
||||
confirmButtonColor: "#DD6B55",
|
||||
confirmButtonText: 'Confirm',
|
||||
closeOnConfirm: false
|
||||
}, function () {
|
||||
doRemove()
|
||||
});
|
||||
}
|
||||
jumpserver.assets_selected = {};
|
||||
jumpserver.asset_groups_selected = {};
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2()
|
||||
.on("select2:select", function (evt) {
|
||||
var data = evt.params.data;
|
||||
jumpserver.assets_selected[data.id] = data.text;
|
||||
jumpserver.asset_groups_selected[data.id] = data.text;
|
||||
})
|
||||
.on('select2:unselect', function(evt) {
|
||||
var data = evt.params.data;
|
||||
delete jumpserver.assets_selected[data.id];
|
||||
delete jumpserver.asset_groups_selected[data.id]
|
||||
});
|
||||
var options = {
|
||||
ele: $('#system_user_assets_table'),
|
||||
buttons: [],
|
||||
order: [],
|
||||
columnDefs: [
|
||||
{targets: 0, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url "assets:asset-detail" pk=99991937 %}" data-aid="'+rowData.id+'">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('99991937', rowData.id));
|
||||
}},
|
||||
{targets: 3, createdCell: function (td, cellData) {
|
||||
if (!cellData) {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "assets:asset-update" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', rowData.id);
|
||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_remove" data-aid="99991937">{% trans "Remove" %}</a>'.replace('99991937', rowData.id);
|
||||
$(td).html(update_btn + del_btn)
|
||||
}}
|
||||
|
||||
],
|
||||
ajax_url: '{% url "api-assets:asset-list" %}?admin_user_id={{ admin_user.id }}',
|
||||
columns: [{data: "hostname" }, {data: "ip" }, {data: "port" },
|
||||
{data: "is_active" }, {data: "id"}],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
|
||||
function adminUserDelete(name, url) {
|
||||
function doDelete() {
|
||||
var body = {};
|
||||
var success = function() {
|
||||
swal('Deleted!', "[ "+name+"]"+" has been deleted ", "success");
|
||||
window.location.href="{% url 'assets:idc-list' %}";
|
||||
};
|
||||
var fail = function() {
|
||||
swal("Failed", "Delete"+"[ "+name+" ]"+"failed", "error");
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: url,
|
||||
body: JSON.stringify(body),
|
||||
method: 'DELETE',
|
||||
success: success,
|
||||
error: fail
|
||||
});
|
||||
}
|
||||
swal({
|
||||
title: 'Are you sure delete ?',
|
||||
text: " [" + name + "] ",
|
||||
type: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonText: 'Cancel',
|
||||
confirmButtonColor: "#DD6B55",
|
||||
confirmButtonText: 'Confirm',
|
||||
closeOnConfirm: false
|
||||
}, function () {
|
||||
doDelete()
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
.on('click', '.btn-replace-asset-admin_user', function () {
|
||||
if (Object.keys(jumpserver.assets_selected).length === 0) {
|
||||
return false;
|
||||
}
|
||||
jumpserver.asset_groups_selected = {};
|
||||
var $data_table = $("#system_user_assets_table").DataTable();
|
||||
var assets = [];
|
||||
$.map(jumpserver.assets_selected, function(value, index) {
|
||||
assets.push(parseInt(index));
|
||||
});
|
||||
assets.unique();
|
||||
var data = [];
|
||||
var admin_user_id = {{ admin_user.id }};
|
||||
var the_url = '{% url "api-assets:asset-list" %}';
|
||||
for (var i=0; i<assets.length; i++) {
|
||||
data.push({"id": assets[i], "admin_user": admin_user_id});
|
||||
}
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
body: JSON.stringify(data),
|
||||
method: 'PATCH'
|
||||
});
|
||||
$data_table.ajax.reload();
|
||||
})
|
||||
|
||||
.on('click', '.btn-replace-asset_groups-admin_user', function () {
|
||||
if (Object.keys(jumpserver.asset_groups_selected).length === 0) {
|
||||
return false;
|
||||
}
|
||||
jumpserver.assets_selected = {};
|
||||
var $data_table = $("#system_user_assets_table").DataTable();
|
||||
var asset_groups = [];
|
||||
var assets = [];
|
||||
var data = [];
|
||||
var the_url = '{% url "api-assets:asset-list" %}';
|
||||
$.map(jumpserver.asset_groups_selected, function(value, index) {
|
||||
asset_groups.push(parseInt(index));
|
||||
});
|
||||
$.ajax({
|
||||
url: '{% url "api-assets:asset-group-list" %}?id__in=['+asset_groups.join(',')+']',
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
success: function (result) {
|
||||
for (var i=0; i<result.length; i++) {
|
||||
for (var j=0; j<result[i]['assets'].length; j++) {
|
||||
assets.push(result[i]['assets'][j])
|
||||
}
|
||||
}
|
||||
for (var z=0; z<assets.length; z++) {
|
||||
data.push({"id":assets[z], "admin_user":{{admin_user.id}} });
|
||||
}
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
body: JSON.stringify(data),
|
||||
method: 'PATCH'
|
||||
});
|
||||
$data_table.ajax.reload();
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
.on('click', '.btn_asset_remove', function () {
|
||||
var $this = $(this);
|
||||
var the_url = "{% url 'api-assets:admin-user-detail' pk=admin_user.id %}";
|
||||
var name = $(this).closest("tr").find(":nth-child(1) > a").html();
|
||||
var assets = [];
|
||||
var delete_asset_id = $(this).data('aid');
|
||||
$.ajax({
|
||||
url: the_url,
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
success: function (result) {
|
||||
for (var i=0; i<result['assets'].length; i++) {
|
||||
assets.push(result['assets'][i])
|
||||
}
|
||||
assets.remove(delete_asset_id);
|
||||
var data = {"assets": assets};
|
||||
objectRemove($this, name, the_url, data);
|
||||
}
|
||||
})
|
||||
}).on('click', '.btn-delete-admin-user', function () {
|
||||
var $this = $(this);
|
||||
var name = "{{ admin_user.name }}";
|
||||
var uid = "{{ admin_user.id }}";
|
||||
var the_url = '{% url "api-assets:admin-user-detail" pk=99991937 %}'.replace('99991937', uid);
|
||||
var redirect_url = "{% url 'assets:admin-user-list' %}";
|
||||
objectDelete($this, name, the_url, redirect_url);
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
71
apps/assets/templates/assets/admin_user_list.html
Normal file
71
apps/assets/templates/assets/admin_user_list.html
Normal file
@@ -0,0 +1,71 @@
|
||||
{% extends '_base_list.html' %}
|
||||
{% load i18n static %}
|
||||
{% block table_search %}
|
||||
{% endblock %}
|
||||
{% block table_container %}
|
||||
<div class="uc pull-left m-l-5 m-r-5">
|
||||
<a href="{% url "assets:admin-user-create" %}" class="btn btn-sm btn-primary"> {% trans "Create admin user" %} </a>
|
||||
</div>
|
||||
<table class="table table-striped table-bordered table-hover " id="admin_user_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 'Username' %}</th>
|
||||
<th class="text-center">{% trans 'Asset num' %}</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>
|
||||
$(document).ready(function(){
|
||||
var options = {
|
||||
ele: $('#admin_user_list_table'),
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url "assets:admin-user-detail" pk=99991937 %}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('99991937', rowData.id));
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData) {
|
||||
var innerHtml = cellData.length > 8 ? cellData.substring(0, 24) + '...': cellData;
|
||||
$(td).html('<a href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</a>');
|
||||
}},
|
||||
{targets: 5, createdCell: function (td, cellData, rowData) {
|
||||
{# var script_btn = '<a href="{% url "assets:admin-user-update" pk=99991937 %}" class="btn btn-xs btn-primary">{% trans "Script" %}</a>'.replace('99991937', cellData);#}
|
||||
var update_btn = '<a href="{% url "assets:admin-user-update" pk=99991937 %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', cellData);
|
||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="99991937">{% trans "Delete" %}</a>'.replace('99991937', cellData);
|
||||
{# $(td).html(script_btn + update_btn + del_btn)#}
|
||||
$(td).html(update_btn + del_btn)
|
||||
}}],
|
||||
ajax_url: '{% url "api-assets:admin-user-list" %}',
|
||||
columns: [{data: function(){return ""}}, {data: "name" }, {data: "username" }, {data: "assets_amount" },
|
||||
{data: "comment" }, {data: "id" }],
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
})
|
||||
|
||||
.on('click', '.btn_admin_user_delete', function () {
|
||||
var $this = $(this);
|
||||
var $data_table = $("#admin_user_list_table").DataTable();
|
||||
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
|
||||
var uid = $this.data('uid');
|
||||
var the_url = '{% url "api-assets:admin-user-detail" pk=99991937 %}'.replace('99991937', uid);
|
||||
objectDelete($this, name, the_url);
|
||||
setTimeout( function () {
|
||||
$data_table.ajax.reload();
|
||||
}, 3000);
|
||||
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
69
apps/assets/templates/assets/asset_bulk_update.html
Normal file
69
apps/assets/templates/assets/asset_bulk_update.html
Normal file
@@ -0,0 +1,69 @@
|
||||
{% extends '_base_create_update.html' %}
|
||||
{% load static %}
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form %}
|
||||
<div class="ydxbd" id="formlists" style="display: block;">
|
||||
<p id="tags_p" class="mgl-5 c02">选择需要修改属性</p>
|
||||
<div class="tagBtnList">
|
||||
<a class="label label-primary" id="change_all" value="1">全选</a>
|
||||
{% for field in form %}
|
||||
{% if field.name != 'assets' %}
|
||||
<a data-id="{{ field.id_for_label }}" class="label label-default label-primary field-tag" value="1">{{ field.label }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<form method="post" class="form-horizontal" id="add_form">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form layout="horizontal" %}
|
||||
<div class="form-group abc">
|
||||
<div class="col-sm-4 col-sm-offset-2">
|
||||
<button class="btn btn-white" type="reset">{% trans 'Reset' %}</button>
|
||||
<button class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
}).on('click', '.field-tag', function() {
|
||||
changeField(this);
|
||||
}).on('click', '#change_all', function () {
|
||||
var tag_fields = $('.field-tag');
|
||||
var $this = $(this);
|
||||
var active = '1';
|
||||
if ($this.attr('value') == '0'){
|
||||
active = '0';
|
||||
$this.attr('value', '1').addClass('label-primary')
|
||||
} else {
|
||||
active = '1';
|
||||
$this.attr('value', '0').removeClass('label-primary')
|
||||
}
|
||||
$.each(tag_fields, function (k, v) {
|
||||
changeField(v, active)
|
||||
})
|
||||
});
|
||||
|
||||
function changeField(obj, active) {
|
||||
var $this = $(obj);
|
||||
var field_id = $this.data('id');
|
||||
if (!active) {
|
||||
active = $this.attr('value');
|
||||
}
|
||||
if (active == '0') {
|
||||
$this.attr('value', '1').addClass('label-primary');
|
||||
var form_groups = $('#add_form .form-group:not(.abc)');
|
||||
form_groups.filter(':has(#' + field_id + ')').show().find('select,input').prop('disabled', false)
|
||||
} else {
|
||||
$this.attr('value', '0').removeClass('label-primary');
|
||||
var form_groups = $('#add_form .form-group:not(.abc)');
|
||||
form_groups.filter(':has(#' + field_id + ')').hide().find('select,input').prop('disabled', true)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
58
apps/assets/templates/assets/asset_create.html
Normal file
58
apps/assets/templates/assets/asset_create.html
Normal file
@@ -0,0 +1,58 @@
|
||||
{% extends '_base_create_update.html' %}
|
||||
{% load static %}
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form %}
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger">
|
||||
{{ form.non_field_errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% csrf_token %}
|
||||
<h3>{% trans 'Basic' %}</h3>
|
||||
{% bootstrap_field form.hostname layout="horizontal" %}
|
||||
{% bootstrap_field form.ip layout="horizontal" %}
|
||||
{% bootstrap_field form.public_ip layout="horizontal" %}
|
||||
{% bootstrap_field form.port layout="horizontal" %}
|
||||
{% bootstrap_field form.type layout="horizontal" %}
|
||||
{% bootstrap_field form.env layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Group' %}</h3>
|
||||
{% bootstrap_field form.idc layout="horizontal" %}
|
||||
{% bootstrap_field form.groups layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Asset user' %}</h3>
|
||||
{% bootstrap_field form.admin_user layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Other' %}</h3>
|
||||
{% bootstrap_field form.comment layout="horizontal" %}
|
||||
{% bootstrap_field form.is_active 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>
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
$("#id_tags").select2({
|
||||
tags: true,
|
||||
maximumSelectionLength: 8 //最多能够选择的个数
|
||||
//closeOnSelect: false
|
||||
});
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
471
apps/assets/templates/assets/asset_detail.html
Normal file
471
apps/assets/templates/assets/asset_detail.html
Normal file
@@ -0,0 +1,471 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block custom_head_css_js %}
|
||||
<link href='{% static "css/plugins/select2/select2.min.css" %}' rel="stylesheet">
|
||||
<link href='{% static "css/plugins/sweetalert/sweetalert.css" %}' rel="stylesheet">
|
||||
<script src='{% static "js/plugins/select2/select2.full.min.js" %}'></script>
|
||||
<script src='{% static "js/plugins/sweetalert/sweetalert.min.js" %}'></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active">
|
||||
<a href="{% url 'assets:asset-detail' pk=asset.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Asset detail' %} </a>
|
||||
</li>
|
||||
{% if user.is_superuser %}
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-default" href="{% url 'assets:asset-update' pk=asset.id %}"><i class="fa fa-edit"></i>Update</a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-danger btn-delete-asset">
|
||||
<i class="fa fa-edit"></i>Delete
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-7" style="padding-left: 0">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span class="label"><b>{{ asset.hostname }}</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 width="20%">{% trans 'Hostname' %}:</td>
|
||||
<td><b>{{ asset.hostname }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'IP' %}:</td>
|
||||
<td><b>{{ asset.ip }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Public IP' %}:</td>
|
||||
<td><b>{{ asset.public_ip }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Port' %}:</td>
|
||||
<td><b>{{ asset.port }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Admin user' %}:</td>
|
||||
{% if asset.admin_user %}
|
||||
<td><b>{{ asset.admin_user.name }}</b></td>
|
||||
{% else %}
|
||||
<td><b>None</b></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Remote card IP' %}:</td>
|
||||
<td><b>{{ asset.remote_card_ip }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'IDC' %}:</td>
|
||||
<td><b>{{ asset.idc.name }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Cabinet number' %}:</td>
|
||||
<td><b>{{ asset.cabinet_no }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Cabinet position' %}:</td>
|
||||
<td><b>{{ asset.cabinet_pos }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Vendor' %}:</td>
|
||||
<td><b>{{ asset.vendor }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Model' %}:</td>
|
||||
<td><b>{{ asset.model }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'CPU' %}:</td>
|
||||
<td><b>{{ asset.cpu_model }} {{ asset.cpu_count }}*{{ asset.cpu_cores }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Memory' %}:</td>
|
||||
<td><b>{{ asset.memory }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Disk' %}:</td>
|
||||
<td><b>{{ asset.disk_total }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Platform' %}:</td>
|
||||
<td><b>{{ asset.platform }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'OS' %}:</td>
|
||||
<td><b>{{ asset.os }} {{ asset.os_version }} {{ asset.os_arch }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Asset status' %}:</td>
|
||||
<td><b>{{ asset.status }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Is active' %}:</td>
|
||||
<td><b>{{ asset.is_active }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Asset type' %}:</td>
|
||||
<td><b>{{ asset.type }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Asset environment' %}:</td>
|
||||
<td><b>{{ asset.env }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Serial number' %}:</td>
|
||||
<td><b>{{ asset.sn }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Asset number' %}:</td>
|
||||
<td><b>{{ asset.number }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Created by' %}:</td>
|
||||
<td><b>{{ asset.created_by }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Date joined' %}:</td>
|
||||
<td><b>{{ asset.date_joined|date:"Y-m-j H:i:s" }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Comment' %}:</td>
|
||||
<td><b>{{ asset.comment }}</b></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if user.is_superuser %}
|
||||
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Quick modify' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr class="no-borders-tr">
|
||||
<td width="50%">{% trans 'Active' %}:</td>
|
||||
<td><span class="pull-right">
|
||||
<div class="switch">
|
||||
<div class="onoffswitch">
|
||||
<input type="checkbox" {% if asset.is_active %} checked {% endif %} class="onoffswitch-checkbox" id="is_active">
|
||||
<label class="onoffswitch-label" for="is_active">
|
||||
<span class="onoffswitch-inner"></span>
|
||||
<span class="onoffswitch-switch"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Refresh hardware' %}:</td>
|
||||
<td>
|
||||
<span class="pull-right">
|
||||
<button type="button" class="btn btn-primary btn-xs" id="btn_refresh_asset" style="width: 54px">{% trans 'Refresh' %}</button>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Test admin user' %}:</td>
|
||||
<td>
|
||||
<span class="pull-right">
|
||||
<button type="button" class="btn btn-primary btn-xs" id="btn_test_admin_user" style="width: 54px;">{% trans 'Test' %}</button>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Asset groups' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table group_edit" id="add-asset2group">
|
||||
<tbody>
|
||||
<form>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<select data-placeholder="{% trans 'Join asset groups' %}" id="groups_selected" class="select2 groups" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for asset_group in asset_groups_remain %}
|
||||
<option value="{{ asset_group.id }}" id="opt_{{ asset_group.id }}" >{{ asset_group.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<button type="button" class="btn btn-info btn-sm" id="btn_add_user_group">{% trans 'Confirm' %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
|
||||
{% for asset_group in asset_groups %}
|
||||
<tr>
|
||||
<td ><b class="bdg_group" data-gid={{ asset_group.id }}>{{ asset_group.name }}</b></td>
|
||||
<td>
|
||||
<button class="btn btn-danger pull-right btn-xs btn_leave_group" type="button"><i class="fa fa-minus"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-warning">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Push system users' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table group_edit" id="add-asset2systemuser">
|
||||
<tbody>
|
||||
<form>
|
||||
<tr class="no-borders-tr">
|
||||
<td colspan="2">
|
||||
<select data-placeholder="{% trans 'Select system users' %}" class="select2 system-user" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for system_user in system_users_all %}
|
||||
<option value="{{ system_user.id }}" id="opt_{{ system_user.id }}">{{ system_user.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="no-borders-tr">
|
||||
<td colspan="2">
|
||||
<button type="button" class="btn btn-warning btn-sm btn-system-user">{% trans 'Confirm' %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
{% for system_user in system_users %}
|
||||
<tr>
|
||||
<td ><b class="bdg_group" data-sid={{ system_user.id }}>{{ system_user.name }}</b></td>
|
||||
<td>
|
||||
<button class="btn btn-danger btn-xs pull-right btn_leave_system" type="button" style="float: right;"><i class="fa fa-minus"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
jumpserver.groups_selected = {};
|
||||
jumpserver.system_user_selected = {};
|
||||
function updateAssetGroups(groups) {
|
||||
var the_url = "{% url 'api-assets:asset-update-group' pk=asset.id %}";
|
||||
var body = {
|
||||
groups: Object.assign([], groups)
|
||||
};
|
||||
var success = function(data) {
|
||||
// remove all the selected groups from select > option and rendered ul element;
|
||||
$('.select2-selection__rendered').empty();
|
||||
$('#groups_selected').val('');
|
||||
$.map(jumpserver.groups_selected, function(group_name, index) {
|
||||
$('#opt_' + index).remove();
|
||||
// change tr html of user groups.
|
||||
$('#add-asset2group tbody').append(
|
||||
'<tr>' +
|
||||
'<td><b class="bdg_group" data-gid="' + index + '">' + group_name + '</b></td>' +
|
||||
'<td><button class="btn btn-danger btn-xs pull-right btn_leave_group" type="button"><i class="fa fa-minus"></i></button></td>' +
|
||||
'</tr>'
|
||||
)
|
||||
});
|
||||
// clear jumpserver.groups_selected
|
||||
jumpserver.groups_selected = {};
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
body: JSON.stringify(body),
|
||||
success: success
|
||||
});
|
||||
}
|
||||
|
||||
function updateAssetSystemUser(system_users) {
|
||||
var the_url = "{% url 'api-assets:asset-update-system-users' pk=asset.id %}";
|
||||
var body = {
|
||||
system_users: Object.assign([], system_users)
|
||||
};
|
||||
var success = function(data) {
|
||||
$('.select2-selection__rendered').empty();
|
||||
$('#groups_selected').val('');
|
||||
$.map(jumpserver.system_user_selected, function(name, index) {
|
||||
$('#opt_' + index).remove();
|
||||
|
||||
$('#add-asset2systemuser tbody').append(
|
||||
'<tr>' +
|
||||
'<td><b class="bdg_group" data-sid="' + index + '">' + name + '</b></td>' +
|
||||
'<td><button class="btn btn-danger btn-xs pull-right btn_leave_system" type="button"><i class="fa fa-minus"></i></button></td>' +
|
||||
'</tr>'
|
||||
)
|
||||
});
|
||||
// clear jumpserver.groups_selected
|
||||
jumpserver.system_user_selected = {};
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
body: JSON.stringify(body),
|
||||
success: success
|
||||
});
|
||||
}
|
||||
|
||||
function refreshAssetHardware() {
|
||||
var the_url = "{% url 'api-assets:asset-refresh' pk=asset.id %}";
|
||||
var success = function (data) {
|
||||
location.reload();
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
success: success,
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
$(document).ready(function () {
|
||||
$('.select2.groups').select2().on('select2:select', function(evt) {
|
||||
var data = evt.params.data;
|
||||
jumpserver.groups_selected[data.id] = data.text;
|
||||
}).on('select2:unselect', function(evt) {
|
||||
var data = evt.params.data;
|
||||
delete jumpserver.groups_selected[data.id]
|
||||
});
|
||||
$('.select2.system-user').select2().on('select2:select', function(evt) {
|
||||
var data = evt.params.data;
|
||||
jumpserver.system_user_selected[data.id] = data.text;
|
||||
}).on('select2:unselect', function(evt) {
|
||||
var data = evt.params.data;
|
||||
delete jumpserver.system_user_selected[data.id]
|
||||
})
|
||||
}).on('click', '#is_active', function () {
|
||||
var the_url = '{% url "api-assets:asset-detail" pk=asset.id %}';
|
||||
var checked = $(this).prop('checked');
|
||||
var body = {
|
||||
'is_active': checked
|
||||
};
|
||||
var success = '{% trans "Update successfully!" %}';
|
||||
var status = $(".ibox-content > table > tbody > tr:nth-child(13) > td:last >b").text();
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
body: JSON.stringify(body),
|
||||
success_message: success
|
||||
});
|
||||
if (status == "False") {
|
||||
$(".ibox-content > table > tbody > tr:nth-child(13) > td:last >b").html('True');
|
||||
}else{
|
||||
$(".ibox-content > table > tbody > tr:nth-child(13) > td:last >b").html('False');
|
||||
}
|
||||
}).on('click', '#btn_add_user_group', function () {
|
||||
if (Object.keys(jumpserver.groups_selected).length === 0) {
|
||||
return false;
|
||||
}
|
||||
var groups = $('.bdg_group').map(function() {
|
||||
return $(this).data('gid');
|
||||
}).get();
|
||||
$.map(jumpserver.groups_selected, function(value, index) {
|
||||
groups.push(parseInt(index));
|
||||
$('#opt_' + index).remove();
|
||||
});
|
||||
updateAssetGroups(groups)
|
||||
}).on('click', '.btn_leave_group', function() {
|
||||
var $this = $(this);
|
||||
var $tr = $this.closest('tr');
|
||||
var $badge = $tr.find('.bdg_group');
|
||||
var gid = $badge.data('gid');
|
||||
var group_name = $badge.html() || $badge.text();
|
||||
$('#groups_selected').append(
|
||||
'<option value="' + gid + '" id="opt_' + gid + '">' + group_name + '</option>'
|
||||
);
|
||||
$tr.remove();
|
||||
var groups = $('.bdg_group').map(function () {
|
||||
return $(this).data('gid');
|
||||
}).get();
|
||||
updateAssetGroups(groups)
|
||||
}).on('click', '.btn-system-user', function () {
|
||||
if (Object.keys(jumpserver.system_user_selected).length === 0) {
|
||||
return false;
|
||||
}
|
||||
var system_users = $('.bdg_group').map(function() {
|
||||
return $(this).data('sid');
|
||||
}).get();
|
||||
$.map(jumpserver.system_user_selected, function(value, index) {
|
||||
system_users.push(parseInt(index));
|
||||
$('#opt_' + index).remove();
|
||||
});
|
||||
updateAssetSystemUser(system_users)
|
||||
|
||||
}).on('click', '.btn_leave_system', function () {
|
||||
var $this = $(this);
|
||||
var $tr = $this.closest('tr');
|
||||
var $badge = $tr.find('.bdg_group');
|
||||
var sid = $badge.data('sid');
|
||||
var name = $badge.html() || $badge.text();
|
||||
$('#groups_selected').append(
|
||||
'<option value="' + sid + '" id="opt_' + sid + '">' + name + '</option>'
|
||||
);
|
||||
$tr.remove();
|
||||
var system_users = $('.bdg_group').map(function () {
|
||||
return $(this).data('sid');
|
||||
}).get();
|
||||
updateAssetSystemUser(system_users)
|
||||
|
||||
}).on('click', '.btn-delete-asset', function () {
|
||||
var $this = $(this);
|
||||
var name = "{{ asset.hostname }}";
|
||||
var uid = "{{ asset.id }}";
|
||||
var the_url = '{% url "api-assets:asset-detail" pk=99991937 %}'.replace('99991937', uid);
|
||||
var redirect_url = "{% url 'assets:asset-list' %}";
|
||||
objectDelete($this, name, the_url, redirect_url);
|
||||
}).on('click', '#btn_refresh_asset', function () {
|
||||
alert('请等待几秒, 等待完成');
|
||||
refreshAssetHardware()
|
||||
}).on('click', '#btn_test_admin_user', function () {
|
||||
$.ajax({
|
||||
url: '{% url "api-assets:asset-admin-user-test" pk=asset.id %}'
|
||||
}).done(function (data, textStatue, jqXHR) {
|
||||
toastr.success('Success')
|
||||
}).fail(function (data, textStaue, errorThrown) {
|
||||
toastr.error('Error')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
121
apps/assets/templates/assets/asset_group_create.html
Normal file
121
apps/assets/templates/assets/asset_group_create.html
Normal file
@@ -0,0 +1,121 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load bootstrap3 %}
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
|
||||
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-10">
|
||||
<div class="ibox float-e-margins">
|
||||
<div id="ibox-content" class="ibox-title">
|
||||
<h5> {{ action }}</h5>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ibox-content">
|
||||
<div class="panel blank-panel">
|
||||
<div class="panel-body">
|
||||
<div class="tab-content">
|
||||
<div id="tab-1" class="ibox float-e-margins tab-pane active"></div>
|
||||
<form id="groupForm" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
<h3 class="widget-head-color-box">资产组信息</h3>
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
{% bootstrap_field form.comment layout="horizontal" %}
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3 class="widget-head-color-box">用户选择的资产</h3>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" id="asset_on_count">已选({{ assets_count }})</label>
|
||||
<div class="col-sm-9" id="asset_sed">
|
||||
<div class="form-asset-on" id="add_asset">
|
||||
<p id="asset_on_p">
|
||||
{% for asset in assets_on_list %}
|
||||
<button name='asset_hostname' title='{{ asset.ip }}' type='button' class='btn btn-default btn-xs'>{{ asset.hostname }}</button>
|
||||
{% endfor %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hr-line-dashed"></div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-4 col-sm-offset-5">
|
||||
<button class="btn btn-white" type="reset"> 重置 </button>
|
||||
<button class="btn btn-primary" type="submit"> 提交 </button>
|
||||
<div id='box2'> </div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 模态框(Modal) -->
|
||||
<div class="modal fade" id="modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content" id="box">
|
||||
<!--此部分为主体内容,将远程加载进来-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
$('.select2-system-user').select2();
|
||||
});
|
||||
|
||||
$('#add_asset').on('click',function(){
|
||||
$('#modal').modal('show');
|
||||
});
|
||||
|
||||
$('#modal').modal({
|
||||
show: false,
|
||||
backdrop: 'static',
|
||||
keyboard: 'false',
|
||||
remote:"{% url 'assets:asset-modal-list' %}?group_id={{ group_id }}"
|
||||
});
|
||||
$('#modal').on('show.bs.modal',function(){
|
||||
//alert('当调用show方法时,立即触发;')
|
||||
});
|
||||
|
||||
$('#modal').on('shown.bs.modal',function(){
|
||||
//alert('当弹窗完全加载完后,再触发;')
|
||||
});
|
||||
|
||||
$('#modal').on('hide.bs.modal',function(){
|
||||
//alert('当关闭时,立即触发;')
|
||||
});
|
||||
|
||||
$('#modal').on('hidden.bs.modal',function(){
|
||||
//alert('当关完全关闭后,再触发;')
|
||||
});
|
||||
|
||||
$('#modal').on('loaded.bs.modal',function(){
|
||||
//alert('当远程数据加载完毕后,再触发;')
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
271
apps/assets/templates/assets/asset_group_detail.html
Normal file
271
apps/assets/templates/assets/asset_group_detail.html
Normal file
@@ -0,0 +1,271 @@
|
||||
{% 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="" 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 'assets:asset-group-update' pk=asset_group.id %}"><i class="fa fa-edit"></i>Update</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-7" style="padding-left: 0">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span style="float: left"></span>{% trans 'Asset list of ' %} <b>{{ asset_group.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 table-hover " id="asset_list_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans 'Hostname' %}</th>
|
||||
<th>{% trans 'IP' %}</th>
|
||||
<th>{% trans 'Port' %}</th>
|
||||
<th>{% trans 'Type' %}</th>
|
||||
<th>{% trans 'Alive' %}</th>
|
||||
<th>{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Push system users' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<form>
|
||||
<tr class="no-borders-tr">
|
||||
<td colspan="2">
|
||||
<select data-placeholder="{% trans 'Select system users' %}" class="select2 system-user-select" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for system_user in system_users %}
|
||||
<option value="{{ system_user.id }}"> {{ system_user.name }} </option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="no-borders-tr">
|
||||
<td colspan="2">
|
||||
<button type="button" class="btn btn-primary btn-sm btn-push-system-user">{% trans 'Push' %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{# </div>#}
|
||||
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
jumpserver.assets_selected = {};
|
||||
function updateGroupAssets(assets) {
|
||||
var the_url = "{}";
|
||||
var body = {
|
||||
assets: Object.assign([], assets)
|
||||
};
|
||||
var $data_table = $("#asset_list_table").DataTable();
|
||||
var success = function(data) {
|
||||
$('.select2-selection__rendered').empty();
|
||||
$.map(jumpserver.assets_selected, function(asset_ip, index) {
|
||||
$('#opt_' + index).remove();
|
||||
$data_table.ajax.reload();
|
||||
});
|
||||
jumpserver.groups_selected = {};
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
body: JSON.stringify(body),
|
||||
method: 'PUT',
|
||||
success: success
|
||||
});
|
||||
}
|
||||
|
||||
function leaveGroup(obj, name, url, data) {
|
||||
function doDelete() {
|
||||
var body = data;
|
||||
var success = function() {
|
||||
swal('Deleted!', "[ "+name+"]"+" has been deleted ", "success");
|
||||
$(obj).parent().parent().remove();
|
||||
};
|
||||
var fail = function() {
|
||||
swal("Failed", "Delete"+"[ "+name+" ]"+"failed", "error");
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: url,
|
||||
body: JSON.stringify(body),
|
||||
method: 'PATCH',
|
||||
success: success,
|
||||
error: fail
|
||||
});
|
||||
}
|
||||
swal({
|
||||
title: 'Are you sure delete ?',
|
||||
text: " [" + name + "] ",
|
||||
type: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonText: 'Cancel',
|
||||
confirmButtonColor: "#DD6B55",
|
||||
confirmButtonText: 'Confirm',
|
||||
closeOnConfirm: false
|
||||
}, function () {
|
||||
doDelete()
|
||||
});
|
||||
}
|
||||
|
||||
function pushSystemUser(sysUserID) {
|
||||
var the_url = "{% url 'api-assets:asset-group-push-system-user' pk=asset_group.id %}";
|
||||
var body = {
|
||||
system_user: sysUserID
|
||||
};
|
||||
var success = function(data) {
|
||||
var url = "{% url 'ops:task-detail' pk=234234234 %}".replace("234234234", data);
|
||||
setTimeout(function () {
|
||||
location.href = url
|
||||
}, 1000);
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(body),
|
||||
success: success
|
||||
});
|
||||
}
|
||||
|
||||
Array.prototype.remove = function(val) {
|
||||
var index = this.indexOf(val);
|
||||
if (index > -1) {
|
||||
this.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
Array.prototype.unique = function(){
|
||||
var res = [];
|
||||
var json = {};
|
||||
for(var i = 0; i < this.length; i++){
|
||||
if(!json[this[i]]){
|
||||
res.push(this[i]);
|
||||
json[this[i]] = 1;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
|
||||
$('.select2.asset-select').select2()
|
||||
.on('select2:select', function(evt) {
|
||||
var data = evt.params.data;
|
||||
jumpserver.assets_selected[data.id] = data.text;
|
||||
console.log(jumpserver.assets_selected)
|
||||
})
|
||||
.on('select2:unselect', function(evt) {
|
||||
var data = evt.params.data;
|
||||
delete jumpserver.assets_selected[data.id]
|
||||
});
|
||||
|
||||
var options = {
|
||||
ele: $('#asset_list_table'),
|
||||
buttons: [],
|
||||
order: [],
|
||||
columnDefs: [
|
||||
{targets: 0, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url "assets:asset-detail" pk=99991937 %}" data-aid="'+rowData.id+'">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('99991937', rowData.id));
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData) {
|
||||
if (!cellData) {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 5, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "assets:asset-update" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', rowData.id);
|
||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_delete" data-aid="99991937">{% trans "Remove" %}</a>'.replace('99991937', rowData.id);
|
||||
$(td).html(update_btn + del_btn)
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-assets:asset-list" %}?asset_group_id={{ asset_group.id }}',
|
||||
columns: [{data: "hostname" }, {data: "ip" }, {data: "port" },
|
||||
{data: "type" }, {data: "is_active" }, {data: "id"}],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
})
|
||||
|
||||
.on('click', ".btn-asset-group-add-asset", function () {
|
||||
if (Object.keys(jumpserver.assets_selected).length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
updateGroupAssets(jumpserver.assets_selected);
|
||||
|
||||
})
|
||||
|
||||
.on('click', '.btn-push-system-user', function () {
|
||||
var data = $('.system-user-select').select2();
|
||||
var system_id = data.val()[0];
|
||||
if (!system_id) {
|
||||
return false
|
||||
}
|
||||
pushSystemUser(system_id)
|
||||
})
|
||||
|
||||
.on('click', '.btn_asset_delete', function () {
|
||||
var $this = $(this);
|
||||
var the_url = "{% url 'api-assets:asset-groups-update' pk=asset_group.id %}";
|
||||
var name = $(this).closest("tr").find(":nth-child(1) > a").html();
|
||||
var assets = [];
|
||||
$('#asset_list_table > tbody > tr').map(function () {
|
||||
assets.push(parseInt($(this).closest("tr").find(":nth-child(1) > a").attr("data-aid")))
|
||||
});
|
||||
var delete_asset_id = $(this).data('aid');
|
||||
assets.remove(delete_asset_id);
|
||||
var data = {"assets": assets};
|
||||
leaveGroup($this, name, the_url, data);
|
||||
})
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
183
apps/assets/templates/assets/asset_group_list.html
Normal file
183
apps/assets/templates/assets/asset_group_list.html
Normal file
@@ -0,0 +1,183 @@
|
||||
{% extends '_base_list.html' %}
|
||||
{% load i18n static %}
|
||||
{% block table_search %}
|
||||
{% endblock %}
|
||||
{% block table_container %}
|
||||
<div class="uc pull-left m-l-5 m-r-5">
|
||||
<a href="{% url "assets:asset-group-create" %}" class="btn btn-sm btn-primary"> {% trans "Create asset group" %} </a>
|
||||
</div>
|
||||
<table class="table table-striped table-bordered table-hover " id="asset_groups_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 'Asset' %}</th>
|
||||
<th class="text-center">{% trans 'Comment' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="actions" class="hide">
|
||||
<div class="input-group">
|
||||
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
|
||||
<option value="delete">{% trans 'Delete selected' %}</option>
|
||||
<option value="update">{% trans 'Update selected' %}</option>
|
||||
</select>
|
||||
<div class="input-group-btn pull-left" style="padding-left: 5px;">
|
||||
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
|
||||
{% trans 'Submit' %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'assets/_asset_group_bulk_update_modal.html' %}
|
||||
{% endblock %}
|
||||
{% block content_bottom_left %}{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
var options = {
|
||||
ele: $('#asset_groups_list_table'),
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url "assets:asset-group-detail" pk=99991937 %}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('99991937', rowData.id));
|
||||
}},
|
||||
{targets: 3, createdCell: function (td, cellData) {
|
||||
var innerHtml = cellData.length > 30 ? cellData.substring(0, 30) + '...': cellData;
|
||||
$(td).html('<a href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</a>');
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "assets:asset-group-update" pk=99991937 %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', cellData);
|
||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_group_delete" data-uid="99991937">{% trans "Delete" %}</a>'.replace('99991937', cellData);
|
||||
$(td).html(update_btn + del_btn)
|
||||
}}],
|
||||
ajax_url: '{% url "api-assets:asset-group-list" %}',
|
||||
columns: [{data: "id"}, {data: "name" }, {data: "assets_amount" }, {data: "comment" }, {data: "id"}],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
})
|
||||
|
||||
.on('click', '.btn_asset_group_delete', function () {
|
||||
var $this = $(this);
|
||||
var $data_table = $('#asset_groups_list_table').DataTable();
|
||||
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
|
||||
var uid = $this.data('uid');
|
||||
var the_url = '{% url "api-assets:asset-group-detail" pk=99991937 %}'.replace('99991937', uid);
|
||||
objectDelete($this, name, the_url);
|
||||
setTimeout( function () {
|
||||
$data_table.ajax.reload();
|
||||
}, 3000);
|
||||
})
|
||||
|
||||
.on('click', '#btn_bulk_update', function () {
|
||||
var action = $('#slct_bulk_update').val();
|
||||
var $data_table = $('#asset_groups_list_table').DataTable();
|
||||
var id_list = [];
|
||||
var plain_id_list = [];
|
||||
$data_table.rows({selected: true}).every(function(){
|
||||
id_list.push({id: this.data().id});
|
||||
plain_id_list.push(this.data().id);
|
||||
});
|
||||
if (id_list === []) {
|
||||
return false;
|
||||
}
|
||||
var the_url = '{% url "api-assets:asset-group-list" %}';
|
||||
console.log(plain_id_list);
|
||||
console.log(the_url);
|
||||
function doDelete() {
|
||||
swal({
|
||||
title: "{% trans 'Are you sure?' %}",
|
||||
text: "{% trans 'This will delete the selected groups !!!' %}",
|
||||
type: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#DD6B55",
|
||||
confirmButtonText: "{% trans 'Confirm' %}",
|
||||
closeOnConfirm: false
|
||||
}, function() {
|
||||
var success = function() {
|
||||
var msg = "{% trans 'Group deleted' %}";
|
||||
swal("{% trans 'Group Delete' %}", msg, "success");
|
||||
$('#asset_groups_list_table').DataTable().ajax.reload();
|
||||
};
|
||||
var fail = function() {
|
||||
var msg = "{% trans 'Group deleting failed.' %}";
|
||||
swal("{% trans 'Group Delete' %}", msg, "error");
|
||||
};
|
||||
var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list);
|
||||
APIUpdateAttr({url: url_delete, method: 'DELETE', success: success, error: fail});
|
||||
$data_table.ajax.reload();
|
||||
jumpserver.checked = false;
|
||||
});
|
||||
}
|
||||
function doUpdate() {
|
||||
$('#asset_group_bulk_update_modal').modal('show');
|
||||
}
|
||||
switch(action) {
|
||||
case 'delete':
|
||||
doDelete();
|
||||
break;
|
||||
case 'update':
|
||||
doUpdate();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
})
|
||||
|
||||
.on('click', '#btn_asset_group_bulk_update', function () {
|
||||
var json_data = $("#fm_asset_group_bulk_update").serializeObject();
|
||||
var body = {};
|
||||
body.enable_otp = (json_data.enable_otp === 'on')? true: false;
|
||||
if (json_data.type != '') {
|
||||
body.type = json_data.type;
|
||||
}
|
||||
|
||||
if (json_data.assets != undefined) {
|
||||
body.assets = json_data.assets;
|
||||
}
|
||||
if (typeof body.assets === 'string') {
|
||||
body.assets = [parseInt(body.assets)]
|
||||
} else if(typeof body.assets === 'array') {
|
||||
var new_assets = body.assets.map(Number);
|
||||
body.assets = new_assets;
|
||||
}
|
||||
|
||||
if (json_data.system_users != undefined) {
|
||||
body.system_users = json_data.system_users;
|
||||
}
|
||||
if (typeof body.system_users === 'string') {
|
||||
body.system_users = [parseInt(body.system_users)];
|
||||
} else if (typeof body.system_users === 'array') {
|
||||
var new_system_users = body.system_users.map(Number);
|
||||
body.system_users = new_system_users;
|
||||
}
|
||||
|
||||
var post_list = [];
|
||||
var $data_table = $('#asset_groups_list_table').DataTable()
|
||||
$data_table.rows({selected: true}).every(function(){
|
||||
var content = Object.assign({id: this.data().id}, body);
|
||||
post_list.push(content);
|
||||
});
|
||||
if (post_list === []) {
|
||||
return false
|
||||
}
|
||||
var the_url = '{% url "api-assets:asset-group-list" %}';
|
||||
var success = function() {
|
||||
var msg = "{% trans 'The selected asset groups has been updated successfully.' %}";
|
||||
swal("{% trans 'AssetGroup Updated' %}", msg, "success");
|
||||
$('#asset_groups_list_table').DataTable().ajax.reload();
|
||||
jumpserver.checked = false;
|
||||
};
|
||||
{# console.log(JSON.stringify(post_list));#}
|
||||
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(post_list), success: success});
|
||||
$('#asset_group_bulk_update_modal').modal('hide');
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
319
apps/assets/templates/assets/asset_list.html
Normal file
319
apps/assets/templates/assets/asset_list.html
Normal file
@@ -0,0 +1,319 @@
|
||||
{% extends '_base_list.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% 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>
|
||||
|
||||
{#<style>#}
|
||||
{# .custom{#}
|
||||
{# margin-right:5px;#}
|
||||
{# }#}
|
||||
{# #modal .modal-body { max-height: 200px; }#}
|
||||
{#</style>#}
|
||||
{% endblock %}
|
||||
{% block content_left_head %}{% endblock %}
|
||||
|
||||
{% block table_search %}
|
||||
<div class="html5buttons">
|
||||
<div class="dt-buttons btn-group">
|
||||
<a class="btn btn-default btn_import" data-toggle="modal" data-target="#asset_import_modal" tabindex="0">
|
||||
<span>{% trans "Import" %}</span>
|
||||
</a>
|
||||
<a class="btn btn-default btn_export" tabindex="0">
|
||||
<span>{% trans "Export" %}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block table_container %}
|
||||
<div class="uc pull-left m-l-5 m-r-5"><a href="{% url "assets:asset-create" %}" class="btn btn-sm btn-primary"> {% trans "Create asset" %} </a></div>
|
||||
<table class="table table-striped table-bordered table-hover " id="asset_list_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
|
||||
<th class="text-center">{% trans 'Hostname' %}</th>
|
||||
<th class="text-center">{% trans 'IP' %}</th>
|
||||
<th class="text-center">{% trans 'Port' %}</th>
|
||||
<th class="text-center">{% trans 'Type' %}</th>
|
||||
<th class="text-center">{% trans 'Env' %}</th>
|
||||
<th class="text-center">{% trans 'Hardware' %}</th>
|
||||
<th class="text-center">{% trans 'Valid' %}</th>
|
||||
<th class="text-center">{% trans 'Alive' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="actions" class="hide">
|
||||
<div class="input-group">
|
||||
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
|
||||
<option value="delete">{% trans 'Delete selected' %}</option>
|
||||
<option value="update">{% trans 'Update selected' %}</option>
|
||||
<option value="deactive">{% trans 'Deactive selected' %}</option>
|
||||
<option value="active">{% trans 'Active' %}</option>
|
||||
</select>
|
||||
<div class="input-group-btn pull-left" style="padding-left: 5px;">
|
||||
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
|
||||
{% trans 'Submit' %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'assets/_asset_import_modal.html' %}
|
||||
{#{% include 'assets/_asset_bulk_update_modal.html' %}#}
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
||||
<script type="text/javascript">
|
||||
window.onload = function (){
|
||||
var tag_on = document.getElementsByName("tag_on");
|
||||
var oDiv = document.getElementById("ydxbd");
|
||||
if(tag_on.length > 0){
|
||||
oDiv.style.display = "block";
|
||||
}
|
||||
};
|
||||
|
||||
function tagShow() {
|
||||
var oDiv = document.getElementById("ydxbd");
|
||||
if (oDiv.style.display == 'none'){
|
||||
oDiv.style.display = "block";
|
||||
}else{
|
||||
oDiv.style.display = "none";
|
||||
}
|
||||
} //onload;
|
||||
|
||||
|
||||
|
||||
$(document).ready(function(){
|
||||
var options = {
|
||||
ele: $('#asset_list_table'),
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url "assets:asset-detail" pk=99991937 %}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('99991937', rowData.id));
|
||||
}},
|
||||
{targets: 7, createdCell: function (td, cellData) {
|
||||
if (!cellData) {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 8, createdCell: function (td, cellData) {
|
||||
if (cellData == 'Unknown'){
|
||||
$(td).html('<i class="fa fa-circle text-warning"></i>')
|
||||
} else if (!cellData) {
|
||||
$(td).html('<i class="fa fa-circle text-danger"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-circle text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 9, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "assets:asset-update" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', cellData);
|
||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_delete" data-uid="99991937">{% trans "Delete" %}</a>'.replace('99991937', cellData);
|
||||
$(td).html(update_btn + del_btn)
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-assets:asset-list" %}',
|
||||
columns: [{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" },
|
||||
{data: "get_type_display" }, {data: "get_env_display"}, {data: "hardware"},
|
||||
{data: "is_active" }, {data: "is_online"}, {data: "id" }],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
var table = jumpserver.initDataTable(options);
|
||||
|
||||
$('.btn_export').click(function () {
|
||||
var assets = [];
|
||||
var rows = table.rows('.selected').data();
|
||||
$.each(rows, function (index, obj) {
|
||||
assets.push(obj.id)
|
||||
});
|
||||
console.log(assets);
|
||||
$.ajax({
|
||||
url: "{% url "assets:asset-export" %}",
|
||||
method: 'POST',
|
||||
data: JSON.stringify({assets_id: assets}),
|
||||
dataType: "json",
|
||||
success: function (data, textStatus) {
|
||||
window.open(data.redirect)
|
||||
},
|
||||
error: function () {
|
||||
toastr.error('Export failed');
|
||||
}
|
||||
})
|
||||
});
|
||||
$('#btn_asset_import').click(function() {
|
||||
var $form = $('#fm_asset_import');
|
||||
$form.find('.help-block').remove();
|
||||
function success (data) {
|
||||
if (data.valid === false) {
|
||||
$('<span />', {class: 'help-block text-danger'}).html(data.msg).insertAfter($('#id_assets'));
|
||||
} else {
|
||||
$('#id_created').html(data.created_info);
|
||||
$('#id_created_detail').html(data.created.join(', '));
|
||||
$('#id_updated').html(data.updated_info);
|
||||
$('#id_updated_detail').html(data.updated.join(', '));
|
||||
$('#id_failed').html(data.failed_info);
|
||||
$('#id_failed_detail').html(data.failed.join(', '));
|
||||
var $data_table = $('#asset_list_table').DataTable();
|
||||
$data_table.ajax.reload();
|
||||
}
|
||||
}
|
||||
$form.ajaxSubmit({success: success});
|
||||
})
|
||||
})
|
||||
|
||||
.on('click', '.btn_asset_delete', function () {
|
||||
var $this = $(this);
|
||||
var $data_table = $("#asset_list_table").DataTable();
|
||||
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
|
||||
var uid = $this.data('uid');
|
||||
var the_url = '{% url "api-assets:asset-detail" pk=99991937 %}'.replace('99991937', uid);
|
||||
console.log(the_url);
|
||||
objectDelete($this, name, the_url);
|
||||
setTimeout( function () {
|
||||
$data_table.ajax.reload();
|
||||
}, 3000);
|
||||
})
|
||||
.on('click', '#btn_bulk_update', function () {
|
||||
var action = $('#slct_bulk_update').val();
|
||||
var $data_table = $('#asset_list_table').DataTable();
|
||||
var id_list = [];
|
||||
$data_table.rows({selected: true}).every(function(){
|
||||
id_list.push(this.data().id);
|
||||
});
|
||||
if (id_list.length == 0) {
|
||||
return false;
|
||||
}
|
||||
var the_url = "{% url 'api-assets:asset-list' %}";
|
||||
|
||||
function doDeactive() {
|
||||
var body = $.each(id_list, function(index, asset_object) {
|
||||
asset_object['is_active'] = false;
|
||||
});
|
||||
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(body)});
|
||||
$data_table.ajax.reload();
|
||||
jumpserver.checked = false;
|
||||
}
|
||||
function doActive() {
|
||||
var body = $.each(id_list, function(index, asset_object) {
|
||||
asset_object['is_active'] = true;
|
||||
});
|
||||
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(body)});
|
||||
$data_table.ajax.reload();
|
||||
jumpserver.checked = false;
|
||||
}
|
||||
function doDelete() {
|
||||
swal({
|
||||
title: "{% trans 'Are you sure?' %}",
|
||||
text: "{% trans 'This will delete the selected assets !!!' %}",
|
||||
type: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#DD6B55",
|
||||
confirmButtonText: "{% trans 'Confirm' %}",
|
||||
closeOnConfirm: false
|
||||
}, function() {
|
||||
var success = function() {
|
||||
var msg = "{% trans 'Asset Deleted.' %}";
|
||||
swal("{% trans 'Asset Delete' %}", msg, "success");
|
||||
$('#asset_list_table').DataTable().ajax.reload();
|
||||
};
|
||||
var fail = function() {
|
||||
var msg = "{% trans 'Asset Deleting failed.' %}";
|
||||
swal("{% trans 'Asset Delete' %}", msg, "error");
|
||||
};
|
||||
var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list);
|
||||
APIUpdateAttr({url: url_delete, method: 'DELETE', success: success, error: fail});
|
||||
$data_table.ajax.reload();
|
||||
jumpserver.checked = false;
|
||||
});
|
||||
}
|
||||
function doUpdate() {
|
||||
var id_list_string = id_list.join(',');
|
||||
var url = "{% url 'assets:asset-bulk-update' %}?assets_id=" + id_list_string;
|
||||
location.href = url
|
||||
}
|
||||
switch(action) {
|
||||
case 'deactive':
|
||||
doDeactive();
|
||||
break;
|
||||
case 'delete':
|
||||
doDelete();
|
||||
break;
|
||||
case 'update':
|
||||
doUpdate();
|
||||
break;
|
||||
case 'active':
|
||||
doActive();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
{##}
|
||||
{#.on('click', '#btn_asset_bulk_update', function () {#}
|
||||
{# var json_data = $("#fm_asset_bulk_update").serializeObject();#}
|
||||
{# var body = {};#}
|
||||
{# body.enable_otp = (json_data.enable_otp === 'on')? true: false;#}
|
||||
{# if (json_data.type != '') {#}
|
||||
{# body.type = json_data.type;#}
|
||||
{# }#}
|
||||
{# if (json_data.groups != undefined) {#}
|
||||
{# body.groups = json_data.groups;#}
|
||||
{# }#}
|
||||
{# if (typeof body.groups === 'string') {#}
|
||||
{# body.groups = [parseInt(body.groups)]#}
|
||||
{# } else if(typeof body.groups === 'array') {#}
|
||||
{# var new_groups = body.groups.map(Number);#}
|
||||
{# body.groups = new_groups;#}
|
||||
{# }#}
|
||||
{##}
|
||||
{# if (json_data.system_users != undefined) {#}
|
||||
{# body.system_users = json_data.system_users;#}
|
||||
{# }#}
|
||||
{# if (typeof body.system_users === 'string') {#}
|
||||
{# body.system_users = [parseInt(body.system_users)]#}
|
||||
{# } else if(typeof body.system_users === 'array') {#}
|
||||
{# var new_users = body.system_users.map(Number);#}
|
||||
{# body.system_users = new_users;#}
|
||||
{# }#}
|
||||
{##}
|
||||
{# if (json_data.tags != undefined) {#}
|
||||
{# body.tags = json_data.tags;#}
|
||||
{# }#}
|
||||
{# if (typeof body.tags == 'string') {#}
|
||||
{# body.tags = [parseInt(body.tags)];#}
|
||||
{# } else if (typeof body.tags === 'array') {#}
|
||||
{# var new_tags = body.tags.map(Number);#}
|
||||
{# body.tags = new_tags;#}
|
||||
{# }#}
|
||||
{##}
|
||||
{# var $data_table = $('#asset_list_table').DataTable();#}
|
||||
{# var post_list = [];#}
|
||||
{# $data_table.rows({selected: true}).every(function(){#}
|
||||
{# var content = Object.assign({id: this.data().id}, body);#}
|
||||
{# post_list.push(content);#}
|
||||
{# });#}
|
||||
{# if (post_list === []) {#}
|
||||
{# return false#}
|
||||
{# }#}
|
||||
{# var the_url = "{% url 'api-assets:asset-list' %}";#}
|
||||
{# var success = function() {#}
|
||||
{# var msg = "{% trans 'The selected assets has been updated successfully.' %}";#}
|
||||
{# swal("{% trans 'Asset Updated' %}", msg, "success");#}
|
||||
{# $('#asset_list_table').DataTable().ajax.reload();#}
|
||||
{# jumpserver.checked = false;#}
|
||||
{# };#}
|
||||
{# console.log(JSON.stringify(post_list));#}
|
||||
{# console.log(the_url);#}
|
||||
{# APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(post_list), success: success});#}
|
||||
{# $('#asset_bulk_update_modal').modal('hide');#}
|
||||
{#})#}
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
127
apps/assets/templates/assets/asset_modal_list.html
Normal file
127
apps/assets/templates/assets/asset_modal_list.html
Normal file
@@ -0,0 +1,127 @@
|
||||
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title" id="myModalLabel">分配/回收资产</h4>
|
||||
</div>
|
||||
|
||||
<div class="modal-body" style="padding-bottom: 0px;">
|
||||
<table aria-describedby="editable_info" role="grid" class="table table-striped table-bordered table-hover dataTable" id="editable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center" style="background-color:white">
|
||||
<input type="checkbox" id="check_all" onclick="checkAll()">
|
||||
</th>
|
||||
<th id="th_no">id</th>
|
||||
<th>资产名称</th>
|
||||
<th>IP</th>
|
||||
<th>类型</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for asset in assets %}
|
||||
{% if asset.id in all_assets %}
|
||||
<tr name="oAssets" class="odd selected text-center">
|
||||
<td class="text-center" ><input type="checkbox" name="checked" value="{{ asset.id }}" checked="checked"></td>
|
||||
{% else %}
|
||||
<tr name="oAssets">
|
||||
<td class="text-center"><input type="checkbox" name="checked" value="{{ asset.id }}" ></td>
|
||||
{% endif %}
|
||||
<td class="text-center">{{ asset.id }}</td>
|
||||
<td class="text-center">{{ asset.hostname }}</td>
|
||||
<td class="text-center">{{ asset.ip }}</td>
|
||||
<td class="text-center">{{ asset.env }}-{{ asset.type }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" id="close-btn">取消</button>
|
||||
<button type="button" class="btn btn-primary" id="save-btn">保存</button>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function(){
|
||||
var table = $('#editable').DataTable({
|
||||
"aLengthMenu": [[10, 25, 50, -1], ["10", "25", "50", "all"]],
|
||||
"iDisplayLength":25,
|
||||
"aaSorting": [[2, "asc"]],
|
||||
"aoColumnDefs": [ { "bSortable": false, "aTargets": [ 0 ] }],
|
||||
"bAutoWidth": false,
|
||||
"language": {
|
||||
"url": "/static/js/plugins/dataTables/i18n/zh-hans.json"
|
||||
},
|
||||
columns: [
|
||||
{data: "checkbox"},
|
||||
{data: "id"},
|
||||
{data: "hostname"},
|
||||
{data: "ip"},
|
||||
{data: "type"}
|
||||
]
|
||||
});
|
||||
//将ID列隐藏
|
||||
table.column('1').visible(false);
|
||||
|
||||
$('#editable tbody').on( 'click', 'tr', function () {
|
||||
//alert($(this).hasClass('selected'));
|
||||
if($(this).hasClass('selected')){
|
||||
$(this).removeClass('selected');
|
||||
this.children[0].children[0].checked=0;
|
||||
}else{
|
||||
$(this).addClass('selected');
|
||||
this.children[0].children[0].checked=1;
|
||||
}
|
||||
});
|
||||
|
||||
$('#close-btn').on('click',function(){
|
||||
$('#modal').modal('hide');
|
||||
});
|
||||
var size_name = document.getElementById('asset_on_count').innerText;
|
||||
$('#save-btn').on('click',function(){
|
||||
//alert( table.rows('.selected').data().length +' row(s) selected' );
|
||||
var d = table.rows('.selected').data();
|
||||
var size = d.length;
|
||||
var re = /\d+/;
|
||||
document.getElementById('add_asset').value = size;
|
||||
var str= size_name;
|
||||
var re=/\d+/g;
|
||||
document.getElementById('asset_on_count').innerText = str.replace(re, size);
|
||||
var column2 = table.rows('.selected').data();
|
||||
$("#asset_sed").find("input[name='assets']").remove();
|
||||
$("#asset_sed").find("button[name='asset_hostname']").remove();
|
||||
for(var i=0;i<column2.length;i++){
|
||||
column2[i].checkbox='<input name="checked" value="1" checked="" type="checkbox">';
|
||||
var value = column2[i].id;
|
||||
var ip = column2[i].ip;
|
||||
var hostname = column2[i].hostname;
|
||||
$("#asset_sed").append("<input type='hidden' name='assets' value='"+value+"'>");
|
||||
$("#asset_on_p").append("<button name='asset_hostname' title='"+ip+"' type='button' class='btn btn-default btn-xs ss'>"+hostname+"</button> ");
|
||||
}
|
||||
$('#modal').modal('hide');
|
||||
});
|
||||
|
||||
}); //$(document).ready
|
||||
|
||||
var bCheck = 1;
|
||||
function checkAll(){
|
||||
if(bCheck){
|
||||
$("tr[name='oAssets']").each(function(){
|
||||
oCheckbox = this.children[0].children[0];
|
||||
$(this).toggleClass('selected',true);
|
||||
oCheckbox.checked=1;
|
||||
});
|
||||
document.getElementById('check_all').checked=1;
|
||||
bCheck = 0;
|
||||
}else{
|
||||
$("tr[name='oAssets']").each(function(){
|
||||
oCheckbox = this.children[0].children[0];
|
||||
$(this).toggleClass('selected',false);
|
||||
oCheckbox.checked=0;
|
||||
});
|
||||
document.getElementById('check_all').checked=0;
|
||||
bCheck = 1;
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
73
apps/assets/templates/assets/asset_update.html
Normal file
73
apps/assets/templates/assets/asset_update.html
Normal file
@@ -0,0 +1,73 @@
|
||||
{% extends '_base_create_update.html' %}
|
||||
{% load static %}
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block custom_head_css_js_create %}
|
||||
<link href="{% static "css/plugins/inputTags.css" %}" rel="stylesheet">
|
||||
<script src="{% static "js/plugins/inputTags.jquery.min.js" %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block form %}
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% if form.no_field_errors %}
|
||||
<div class="alert alert-danger">
|
||||
{{ form.non_field_errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% csrf_token %}
|
||||
<h3>{% trans 'Basic' %}</h3>
|
||||
{% bootstrap_field form.hostname layout="horizontal" %}
|
||||
{% bootstrap_field form.ip layout="horizontal" %}
|
||||
{% bootstrap_field form.public_ip layout="horizontal" %}
|
||||
{% bootstrap_field form.port layout="horizontal" %}
|
||||
{% bootstrap_field form.type layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Group' %}</h3>
|
||||
{% bootstrap_field form.idc layout="horizontal" %}
|
||||
{% bootstrap_field form.groups layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Asset user' %}</h3>
|
||||
{% bootstrap_field form.admin_user layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Configuration' %}</h3>
|
||||
{% bootstrap_field form.number layout="horizontal" %}
|
||||
{% bootstrap_field form.remote_card_ip layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Location' %}</h3>
|
||||
{% bootstrap_field form.cabinet_no layout="horizontal" %}
|
||||
{% bootstrap_field form.cabinet_pos layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Other' %}</h3>
|
||||
{% bootstrap_field form.status layout="horizontal" %}
|
||||
{% bootstrap_field form.env layout="horizontal" %}
|
||||
{% bootstrap_field form.comment layout="horizontal" %}
|
||||
{% bootstrap_field form.is_active 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-white" 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>
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
$("#tags").select2({
|
||||
tags: true,
|
||||
maximumSelectionLength: 8 //最多能够选择的个数
|
||||
});
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
15
apps/assets/templates/assets/delete_confirm.html
Normal file
15
apps/assets/templates/assets/delete_confirm.html
Normal file
@@ -0,0 +1,15 @@
|
||||
{% load i18n %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{% trans 'Confirm delete' %}</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<p>{% trans 'Are you sure delete' %} <b>{{ object.name }} </b> ?</p>
|
||||
<input type="submit" value="Confirm" />
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
364
apps/assets/templates/assets/idc_assets.html
Normal file
364
apps/assets/templates/assets/idc_assets.html
Normal file
@@ -0,0 +1,364 @@
|
||||
{% 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>
|
||||
<style type="text/css">
|
||||
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<li>
|
||||
<a href="{% url 'assets:idc-detail' pk=idc.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
|
||||
</li>
|
||||
<li class="active"><a href="{% url 'assets:idc-assets' pk=idc.id %}" class="text-center">
|
||||
<i class="fa fa-bar-chart-o"></i> {% trans 'IDC assets' %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-7" style="padding-left: 0;">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span style="float: left">{% trans 'IDC assets' %} <b>{{ idc.name }} </b><span class="badge"></span></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 table-striped table-bordered table-hover " id="idc_assets_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<input type="checkbox" id="check_all" class="ipt_check_all" >
|
||||
</th>
|
||||
<th>{% trans 'Hostname' %}</th>
|
||||
<th>{% trans 'IP' %}</th>
|
||||
<th>{% trans 'Port' %}</th>
|
||||
<th>{% trans 'Type' %}</th>
|
||||
<th>{% trans 'Valid' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div id="actions" class="hide">
|
||||
<div class="input-group">
|
||||
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
|
||||
<option value="delete">{% trans 'Remove selected' %}</option>
|
||||
</select>
|
||||
<div class="input-group-btn pull-left" style="padding-left: 5px;">
|
||||
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-warning">
|
||||
{% trans 'Submit' %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Add assets to' %} {{ idc.name }}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<form>
|
||||
<tr class="no-borders-tr">
|
||||
<td colspan="2">
|
||||
<select data-placeholder="{% trans 'Select asset' %}" class="select2" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for asset in assets_remain %}
|
||||
<option value="{{ asset.id }}" id="opt_{{ asset.id }}">{{ asset.ip}}:{{ asset.port }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="no-borders-tr">
|
||||
<td colspan="2">
|
||||
<button type="button" class="btn btn-primary btn-sm btn-asset-attach">{% trans 'Confirm' %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
||||
<script>
|
||||
|
||||
jumpserver.assets_selected = {};
|
||||
|
||||
Array.prototype.remove = function(val) {
|
||||
var index = this.indexOf(val);
|
||||
if (index > -1) {
|
||||
this.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
function updateIDCAssets(assets) {
|
||||
var the_url = "{% url 'api-assets:idc-update-assets' pk=idc.id %}";
|
||||
var body = {
|
||||
assets: Object.assign([], assets)
|
||||
};
|
||||
var $data_table = $("#idc_assets_table").DataTable();
|
||||
var success = function(data) {
|
||||
$('.select2-selection__rendered').empty();
|
||||
$.map(jumpserver.assets_selected, function(asset_ip, index) {
|
||||
$('#opt_' + index).remove();
|
||||
$data_table.ajax.reload();
|
||||
});
|
||||
jumpserver.groups_selected = {};
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
body: JSON.stringify(body),
|
||||
method: 'PUT',
|
||||
success: success
|
||||
});
|
||||
}
|
||||
|
||||
function deleteIDCAssets(assets) {
|
||||
var the_url = "{% url 'api-assets:idc-update-assets' pk=idc.id %}";
|
||||
var body = {
|
||||
assets: Object.assign([], assets)
|
||||
};
|
||||
var $data_table = $("#idc_assets_table").DataTable();
|
||||
var success = function(data) {
|
||||
$data_table.ajax.reload();
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
body: JSON.stringify(body),
|
||||
method: 'PUT',
|
||||
success: success
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2()
|
||||
.on("select2:select", function (evt) {
|
||||
var data = evt.params.data;
|
||||
jumpserver.assets_selected[data.id] = data.text;
|
||||
})
|
||||
.on('select2:unselect', function(evt) {
|
||||
var data = evt.params.data;
|
||||
delete jumpserver.assets_selected[data.id];
|
||||
});
|
||||
var options = {
|
||||
ele: $('#idc_assets_table'),
|
||||
buttons: [],
|
||||
order: [],
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url "assets:asset-detail" pk=99991937 %}" data-aid="'+rowData.id+'">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('99991937', rowData.id));
|
||||
}},
|
||||
{targets: 5, createdCell: function (td, cellData) {
|
||||
if (!cellData) {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
}
|
||||
}}],
|
||||
ajax_url: '{% url "api-assets:asset-list" %}?idc_id={{ idc.id }}',
|
||||
columns: [{data: function(){return ""}}, {data: "hostname" }, {data: "ip" }, {data: "port" },
|
||||
{data: "type" }, {data: "is_active" }],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
|
||||
})
|
||||
|
||||
.on('click', '.btn-asset-attach', function () {
|
||||
if (Object.keys(jumpserver.assets_selected).length === 0) {
|
||||
return false;
|
||||
}
|
||||
var assets=[];
|
||||
var $data_table = $("#idc_assets_table").DataTable();
|
||||
$.ajax({
|
||||
url: '{% url "api-assets:asset-list" %}',
|
||||
method: 'GET',
|
||||
data: {"idc_id": {{ idc.id }}},
|
||||
dataType: 'json',
|
||||
success: function (result) {
|
||||
for(var i in result){
|
||||
if (!isNaN(parseInt(result[i]['id']))) {
|
||||
assets.push(parseInt(result[i]['id']))
|
||||
}
|
||||
}
|
||||
$.map(jumpserver.assets_selected, function(value, index) {
|
||||
assets.push(parseInt(index));
|
||||
});
|
||||
updateIDCAssets(assets);
|
||||
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
.on('click', '#btn_bulk_update', function () {
|
||||
var action = $("#slct_bulk_update").val();
|
||||
var $data_table = $("#idc_assets_table").DataTable();
|
||||
var id_list = [];
|
||||
var plain_id_list = [];
|
||||
var assets = [];
|
||||
$data_table.rows({selected: true}).every(function(){
|
||||
id_list.push({id: this.data().id});
|
||||
plain_id_list.push(this.data().id);
|
||||
});
|
||||
if (id_list === []) {
|
||||
return false;
|
||||
}
|
||||
$.ajax({
|
||||
url: '{% url "api-assets:asset-list" %}',
|
||||
data: {"idc_id": {{ idc.id }}},
|
||||
dataType: 'json',
|
||||
method: 'GET',
|
||||
success: function (result) {
|
||||
for (var i in result) {
|
||||
if (!isNaN(result[i]['id'])) {
|
||||
assets.push(result[i]['id']);
|
||||
}
|
||||
}
|
||||
for (var j in plain_id_list) {
|
||||
assets.remove(plain_id_list[j])
|
||||
}
|
||||
function doDelete() {
|
||||
swal({
|
||||
title: "{% trans 'Are you sure?' %}",
|
||||
text: "{% trans 'This will delete the selected assets !!!' %}",
|
||||
type: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#DD6B55",
|
||||
confirmButtonText: "{% trans 'Confirm' %}",
|
||||
closeOnConfirm: false
|
||||
}, function() {
|
||||
var success = function() {
|
||||
var msg = "{% trans 'Asset Deleted.' %}";
|
||||
swal("{% trans 'Asset Delete' %}", msg, "success");
|
||||
$('#idc_assets_table').DataTable().ajax.reload();
|
||||
};
|
||||
var fail = function() {
|
||||
var msg = "{% trans 'Asset Deleting failed.' %}";
|
||||
swal("{% trans 'Asset Delete' %}", msg, "error");
|
||||
};
|
||||
var url_delete = "{% url 'api-assets:idc-update-assets' pk=idc.id %}";
|
||||
var body = {
|
||||
assets: Object.assign([], assets)
|
||||
};
|
||||
APIUpdateAttr({url: url_delete, body: JSON.stringify(body), method: 'PUT', success: success, error: fail});
|
||||
jumpserver.checked = false;
|
||||
});
|
||||
}
|
||||
function doUpdate() {
|
||||
$('#asset_bulk_update_modal').modal('show');
|
||||
}
|
||||
switch (action) {
|
||||
case 'delete':
|
||||
doDelete();
|
||||
break;
|
||||
case 'update':
|
||||
doUpdate();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
.on('click', '#btn_asset_bulk_update', function () {
|
||||
var json_data = $("#fm_asset_bulk_update").serializeObject();
|
||||
var body = {};
|
||||
body.enable_otp = (json_data.enable_otp === 'on')? true: false;
|
||||
if (json_data.type != '') {
|
||||
body.type = json_data.type;
|
||||
}
|
||||
|
||||
if (json_data.groups != undefined) {
|
||||
body.groups = json_data.groups;
|
||||
}
|
||||
if (typeof body.groups === 'string') {
|
||||
body.groups = [parseInt(body.groups)]
|
||||
} else if(typeof body.groups === 'array') {
|
||||
var new_groups = body.groups.map(Number);
|
||||
body.groups = new_groups;
|
||||
}
|
||||
|
||||
if (json_data.users != undefined) {
|
||||
body.users = json_data.users;
|
||||
}
|
||||
if (typeof body.users === 'string') {
|
||||
body.users = [parseInt(body.users)]
|
||||
} else if(typeof body.users === 'array') {
|
||||
var new_users = body.users.map(Number);
|
||||
body.users = new_users;
|
||||
}
|
||||
|
||||
if (json_data.tags != undefined) {
|
||||
body.tags = json_data.tags;
|
||||
}
|
||||
if (typeof body.tags == 'string') {
|
||||
body.tags = [parseInt(body.tags)];
|
||||
} else if (typeof body.tags === 'array') {
|
||||
var new_tags = body.tags.map(Number);
|
||||
body.tags = new_tags;
|
||||
}
|
||||
|
||||
var $data_table = $('#asset_list_table').DataTable();
|
||||
var post_list = [];
|
||||
$data_table.rows({selected: true}).every(function(){
|
||||
var content = Object.assign({id: this.data().id}, body);
|
||||
post_list.push(content);
|
||||
});
|
||||
if (post_list === []) {
|
||||
return false
|
||||
}
|
||||
var the_url = "{% url 'api-assets:asset-list' %}";
|
||||
var success = function() {
|
||||
var msg = "{% trans 'The selected assets has been updated successfully.' %}";
|
||||
swal("{% trans 'Asset Updated' %}", msg, "success");
|
||||
$('#asset_list_table').DataTable().ajax.reload();
|
||||
jumpserver.checked = false;
|
||||
};
|
||||
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(post_list), success: success});
|
||||
$('#asset_bulk_update_modal').modal('hide');
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
73
apps/assets/templates/assets/idc_create_update.html
Normal file
73
apps/assets/templates/assets/idc_create_update.html
Normal file
@@ -0,0 +1,73 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load bootstrap3 %}
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet">
|
||||
<script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-10">
|
||||
<div class="ibox float-e-margins">
|
||||
<div id="ibox-content" class="ibox-title">
|
||||
<h5> {{ action }}</h5>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ibox-content">
|
||||
<div class="panel blank-panel">
|
||||
<div class="panel-body">
|
||||
<div class="tab-content">
|
||||
<div id="tab-1" class="ibox float-e-margins tab-pane active"></div>
|
||||
<form id="IDCForm" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
<h3 class="widget-head-color-box">基本信息</h3>
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
{% bootstrap_field form.address layout="horizontal" %}
|
||||
{% bootstrap_field form.contact layout="horizontal" %}
|
||||
{% bootstrap_field form.phone layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3 class="widget-head-color-box">IP段</h3>
|
||||
{% bootstrap_field form.operator layout="horizontal" %}
|
||||
{% bootstrap_field form.intranet layout="horizontal" %}
|
||||
{% bootstrap_field form.extranet 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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
156
apps/assets/templates/assets/idc_detail.html
Normal file
156
apps/assets/templates/assets/idc_detail.html
Normal file
@@ -0,0 +1,156 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet">
|
||||
<script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active">
|
||||
<a href="{% url 'assets:idc-detail' pk=idc.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'assets:idc-assets' pk=idc.id %}" class="text-center">
|
||||
<i class="fa fa-bar-chart-o"></i> {% trans 'IDC assets' %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-default" href="{% url 'assets:idc-update' pk=idc.id %}"><i class="fa fa-edit"></i>Update</a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-danger btn-delete-idc">
|
||||
<i class="fa fa-edit"></i>Delete
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-7" style="padding-left: 0;">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span class="label"><b>{{ idc.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 width="20%">{% trans 'Name' %}:</td>
|
||||
<td><b>{{ idc.name }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Bandwidth' %}:</td>
|
||||
<td><b>{{ idc.bandwidth }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Contact' %}:</td>
|
||||
<td><b>{{ idc.contact }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Phone' %}:</td>
|
||||
<td><b>{{ idc.phone }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Address' %}:</td>
|
||||
<td><b>{{ idc.address }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Intranet' %}:</td>
|
||||
<td><b>{{ idc.Intranet }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Extranet' %}:</td>
|
||||
<td><b>{{ idc.extranet }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Operator' %}:</td>
|
||||
<td><b>{{ idc.operator }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Date created' %}:</td>
|
||||
<td><b>{{ system_user.date_created }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Created by' %}:</td>
|
||||
<td><b>{{ asset_group.created_by }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Comment' %}:</td>
|
||||
<td><b>{{ system_user.comment }}</b></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
function idcDelete(name, url) {
|
||||
function doDelete() {
|
||||
var body = {};
|
||||
var success = function() {
|
||||
swal('Deleted!', "[ "+name+"]"+" has been deleted ", "success");
|
||||
window.location.href="{% url 'assets:idc-list' %}";
|
||||
};
|
||||
var fail = function() {
|
||||
swal("Failed", "Delete"+"[ "+name+" ]"+"failed", "error");
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: url,
|
||||
body: JSON.stringify(body),
|
||||
method: 'DELETE',
|
||||
success: success,
|
||||
error: fail
|
||||
});
|
||||
}
|
||||
swal({
|
||||
title: 'Are you sure delete ?',
|
||||
text: " [" + name + "] ",
|
||||
type: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonText: 'Cancel',
|
||||
confirmButtonColor: "#DD6B55",
|
||||
confirmButtonText: 'Confirm',
|
||||
closeOnConfirm: false
|
||||
}, function () {
|
||||
doDelete()
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
})
|
||||
.on('click', '.btn-delete-idc', function () {
|
||||
var name = $('.idc-details > tbody > tr').attr("data-name");
|
||||
var id = {{ idc.id }};
|
||||
var the_url = '{% url "api-assets:idc-detail" pk=99991937 %}'.replace(99991937, id);
|
||||
idcDelete(name, the_url);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
129
apps/assets/templates/assets/idc_list.html
Normal file
129
apps/assets/templates/assets/idc_list.html
Normal file
@@ -0,0 +1,129 @@
|
||||
{% extends '_base_list.html' %}
|
||||
{% load i18n static %}
|
||||
{% 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 table_search %}{% endblock %}
|
||||
{% block table_container %}
|
||||
<div class="uc pull-left m-l-5 m-r-5">
|
||||
<a href="{% url "assets:idc-create" %}" class="btn btn-sm btn-primary"> {% trans "Create IDC" %} </a>
|
||||
</div>
|
||||
<table class="table table-striped table-bordered table-hover " id="idc_list_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<input type="checkbox" id="check_all" class="ipt_check_all" >
|
||||
</th>
|
||||
<th class="text-center"><a href="{% url 'assets:idc-list' %}?sort=name">{% trans 'Name' %}</a></th>
|
||||
<th class="text-center">{% trans 'Asset num' %}</th>
|
||||
<th class="text-center">{% trans 'Contact' %}</th>
|
||||
<th class="text-center">{% trans 'Phone' %}</th>
|
||||
<th class="text-center">{% trans 'Operator' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="actions" class="hide">
|
||||
<div class="input-group">
|
||||
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
|
||||
<option value="delete">{% trans 'Delete selected' %}</option>
|
||||
</select>
|
||||
<div class="input-group-btn pull-left" style="padding-left: 5px;">
|
||||
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
|
||||
{% trans 'Submit' %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block content_bottom_left %}{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
var options = {
|
||||
ele: $('#idc_list_table'),
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url "assets:idc-detail" pk=99991937 %}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('99991937', rowData.id));
|
||||
}},
|
||||
|
||||
{targets: 6, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "assets:idc-update" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', cellData);
|
||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_idc_delete" data-uid="99991937">{% trans "Delete" %}</a>'.replace('99991937', cellData);
|
||||
$(td).html(update_btn + del_btn)
|
||||
}}],
|
||||
ajax_url: '{% url "api-assets:idc-list" %}',
|
||||
columns: [{data: function(){return ""}}, {data: "name" }, {data: "assets_amount" }, {data: "contact" }, {data: "phone" },
|
||||
{data: "operator" }, {data: "id" }],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
})
|
||||
|
||||
.on('click', '.btn_idc_delete', function () {
|
||||
var $this = $(this);
|
||||
var $data_table = $('#idc_list_table').DataTable();
|
||||
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
|
||||
var uid = $this.data('uid');
|
||||
var the_url = '{% url "api-assets:idc-detail" pk=99991937 %}'.replace('99991937', uid);
|
||||
objectDelete($this, name, the_url);
|
||||
setTimeout( function () {
|
||||
$data_table.ajax.reload();
|
||||
}, 3000);
|
||||
})
|
||||
|
||||
.on('click', '#btn_bulk_update', function () {
|
||||
var action = $('#slct_bulk_update').val();
|
||||
var $data_table = $('#idc_list_table').DataTable();
|
||||
var id_list = [];
|
||||
var plain_id_list = [];
|
||||
$data_table.rows({selected: true}).every(function(){
|
||||
id_list.push({id: this.data().id});
|
||||
plain_id_list.push(this.data().id);
|
||||
});
|
||||
if (id_list === []) {
|
||||
return false;
|
||||
}
|
||||
var the_url = "{% url 'api-assets:idc-list' %}";
|
||||
function doDelete() {
|
||||
swal({
|
||||
title: "{% trans 'Are you sure?' %}",
|
||||
text: "{% trans 'This will delete the selected idc' %}",
|
||||
type: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#DD6B55",
|
||||
confirmButtonText: "{% trans 'Confirm' %}",
|
||||
closeOnConfirm: false
|
||||
}, function() {
|
||||
var success = function() {
|
||||
var msg = "{% trans 'IDC Deleted.' %}";
|
||||
swal("{% trans 'IDC Delete' %}", msg, "success");
|
||||
$('#idc_list_table').DataTable().ajax.reload();
|
||||
};
|
||||
var fail = function() {
|
||||
var msg = "{% trans 'IDC Deleting failed.' %}";
|
||||
swal("{% trans 'IDC Delete' %}", msg, "error");
|
||||
};
|
||||
var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list);
|
||||
APIUpdateAttr({url: url_delete, method: 'DELETE', success: success, error: fail});
|
||||
$data_table.ajax.reload();
|
||||
jumpserver.checked = false;
|
||||
});
|
||||
}
|
||||
switch (action) {
|
||||
case 'delete':
|
||||
doDelete();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
371
apps/assets/templates/assets/system_user_asset.html
Normal file
371
apps/assets/templates/assets/system_user_asset.html
Normal file
@@ -0,0 +1,371 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet">
|
||||
<script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<li>
|
||||
<a href="{% url 'assets:system-user-detail' pk=system_user.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
|
||||
</li>
|
||||
<li class="active"><a href="{% url 'assets:system-user-asset' pk=system_user.id %}" class="text-center">
|
||||
<i class="fa fa-bar-chart-o"></i> {% trans 'Attached assets' %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-7" style="padding-left: 0;">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span style="float: left">{% trans 'Assets of ' %} <b>{{ system_user.name }} </b><span class="badge">{{ paginator.count }}</span></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 table-hover" id="system_user_list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans 'Hostname' %}</th>
|
||||
<th>{% trans 'IP' %}</th>
|
||||
<th>{% trans 'Port' %}</th>
|
||||
<th>{% trans 'Reachable' %}</th>
|
||||
<th>{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for asset in page_obj %}
|
||||
<tr>
|
||||
<td>{{ asset.hostname }}</td>
|
||||
<td>{{ asset.ip }}</td>
|
||||
<td>{{ asset.port }}</td>
|
||||
<td>
|
||||
<i class="fa fa-check text-navy"></i>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-danger pull-right btn-xs {% if asset.is_inherit_from_asset_groups %} disabled {% endif %}" type="button"><i class="fa fa-minus"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{# <div class="row">#}
|
||||
{# {% include '_pagination.html' %}#}
|
||||
{# </div>#}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Attach to assets ' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<form>
|
||||
<tr class="no-borders-tr">
|
||||
<td colspan="2">
|
||||
<select data-placeholder="{% trans 'Select asset' %}" class="select2" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for asset in assets_remain %}
|
||||
<option value="{{ asset.id }}">{{ asset.ip}}:{{ asset.port }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="no-borders-tr">
|
||||
<td colspan="2">
|
||||
<button type="button" class="btn btn-primary btn-sm btn-add-asset2system-user">{% trans 'Confirm' %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Attach to asset groups' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table group_edit">
|
||||
<tbody>
|
||||
<form>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<select data-placeholder="{% trans 'Add asset group' %}" class="select2" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for asset_group in asset_groups_remain %}
|
||||
<option value="{{ asset_group.id }}">{{ asset_group.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<button type="button" class="btn btn-info btn-sm" id="btn_add_user_group">{% trans 'Attach AssetGroup' %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
|
||||
{% for asset_group in asset_groups %}
|
||||
<tr>
|
||||
<td ><b class="bdg_asset_groups" data-gid={{ asset_group.id }}>{{ asset_group.name }}</b></td>
|
||||
<td>
|
||||
<button class="btn btn-danger pull-right btn-xs btn-leave-system_user" type="button"><i class="fa fa-minus"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
jumpserver.assets_selected = {};
|
||||
jumpserver.asset_groups_selected = {};
|
||||
Array.prototype.remove = function(val) {
|
||||
var index = this.indexOf(val);
|
||||
if (index > -1) {
|
||||
this.splice(index, 1);
|
||||
}
|
||||
};
|
||||
Array.prototype.unique = function(){
|
||||
var res = [];
|
||||
var json = {};
|
||||
for(var i = 0; i < this.length; i++){
|
||||
if(!json[this[i]]){
|
||||
res.push(this[i]);
|
||||
json[this[i]] = 1;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
};
|
||||
function objectDelete(obj, name, url, data) {
|
||||
function doDelete() {
|
||||
var body = data;
|
||||
var success = function() {
|
||||
swal('Deleted!', "[ "+name+"]"+" has been deleted ", "success");
|
||||
$(obj).parent().parent().remove();
|
||||
};
|
||||
var fail = function() {
|
||||
swal("Failed", "Delete"+"[ "+name+" ]"+"failed", "error");
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: url,
|
||||
body: JSON.stringify(body),
|
||||
method: 'PATCH',
|
||||
success: success,
|
||||
error: fail
|
||||
});
|
||||
}
|
||||
swal({
|
||||
title: 'Are you sure delete ?',
|
||||
text: " [" + name + "] ",
|
||||
type: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonText: 'Cancel',
|
||||
confirmButtonColor: "#DD6B55",
|
||||
confirmButtonText: 'Confirm',
|
||||
closeOnConfirm: false
|
||||
}, function () {
|
||||
doDelete()
|
||||
});
|
||||
}
|
||||
function updateSystemUserAssetGroup(asset_groups) {
|
||||
var the_url = "{% url 'api-assets:systemuser-update-assetgroups' pk=system_user.id %}";
|
||||
var body = {
|
||||
asset_groups: Object.assign([], asset_groups)
|
||||
};
|
||||
var success = function(data) {
|
||||
$('.select2-selection__rendered').empty();
|
||||
$('#groups_selected').val('');
|
||||
$.map(jumpserver.asset_groups_selected, function(asset_groups, index) {
|
||||
$('#opt_' + index).remove();
|
||||
$('.system-user-table tbody').append(
|
||||
'<tr>' +
|
||||
'<td><b class="bdg_asset_groups" data-sid="' + index + '">' + asset_groups + '</b></td>' +
|
||||
'<td><button class="btn btn-danger btn-xs pull-right btn-leave-system_user" type="button"><i class="fa fa-minus"></i></button></td>' +
|
||||
'</tr>'
|
||||
)
|
||||
});
|
||||
jumpserver.assets_selected = {};
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
body: JSON.stringify(body),
|
||||
success: success
|
||||
});
|
||||
}
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2()
|
||||
.on("select2:select", function (evt) {
|
||||
var data = evt.params.data;
|
||||
jumpserver.assets_selected[data.id] = data.text;
|
||||
jumpserver.asset_groups_selected[data.id] = data.text;
|
||||
})
|
||||
.on('select2:unselect', function(evt) {
|
||||
var data = evt.params.data;
|
||||
delete jumpserver.assets_selected[data.id];
|
||||
delete jumpserver.asset_groups_selected[data.id];
|
||||
});
|
||||
var options = {
|
||||
ele: $('#system_user_list'),
|
||||
buttons: [],
|
||||
order: [],
|
||||
columnDefs: [
|
||||
{targets: 0, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url "assets:asset-detail" pk=99991937 %}" data-aid="'+rowData.id+'">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('99991937', rowData.id));
|
||||
}},
|
||||
{targets: 3, createdCell: function (td, cellData) {
|
||||
if (!cellData) {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "assets:asset-update" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', rowData.id);
|
||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_delete" data-aid="99991937">{% trans "Delete" %}</a>'.replace('99991937', rowData.id);
|
||||
$(td).html(update_btn + del_btn)
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-assets:asset-list" %}?system_user_id={{ system_user.id }}',
|
||||
columns: [{data: "hostname" }, {data: "ip" }, {data: "port" }, {data: function () { return ""; } }, {data: "id"}],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
})
|
||||
|
||||
.on('click', '.btn-add-asset2system-user', function () {
|
||||
if (Object.keys(jumpserver.assets_selected).length === 0) {
|
||||
return false;
|
||||
}
|
||||
var $data_table = $("#system_user_list").DataTable();
|
||||
var assets = [];
|
||||
$.ajax({
|
||||
url: '{% url "api-assets:asset-list" %}?system_user_id={{ system_user.id }}',
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
success: function (result) {
|
||||
for(var i in result){
|
||||
if (!isNaN(parseInt(result[i]['id']))) {
|
||||
assets.push(parseInt(result[i]['id']))
|
||||
}
|
||||
}
|
||||
$.map(jumpserver.assets_selected, function(value, index) {
|
||||
assets.push(parseInt(index));
|
||||
});
|
||||
assets.unique();
|
||||
var the_url = "{% url 'api-assets:systemuser-update-assets' pk=system_user.id %}";
|
||||
var body = {"assets": assets};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
body: JSON.stringify(body),
|
||||
method: 'PATCH'
|
||||
});
|
||||
$data_table.ajax.reload();
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
.on('click', '.btn_asset_delete', function () {
|
||||
var $this = $(this);
|
||||
var the_url = "{% url 'api-assets:systemuser-update-assets' pk=system_user.id %}";
|
||||
var name = $(this).closest("tr").find(":nth-child(1) > a").html();
|
||||
var $data_table = $("#system_user_list").DataTable();
|
||||
var assets = [];
|
||||
$('#system_user_list > tbody > tr').map(function () {
|
||||
assets.push(parseInt($(this).closest("tr").find(":nth-child(1) > a").attr("data-aid")))
|
||||
});
|
||||
var delete_asset_id = $(this).data('aid');
|
||||
assets.remove(delete_asset_id);
|
||||
assets.unique();
|
||||
var data = {"assets": assets};
|
||||
objectDelete($this, name, the_url, data);
|
||||
$data_table.ajax.reload();
|
||||
})
|
||||
|
||||
.on('click', '#btn_add_user_group', function () {
|
||||
jumpserver.assets_selected = {};
|
||||
if (Object.keys(jumpserver.asset_groups_selected).length === 0) {
|
||||
return false;
|
||||
}
|
||||
asset_groups = [];
|
||||
$.ajax({
|
||||
url: '{% url "api-assets:systemuser-update-assetgroups" pk=system_user.id %}',
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
success: function (result) {
|
||||
for (var i in result['asset_groups']) {
|
||||
if (!isNaN(result['asset_groups'][i])) {
|
||||
asset_groups.push(parseInt(result['asset_groups'][i]));
|
||||
}
|
||||
}
|
||||
$.map(jumpserver.asset_groups_selected, function(value, index) {
|
||||
asset_groups.push(parseInt(index));
|
||||
});
|
||||
asset_groups.unique();
|
||||
console.log(asset_groups);
|
||||
var the_url = '{% url "api-assets:systemuser-update-assetgroups" pk=system_user.id %}';
|
||||
var body = {"asset_groups": asset_groups};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
body: JSON.stringify(body),
|
||||
method: 'PATCH'
|
||||
});
|
||||
{# TODO: reload the table #}
|
||||
{# window.location.href="{% url 'assets:system-user-asset' pk=system_user.id %}"#}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
.on('click', '.btn-leave-system_user', function () {
|
||||
var $this = $(this);
|
||||
var $tr = $this.closest('tr');
|
||||
var $badge = $tr.find('.bdg_asset_groups');
|
||||
var sid = $badge.data('gid');
|
||||
var name = $badge.html() || $badge.text();
|
||||
$('system-user-table').append(
|
||||
'<option value="' + sid + '" id="opt_' + sid + '">' + name + '</option>'
|
||||
);
|
||||
$tr.remove();
|
||||
var asset_groups = $('.bdg_asset_groups').map(function () {
|
||||
return $(this).data('gid');
|
||||
}).get();
|
||||
updateSystemUserAssetGroup(asset_groups);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
7
apps/assets/templates/assets/system_user_create.html
Normal file
7
apps/assets/templates/assets/system_user_create.html
Normal file
@@ -0,0 +1,7 @@
|
||||
{% extends 'assets/_system_user.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% block auth %}
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
178
apps/assets/templates/assets/system_user_detail.html
Normal file
178
apps/assets/templates/assets/system_user_detail.html
Normal file
@@ -0,0 +1,178 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block custom_head_css_js %}
|
||||
<link href='{% static "css/plugins/select2/select2.min.css" %}' rel="stylesheet">
|
||||
<script src='{% static "js/plugins/select2/select2.full.min.js" %}'></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active">
|
||||
<a href="{% url 'assets:system-user-detail' pk=system_user.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'assets:system-user-asset' pk=system_user.id %}" class="text-center">
|
||||
<i class="fa fa-bar-chart-o"></i> {% trans 'Attached assets' %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-default" href="{% url 'assets:system-user-update' pk=system_user.id %}"><i class="fa fa-edit"></i>Update</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-7" style="padding-left: 0;">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span class="label"><b>{{ system_user.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>{{ system_user.name }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Username' %}:</td>
|
||||
<td><b>{{ system_user.username }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Protocol' %}:</td>
|
||||
<td><b>{{ system_user.protocol }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Auto push' %}:</td>
|
||||
<td><b>{{ system_user.auto_push|yesno:"Yes,No,Unknown" }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Sudo' %}:</td>
|
||||
<td><b>{{ system_user.sudo }}</b></td>
|
||||
</tr>
|
||||
{% if system_user.shell %}
|
||||
<tr>
|
||||
<td>{% trans 'Shell' %}:</td>
|
||||
<td><b>{{ system_user.shell }}</b></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if system_user.home %}
|
||||
<tr>
|
||||
<td>{% trans 'Home' %}:</td>
|
||||
<td><b>{{ system_user.home }}</b></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if system_user.uid %}
|
||||
<tr>
|
||||
<td>{% trans 'Uid' %}:</td>
|
||||
<td><b>{{ system_user.uid }}</b></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td>{% trans 'Date created' %}:</td>
|
||||
<td><b>{{ system_user.date_created }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Created by' %}:</td>
|
||||
<td><b>{{ asset_group.created_by }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Comment' %}:</td>
|
||||
<td><b>{{ system_user.comment }}</b></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Quick update' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr class="no-borders-tr">
|
||||
<td width="50%">{% trans 'Get manual install script' %}:</td>
|
||||
<td>
|
||||
<span style="float: right">
|
||||
<button type="button" class="btn btn-primary btn-xs" style="width: 54px">{% trans 'Get' %}</button>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td width="50%">{% trans 'Retest asset connectivity' %}:</td>
|
||||
<td>
|
||||
<span style="float: right">
|
||||
<button type="button" class="btn btn-primary btn-xs" style="width: 54px">{% trans 'Start' %}</button>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td width="50%">{% trans 'Reset private key' %}:</td>
|
||||
<td>
|
||||
<span style="float: right">
|
||||
<button type="button" class="btn btn-primary btn-xs" style="width: 54px">{% trans 'Reset' %}</button>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
{# function switch_user_status(obj) {#}
|
||||
{# var status = $(obj).prop('checked');#}
|
||||
{##}
|
||||
{# $.ajax({#}
|
||||
{# url: "{% url 'users:user-active-api' pk=user.id %}",#}
|
||||
{# type: "PUT",#}
|
||||
{# data: {#}
|
||||
{# 'is_active': status#}
|
||||
{# },#}
|
||||
{# success: function (data, status) {#}
|
||||
{# console.log(data)#}
|
||||
{# },#}
|
||||
{# error: function () {#}
|
||||
{# console.log('error')#}
|
||||
{# }#}
|
||||
{# })#}
|
||||
{# }#}
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
138
apps/assets/templates/assets/system_user_list.html
Normal file
138
apps/assets/templates/assets/system_user_list.html
Normal file
@@ -0,0 +1,138 @@
|
||||
{% extends '_base_list.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block table_search %}
|
||||
{% endblock %}
|
||||
|
||||
{% block table_container %}
|
||||
<div class="uc pull-left m-l-5 m-r-5">
|
||||
<a href="{% url 'assets:system-user-create' %}" class="btn btn-sm btn-primary "> {% trans "Create system user" %} </a>
|
||||
</div>
|
||||
<table class="table table-striped table-bordered table-hover " id="system_user_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 'Username' %}</th>
|
||||
<th class="text-center">{% trans 'Asset' %}</th>
|
||||
<th class="text-center">{% trans 'Unreachable' %}</th>
|
||||
<th class="text-center">{% trans 'Comment' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="actions" class="hide">
|
||||
<div class="input-group">
|
||||
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
|
||||
<option value="delete">{% trans 'Delete selected' %}</option>
|
||||
<option value="update">{% trans 'Update selected' %}</option>
|
||||
</select>
|
||||
<div class="input-group-btn pull-left" style="padding-left: 5px;">
|
||||
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
|
||||
{% trans 'Submit' %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
var options = {
|
||||
ele: $('#system_user_list_table'),
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url "assets:system-user-detail" pk=99991937 %}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('99991937', rowData.id));
|
||||
}},
|
||||
{targets: 5, createdCell: function (td, cellData) {
|
||||
var innerHtml = cellData.length > 30 ? cellData.substring(0, 30) + '...': cellData;
|
||||
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
|
||||
}},
|
||||
{targets: 6, createdCell: function (td, cellData, rowData) {
|
||||
{# var script_btn = '<a href="{% url "assets:system-user-update" pk=99991937 %}" class="btn btn-xs btn-primary">{% trans "Script" %}</a>'.replace('99991937', cellData);#}
|
||||
var update_btn = '<a href="{% url "assets:system-user-update" pk=99991937 %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', cellData);
|
||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="99991937">{% trans "Delete" %}</a>'.replace('99991937', cellData);
|
||||
$(td).html(update_btn + del_btn)
|
||||
}}],
|
||||
ajax_url: '{% url "api-assets:system-user-list" %}',
|
||||
columns: [{data: "id" }, {data: "name" }, {data: "username" }, {data: "assets_amount" }, {data: function () { return "3"}},
|
||||
{data: "comment" }, {data: "id" }],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
})
|
||||
|
||||
.on('click', '.btn_admin_user_delete', function () {
|
||||
var $this = $(this);
|
||||
var $data_table = $('#idc_list_table').DataTable();
|
||||
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
|
||||
var uid = $this.data('uid');
|
||||
var the_url = '{% url "api-assets:system-user-detail" pk=99991937 %}'.replace('99991937', uid);
|
||||
objectDelete($this, name, the_url);
|
||||
setTimeout( function () {
|
||||
$data_table.ajax.reload();
|
||||
}, 3000);
|
||||
})
|
||||
|
||||
.on('click', '#btn_bulk_update', function () {
|
||||
var action = $('#slct_bulk_update').val();
|
||||
var $data_table = $('#system_user_list_table').DataTable();
|
||||
var id_list = [];
|
||||
var plain_id_list = [];
|
||||
$data_table.rows({selected: true}).every(function(){
|
||||
id_list.push({id: this.data().id});
|
||||
plain_id_list.push(this.data().id);
|
||||
});
|
||||
if (id_list === []) {
|
||||
return false;
|
||||
}
|
||||
var the_url = "{% url 'api-assets:system-user-list' %}";
|
||||
function doDelete() {
|
||||
swal({
|
||||
title: "{% trans 'Are you sure?' %}",
|
||||
text: "{% trans 'This will delete the selected System Users !!!' %}",
|
||||
type: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#DD6B55",
|
||||
confirmButtonText: "{% trans 'Confirm' %}",
|
||||
closeOnConfirm: false
|
||||
}, function() {
|
||||
var success = function() {
|
||||
var msg = "{% trans 'System Users Deleted.' %}";
|
||||
swal("{% trans 'System Users Delete' %}", msg, "success");
|
||||
$('#system_user_list_table').DataTable().ajax.reload();
|
||||
};
|
||||
var fail = function() {
|
||||
var msg = "{% trans 'System Users Deleting failed.' %}";
|
||||
swal("{% trans 'System Users Delete' %}", msg, "error");
|
||||
};
|
||||
var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list);
|
||||
APIUpdateAttr({url: url_delete, method: 'DELETE', success: success, error: fail});
|
||||
$data_table.ajax.reload();
|
||||
jumpserver.checked = false;
|
||||
});
|
||||
}
|
||||
function doUpdate() {
|
||||
{# TODO: bulk update the System Users #}
|
||||
}
|
||||
switch (action) {
|
||||
case 'delete':
|
||||
doDelete();
|
||||
break;
|
||||
case 'update':
|
||||
doUpdate();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
53
apps/assets/templates/assets/system_user_update.html
Normal file
53
apps/assets/templates/assets/system_user_update.html
Normal file
@@ -0,0 +1,53 @@
|
||||
{% extends 'assets/_system_user.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load bootstrap3 %}
|
||||
|
||||
{% block auth %}
|
||||
<div class="password-auth hidden">
|
||||
{% bootstrap_field form.password layout="horizontal" %}
|
||||
</div>
|
||||
<div class="public-key-auth">
|
||||
<div>
|
||||
{% bootstrap_field form.private_key_file layout="horizontal" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
var auth_method = '#'+'{{ form.auth_method.id_for_label }}';
|
||||
var auto_generate_key = '#'+'{{ form.auto_generate_key.id_for_label }}';
|
||||
function authMethodDisplay() {
|
||||
if ($(auth_method).val() == 'P') {
|
||||
$('.password-auth').removeClass('hidden');
|
||||
$('.public-key-auth').addClass('hidden');
|
||||
$('#'+'{{ form.password.id_for_label }}').removeAttr('disabled');
|
||||
|
||||
} else if ($(auth_method).val() == 'K') {
|
||||
$('.password-auth').addClass('hidden');
|
||||
$('.public-key-auth').removeClass('hidden');
|
||||
$('#'+'{{ form.password.id_for_label }}').removeAttr('required');
|
||||
$('#'+'{{ form.password.id_for_label }}').attr('disabled', 'disabled');
|
||||
|
||||
if ($(auto_generate_key).prop('checked')){
|
||||
$('#'+'{{ form.private_key_file.id_for_label }}').closest('.form-group').addClass('hidden');
|
||||
} else {
|
||||
$('#'+'{{ form.private_key_file.id_for_label }}').closest('.form-group').removeClass('hidden');
|
||||
}
|
||||
}
|
||||
}
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
authMethodDisplay();
|
||||
$(auth_method).change(function () {
|
||||
authMethodDisplay();
|
||||
});
|
||||
$(auto_generate_key).change(function () {
|
||||
authMethodDisplay();
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
264
apps/assets/templates/assets/user_asset_list.html
Normal file
264
apps/assets/templates/assets/user_asset_list.html
Normal file
@@ -0,0 +1,264 @@
|
||||
{% extends '_base_list.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% 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>
|
||||
|
||||
<style>
|
||||
.custom{
|
||||
margin-right:5px;
|
||||
}
|
||||
#modal .modal-body { max-height: 200px; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block content_left_head %}{% endblock %}
|
||||
|
||||
{% block table_search %}
|
||||
{# <div class="html5buttons">#}
|
||||
{# <div class="dt-buttons btn-group">#}
|
||||
{# <a class="btn btn-default btn_export" tabindex="0">#}
|
||||
{# <span>{% trans "Export" %}</span>#}
|
||||
{# </a>#}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block table_container %}
|
||||
<table class="table table-striped table-bordered table-hover " id="asset_list_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
|
||||
<th class="text-center">{% trans 'Hostname' %}</th>
|
||||
<th class="text-center">{% trans 'IP' %}</th>
|
||||
<th class="text-center">{% trans 'Port' %}</th>
|
||||
<th class="text-center">{% trans 'Type' %}</th>
|
||||
<th class="text-center">{% trans 'Env' %}</th>
|
||||
<th class="text-center">{% trans 'Hardware' %}</th>
|
||||
<th class="text-center">{% trans 'Valid' %}</th>
|
||||
<th class="text-center">{% trans 'Alive' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function(){
|
||||
var options = {
|
||||
ele: $('#asset_list_table'),
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url "assets:asset-detail" pk=99991937 %}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('99991937', rowData.id));
|
||||
}},
|
||||
{targets: 7, createdCell: function (td, cellData) {
|
||||
if (!cellData) {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 8, createdCell: function (td, cellData) {
|
||||
if (!cellData) {
|
||||
$(td).html('<i class="fa fa-circle text-danger"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-circle text-navy"></i>')
|
||||
}
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-assets:asset-list" %}',
|
||||
columns: [{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" },
|
||||
{data: "get_type_display" }, {data: "get_env_display"}, {data: "hardware"},
|
||||
{data: "is_active" }, {data: "is_active"}],
|
||||
};
|
||||
var table = jumpserver.initDataTable(options);
|
||||
$('.btn_export').click(function () {
|
||||
var assets = [];
|
||||
var rows = table.rows('.selected').data();
|
||||
$.each(rows, function (index, obj) {
|
||||
assets.push(obj.id)
|
||||
});
|
||||
console.log(assets);
|
||||
$.ajax({
|
||||
url: "{% url "assets:asset-export" %}",
|
||||
method: 'POST',
|
||||
data: JSON.stringify({assets_id: assets}),
|
||||
dataType: "json",
|
||||
success: function (data, textStatus) {
|
||||
window.open(data.redirect)
|
||||
},
|
||||
error: function () {
|
||||
toastr.error('Export failed');
|
||||
}
|
||||
})
|
||||
});
|
||||
$('#btn_asset_import').click(function() {
|
||||
var $form = $('#fm_asset_import');
|
||||
$form.find('.help-block').remove();
|
||||
function success (data) {
|
||||
if (data.valid === false) {
|
||||
$('<span />', {class: 'help-block text-danger'}).html(data.msg).insertAfter($('#id_assets'));
|
||||
} else {
|
||||
$('#id_created').html(data.created_info);
|
||||
$('#id_created_detail').html(data.created.join(', '));
|
||||
$('#id_updated').html(data.updated_info);
|
||||
$('#id_updated_detail').html(data.updated.join(', '));
|
||||
$('#id_failed').html(data.failed_info);
|
||||
$('#id_failed_detail').html(data.failed.join(', '));
|
||||
var $data_table = $('#asset_list_table').DataTable();
|
||||
$data_table.ajax.reload();
|
||||
}
|
||||
}
|
||||
$form.ajaxSubmit({success: success});
|
||||
})
|
||||
})
|
||||
|
||||
.on('click', '.btn_asset_delete', function () {
|
||||
var $this = $(this);
|
||||
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
|
||||
var uid = $this.data('uid');
|
||||
var the_url = '{% url "api-assets:asset-detail" pk=99991937 %}'.replace('99991937', uid);
|
||||
objectDelete($this, name, the_url);
|
||||
})
|
||||
|
||||
.on('click', '#btn_bulk_update', function () {
|
||||
var action = $('#slct_bulk_update').val();
|
||||
var $data_table = $('#asset_list_table').DataTable();
|
||||
var id_list = [];
|
||||
var plain_id_list = [];
|
||||
$data_table.rows({selected: true}).every(function(){
|
||||
id_list.push({id: this.data().id});
|
||||
plain_id_list.push(this.data().id);
|
||||
});
|
||||
if (plain_id_list.length == 0) {
|
||||
return false;
|
||||
}
|
||||
var the_url = "{% url 'api-assets:asset-list' %}";
|
||||
function doDeactive() {
|
||||
var body = $.each(id_list, function(index, asset_object) {
|
||||
asset_object['is_active'] = false;
|
||||
});
|
||||
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(body)});
|
||||
$data_table.ajax.reload();
|
||||
jumpserver.checked = false;
|
||||
}
|
||||
function doActive() {
|
||||
var body = $.each(id_list, function(index, asset_object) {
|
||||
asset_object['is_active'] = true;
|
||||
});
|
||||
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(body)});
|
||||
$data_table.ajax.reload();
|
||||
jumpserver.checked = false;
|
||||
}
|
||||
function doDelete() {
|
||||
swal({
|
||||
title: "{% trans 'Are you sure?' %}",
|
||||
text: "{% trans 'This will delete the selected assets !!!' %}",
|
||||
type: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#DD6B55",
|
||||
confirmButtonText: "{% trans 'Confirm' %}",
|
||||
closeOnConfirm: false
|
||||
}, function() {
|
||||
var success = function() {
|
||||
var msg = "{% trans 'Asset Deleted.' %}";
|
||||
swal("{% trans 'Asset Delete' %}", msg, "success");
|
||||
$('#asset_list_table').DataTable().ajax.reload();
|
||||
};
|
||||
var fail = function() {
|
||||
var msg = "{% trans 'Asset Deleting failed.' %}";
|
||||
swal("{% trans 'Asset Delete' %}", msg, "error");
|
||||
};
|
||||
var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list);
|
||||
APIUpdateAttr({url: url_delete, method: 'DELETE', success: success, error: fail});
|
||||
$data_table.ajax.reload();
|
||||
jumpserver.checked = false;
|
||||
});
|
||||
}
|
||||
function doUpdate() {
|
||||
$('#asset_bulk_update_modal').modal('show');
|
||||
}
|
||||
switch(action) {
|
||||
case 'deactive':
|
||||
doDeactive();
|
||||
break;
|
||||
case 'delete':
|
||||
doDelete();
|
||||
break;
|
||||
case 'update':
|
||||
doUpdate();
|
||||
break;
|
||||
case 'active':
|
||||
doActive();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
})
|
||||
|
||||
.on('click', '#btn_asset_bulk_update', function () {
|
||||
var json_data = $("#fm_asset_bulk_update").serializeObject();
|
||||
var body = {};
|
||||
body.enable_otp = (json_data.enable_otp === 'on')? true: false;
|
||||
if (json_data.type != '') {
|
||||
body.type = json_data.type;
|
||||
}
|
||||
if (json_data.groups != undefined) {
|
||||
body.groups = json_data.groups;
|
||||
}
|
||||
if (typeof body.groups === 'string') {
|
||||
body.groups = [parseInt(body.groups)]
|
||||
} else if(typeof body.groups === 'array') {
|
||||
var new_groups = body.groups.map(Number);
|
||||
body.groups = new_groups;
|
||||
}
|
||||
|
||||
if (json_data.system_users != undefined) {
|
||||
body.system_users = json_data.system_users;
|
||||
}
|
||||
if (typeof body.system_users === 'string') {
|
||||
body.system_users = [parseInt(body.system_users)]
|
||||
} else if(typeof body.system_users === 'array') {
|
||||
var new_users = body.system_users.map(Number);
|
||||
body.system_users = new_users;
|
||||
}
|
||||
|
||||
if (json_data.tags != undefined) {
|
||||
body.tags = json_data.tags;
|
||||
}
|
||||
if (typeof body.tags == 'string') {
|
||||
body.tags = [parseInt(body.tags)];
|
||||
} else if (typeof body.tags === 'array') {
|
||||
var new_tags = body.tags.map(Number);
|
||||
body.tags = new_tags;
|
||||
}
|
||||
|
||||
var $data_table = $('#asset_list_table').DataTable();
|
||||
var post_list = [];
|
||||
$data_table.rows({selected: true}).every(function(){
|
||||
var content = Object.assign({id: this.data().id}, body);
|
||||
post_list.push(content);
|
||||
});
|
||||
if (post_list === []) {
|
||||
return false
|
||||
}
|
||||
var the_url = "{% url 'api-assets:asset-list' %}";
|
||||
var success = function() {
|
||||
var msg = "{% trans 'The selected assets has been updated successfully.' %}";
|
||||
swal("{% trans 'Asset Updated' %}", msg, "success");
|
||||
$('#asset_list_table').DataTable().ajax.reload();
|
||||
jumpserver.checked = false;
|
||||
};
|
||||
console.log(JSON.stringify(post_list));
|
||||
console.log(the_url);
|
||||
{# APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(post_list), success: success});#}
|
||||
$('#asset_bulk_update_modal').modal('hide');
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
6
apps/assets/templatetags/asset_tags.py
Normal file
6
apps/assets/templatetags/asset_tags.py
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
from django import template
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
register = template.Library()
|
||||
|
||||
1
apps/assets/urls/__init__.py
Normal file
1
apps/assets/urls/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
57
apps/assets/urls/api_urls.py
Normal file
57
apps/assets/urls/api_urls.py
Normal file
@@ -0,0 +1,57 @@
|
||||
# coding:utf-8
|
||||
from django.conf.urls import url
|
||||
from .. import api
|
||||
from rest_framework import routers
|
||||
from rest_framework_bulk.routes import BulkRouter
|
||||
|
||||
app_name = 'assets'
|
||||
|
||||
|
||||
router = BulkRouter()
|
||||
router.register(r'v1/groups', api.AssetGroupViewSet, 'asset-group')
|
||||
router.register(r'v1/assets', api.AssetViewSet, 'asset')
|
||||
router.register(r'v1/idc', api.IDCViewSet, 'idc')
|
||||
router.register(r'v1/admin-user', api.AdminUserViewSet, 'admin-user')
|
||||
router.register(r'v1/system-user', api.SystemUserViewSet, 'system-user')
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^v1/assets-bulk/$', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'),
|
||||
url(r'^v1/system-user/(?P<pk>[0-9]+)/auth-info/', api.SystemUserAuthInfoApi.as_view(),
|
||||
name='system-user-auth-info'),
|
||||
url(r'^v1/assets/(?P<pk>\d+)/groups/$',
|
||||
api.AssetUpdateGroupApi.as_view(), name='asset-update-group'),
|
||||
|
||||
url(r'^v1/assets/(?P<pk>\d+)/refresh/$',
|
||||
api.AssetRefreshHardwareView.as_view(), name='asset-refresh'),
|
||||
url(r'^v1/assets/(?P<pk>\d+)/admin-user-test/$',
|
||||
api.AssetAdminUserTestView.as_view(), name='asset-admin-user-test'),
|
||||
|
||||
url(r'^v1/assets/(?P<pk>\d+)/system-users/$',
|
||||
api.SystemUserUpdateApi.as_view(), name='asset-update-system-users'),
|
||||
|
||||
url(r'^v1/groups/(?P<pk>\d+)/push-system-user/$',
|
||||
api.AssetGroupPushSystemUserView.as_view(), name='asset-group-push-system-user'),
|
||||
|
||||
# update the system users, which add and delete the asset to the system user
|
||||
url(r'^v1/system-user/(?P<pk>\d+)/assets/$',
|
||||
api.SystemUserUpdateAssetsApi.as_view(), name='systemuser-update-assets'),
|
||||
|
||||
url(r'^v1/system-user/(?P<pk>\d+)/groups/$',
|
||||
api.SystemUserUpdateAssetGroupApi.as_view(), name='systemuser-update-assetgroups'),
|
||||
|
||||
# update the asset group, which add or delete the asset to the group
|
||||
url(r'^v1/groups/(?P<pk>\d+)/assets/$',
|
||||
api.AssetGroupUpdateApi.as_view(), name='asset-groups-update'),
|
||||
|
||||
# update the asset group, and add or delete the system_user to the group
|
||||
url(r'^v1/groups/(?P<pk>\d+)/system-users/$',
|
||||
api.AssetGroupUpdateSystemUserApi.as_view(), name='asset-groups-update-systemusers'),
|
||||
|
||||
# update the IDC, and add or delete the assets to the IDC
|
||||
url(r'^v1/idc/(?P<pk>\d+)/assets/$',
|
||||
api.IDCUpdateAssetsApi.as_view(), name='idc-update-assets'),
|
||||
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
||||
|
||||
56
apps/assets/urls/views_urls.py
Normal file
56
apps/assets/urls/views_urls.py
Normal file
@@ -0,0 +1,56 @@
|
||||
# coding:utf-8
|
||||
from django.conf.urls import url
|
||||
from .. import views
|
||||
|
||||
app_name = 'assets'
|
||||
|
||||
urlpatterns = [
|
||||
# Resource asset url
|
||||
url(r'^$', views.AssetListView.as_view(), name='asset-index'),
|
||||
url(r'^asset/$', views.AssetListView.as_view(), name='asset-list'),
|
||||
url(r'^asset/create/$', views.AssetCreateView.as_view(), name='asset-create'),
|
||||
url(r'^asset/export/$', views.AssetExportView.as_view(), name='asset-export'),
|
||||
url(r'^asset/import/$', views.BulkImportAssetView.as_view(), name='asset-import'),
|
||||
url(r'^asset/(?P<pk>[0-9]+)/$', views.AssetDetailView.as_view(), name='asset-detail'),
|
||||
url(r'^asset/(?P<pk>[0-9]+)/update/$', views.AssetUpdateView.as_view(), name='asset-update'),
|
||||
url(r'^asset/(?P<pk>[0-9]+)/delete/$', views.AssetDeleteView.as_view(), name='asset-delete'),
|
||||
url(r'^asset-modal$', views.AssetModalListView.as_view(), name='asset-modal-list'),
|
||||
url(r'^asset/update/$', views.AssetBulkUpdateView.as_view(), name='asset-bulk-update'),
|
||||
|
||||
# User asset view
|
||||
url(r'^user-asset/$', views.UserAssetListView.as_view(), name='user-asset-list'),
|
||||
|
||||
# Resource asset group url
|
||||
url(r'^asset-group/$', views.AssetGroupListView.as_view(), name='asset-group-list'),
|
||||
url(r'^asset-group/create/$', views.AssetGroupCreateView.as_view(), name='asset-group-create'),
|
||||
url(r'^asset-group/(?P<pk>[0-9]+)/$', views.AssetGroupDetailView.as_view(), name='asset-group-detail'),
|
||||
url(r'^asset-group/(?P<pk>[0-9]+)/update/$', views.AssetGroupUpdateView.as_view(), name='asset-group-update'),
|
||||
url(r'^asset-group/(?P<pk>[0-9]+)/delete/$', views.AssetGroupDeleteView.as_view(), name='asset-group-delete'),
|
||||
|
||||
# Resource idc url
|
||||
url(r'^idc/$', views.IDCListView.as_view(), name='idc-list'),
|
||||
url(r'^idc/create/$', views.IDCCreateView.as_view(), name='idc-create'),
|
||||
url(r'^idc/(?P<pk>[0-9]+)/$', views.IDCDetailView.as_view(), name='idc-detail'),
|
||||
url(r'^idc/(?P<pk>[0-9]+)/update/', views.IDCUpdateView.as_view(), name='idc-update'),
|
||||
url(r'^idc/(?P<pk>[0-9]+)/delete/$', views.IDCDeleteView.as_view(), name='idc-delete'),
|
||||
url(r'^idc/(?P<pk>[0-9]+)/assets/$', views.IDCAssetsView.as_view(), name='idc-assets'),
|
||||
|
||||
# Resource admin user url
|
||||
url(r'^admin-user/$', views.AdminUserListView.as_view(), name='admin-user-list'),
|
||||
url(r'^admin-user/create/$', views.AdminUserCreateView.as_view(), name='admin-user-create'),
|
||||
url(r'^admin-user/(?P<pk>[0-9]+)/$', views.AdminUserDetailView.as_view(), name='admin-user-detail'),
|
||||
url(r'^admin-user/(?P<pk>[0-9]+)/update/$', views.AdminUserUpdateView.as_view(), name='admin-user-update'),
|
||||
url(r'^admin-user/(?P<pk>[0-9]+)/delete/$', views.AdminUserDeleteView.as_view(), name='admin-user-delete'),
|
||||
|
||||
# Resource system user url
|
||||
url(r'^system-user/$', views.SystemUserListView.as_view(), name='system-user-list'),
|
||||
url(r'^system-user/create/$', views.SystemUserCreateView.as_view(), name='system-user-create'),
|
||||
url(r'^system-user/(?P<pk>[0-9]+)/$', views.SystemUserDetailView.as_view(), name='system-user-detail'),
|
||||
url(r'^system-user/(?P<pk>[0-9]+)/update/$', views.SystemUserUpdateView.as_view(), name='system-user-update'),
|
||||
url(r'^system-user/(?P<pk>[0-9]+)/delete/$', views.SystemUserDeleteView.as_view(), name='system-user-delete'),
|
||||
url(r'^system-user/(?P<pk>[0-9]+)/asset/$', views.SystemUserAssetView.as_view(), name='system-user-asset'),
|
||||
# url(r'^system-user/(?P<pk>[0-9]+)/asset-group$', views.SystemUserAssetGroupView.as_view(),
|
||||
# name='system-user-asset-group'),
|
||||
|
||||
]
|
||||
|
||||
17
apps/assets/utils.py
Normal file
17
apps/assets/utils.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
#
|
||||
from ops.utils import run_AdHoc
|
||||
|
||||
|
||||
def test_admin_user_connective_manual(asset):
|
||||
if not isinstance(asset, list):
|
||||
asset = [asset]
|
||||
task_tuple = (
|
||||
('ping', ''),
|
||||
)
|
||||
summary, _ = run_AdHoc(task_tuple, asset, record=False)
|
||||
if len(summary['failed']) != 0:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
7
apps/assets/views/__init__.py
Normal file
7
apps/assets/views/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
# coding:utf-8
|
||||
from .asset import *
|
||||
from .group import *
|
||||
from .idc import *
|
||||
from .system_user import *
|
||||
from .admin_user import *
|
||||
|
||||
112
apps/assets/views/admin_user.py
Normal file
112
apps/assets/views/admin_user.py
Normal file
@@ -0,0 +1,112 @@
|
||||
# coding:utf-8
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.conf import settings
|
||||
from django.views.generic import TemplateView, ListView, View
|
||||
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
|
||||
from django.urls import reverse_lazy
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.views.generic.detail import DetailView, SingleObjectMixin
|
||||
|
||||
from .. import forms
|
||||
from ..models import Asset, AssetGroup, AdminUser, IDC, SystemUser
|
||||
from ..hands import AdminUserRequiredMixin
|
||||
|
||||
__all__ = ['AdminUserCreateView', 'AdminUserDetailView',
|
||||
'AdminUserDeleteView', 'AdminUserListView',
|
||||
'AdminUserUpdateView',
|
||||
]
|
||||
|
||||
|
||||
class AdminUserListView(AdminUserRequiredMixin, TemplateView):
|
||||
model = AdminUser
|
||||
template_name = 'assets/admin_user_list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Admin user list'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(AdminUserListView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AdminUserCreateView(AdminUserRequiredMixin,
|
||||
SuccessMessageMixin,
|
||||
CreateView):
|
||||
model = AdminUser
|
||||
form_class = forms.AdminUserForm
|
||||
template_name = 'assets/admin_user_create_update.html'
|
||||
success_url = reverse_lazy('assets:admin-user-list')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': 'assets',
|
||||
'action': 'Create admin user'
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(AdminUserCreateView, self).get_context_data(**kwargs)
|
||||
|
||||
def get_success_message(self, cleaned_data):
|
||||
success_message = _(
|
||||
'Create admin user <a href="{url}">{name}</a> successfully.'.format(
|
||||
url=reverse_lazy('assets:admin-user-detail',
|
||||
kwargs={'pk': self.object.pk}),
|
||||
name=self.object.name,
|
||||
))
|
||||
return success_message
|
||||
|
||||
def form_invalid(self, form):
|
||||
return super(AdminUserCreateView, self).form_invalid(form)
|
||||
|
||||
|
||||
class AdminUserUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
model = AdminUser
|
||||
form_class = forms.AdminUserForm
|
||||
template_name = 'assets/admin_user_create_update.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': 'assets',
|
||||
'action': 'Update admin user'
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(AdminUserUpdateView, self).get_context_data(**kwargs)
|
||||
|
||||
def get_success_url(self):
|
||||
success_url = reverse_lazy('assets:admin-user-detail',
|
||||
kwargs={'pk': self.object.pk})
|
||||
return success_url
|
||||
|
||||
|
||||
class AdminUserDetailView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
|
||||
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
|
||||
template_name = 'assets/admin_user_detail.html'
|
||||
context_object_name = 'admin_user'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(queryset=AdminUser.objects.all())
|
||||
return super(AdminUserDetailView, self).get(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
return self.object.assets.all()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
asset_groups = AssetGroup.objects.all()
|
||||
assets = self.get_queryset()
|
||||
context = {
|
||||
'app': 'assets',
|
||||
'action': 'Admin user detail',
|
||||
'assets_remain': [asset for asset in Asset.objects.all() if asset not in assets],
|
||||
'asset_groups': asset_groups,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(AdminUserDetailView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AdminUserDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||
model = AdminUser
|
||||
template_name = 'assets/delete_confirm.html'
|
||||
success_url = reverse_lazy('assets:admin-user-list')
|
||||
|
||||
|
||||
345
apps/assets/views/asset.py
Normal file
345
apps/assets/views/asset.py
Normal file
@@ -0,0 +1,345 @@
|
||||
# coding:utf-8
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import csv
|
||||
import json
|
||||
import uuid
|
||||
import codecs
|
||||
import chardet
|
||||
from io import StringIO
|
||||
from collections import defaultdict
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import TemplateView, ListView, View
|
||||
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic.detail import DetailView, SingleObjectMixin
|
||||
from django.http import HttpResponse, JsonResponse, HttpResponseRedirect
|
||||
from django.views.decorators.csrf import csrf_protect, csrf_exempt
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.core.cache import cache
|
||||
from django.utils import timezone
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.shortcuts import get_object_or_404, redirect, reverse
|
||||
|
||||
from common.mixins import JSONResponseMixin
|
||||
from common.utils import get_object_or_none
|
||||
from .. import forms
|
||||
from ..models import Asset, AssetGroup, AdminUser, IDC, SystemUser
|
||||
from ..hands import AdminUserRequiredMixin
|
||||
from ..tasks import update_assets_hardware_info
|
||||
|
||||
|
||||
__all__ = ['AssetListView', 'AssetCreateView', 'AssetUpdateView',
|
||||
'UserAssetListView', 'AssetBulkUpdateView', 'AssetDetailView',
|
||||
'AssetModalListView', 'AssetDeleteView', 'AssetExportView',
|
||||
'BulkImportAssetView',
|
||||
]
|
||||
|
||||
|
||||
class AssetListView(AdminUserRequiredMixin, TemplateView):
|
||||
template_name = 'assets/asset_list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': 'Assets',
|
||||
'action': 'Asset list',
|
||||
'groups': AssetGroup.objects.all(),
|
||||
'system_users': SystemUser.objects.all(),
|
||||
# 'form': forms.AssetBulkUpdateForm(),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(AssetListView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class UserAssetListView(LoginRequiredMixin, TemplateView):
|
||||
template_name = 'assets/user_asset_list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': 'Assets',
|
||||
'action': 'Asset list',
|
||||
'system_users': SystemUser.objects.all(),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(UserAssetListView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AssetCreateView(AdminUserRequiredMixin, CreateView):
|
||||
model = Asset
|
||||
form_class = forms.AssetCreateForm
|
||||
template_name = 'assets/asset_create.html'
|
||||
success_url = reverse_lazy('assets:asset-list')
|
||||
|
||||
def form_valid(self, form):
|
||||
self.asset = asset = form.save()
|
||||
asset.created_by = self.request.user.username or 'Admin'
|
||||
asset.date_created = timezone.now()
|
||||
asset.save()
|
||||
return super(AssetCreateView, self).form_valid(form)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': 'Assets',
|
||||
'action': 'Create asset',
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(AssetCreateView, self).get_context_data(**kwargs)
|
||||
|
||||
def get_success_url(self):
|
||||
update_assets_hardware_info.delay([self.asset._to_secret_json()])
|
||||
return super(AssetCreateView, self).get_success_url()
|
||||
|
||||
|
||||
class AssetModalListView(AdminUserRequiredMixin, ListView):
|
||||
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
|
||||
model = Asset
|
||||
context_object_name = 'asset_modal_list'
|
||||
template_name = 'assets/asset_modal_list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
assets = Asset.objects.all()
|
||||
assets_id = self.request.GET.get('assets_id', '')
|
||||
assets_id_list = [i for i in assets_id.split(',') if i.isdigit()]
|
||||
context = {
|
||||
'all_assets': assets_id_list,
|
||||
'assets': assets
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(AssetModalListView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AssetBulkUpdateView(AdminUserRequiredMixin, ListView):
|
||||
model = Asset
|
||||
form_class = forms.AssetBulkUpdateForm
|
||||
template_name = 'assets/asset_bulk_update.html'
|
||||
success_url = reverse_lazy('assets:asset-list')
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
assets_id = self.request.GET.get('assets_id', '')
|
||||
self.assets_id_list = [int(i) for i in assets_id.split(',') if i.isdigit()]
|
||||
|
||||
if kwargs.get('form'):
|
||||
self.form = kwargs['form']
|
||||
elif assets_id:
|
||||
self.form = self.form_class(
|
||||
initial={'assets': self.assets_id_list}
|
||||
)
|
||||
else:
|
||||
self.form = self.form_class()
|
||||
return super(AssetBulkUpdateView, self).get(request, *args, **kwargs)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
form = self.form_class(request.POST)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
return redirect(self.success_url)
|
||||
else:
|
||||
return self.get(request, form=form, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
# assets_list = Asset.objects.filter(id__in=self.assets_id_list)
|
||||
context = {
|
||||
'app': 'Assets',
|
||||
'action': 'Bulk update asset',
|
||||
'form': self.form,
|
||||
'assets_selected': self.assets_id_list,
|
||||
'assets': Asset.objects.all(),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(AssetBulkUpdateView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AssetUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
model = Asset
|
||||
form_class = forms.AssetUpdateForm
|
||||
template_name = 'assets/asset_update.html'
|
||||
success_url = reverse_lazy('assets:asset-list')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': 'Assets',
|
||||
'action': 'Update asset',
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(AssetUpdateView, self).get_context_data(**kwargs)
|
||||
|
||||
def form_invalid(self, form):
|
||||
print(form.errors)
|
||||
return super(AssetUpdateView, self).form_invalid(form)
|
||||
|
||||
|
||||
class AssetDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||
model = Asset
|
||||
template_name = 'assets/delete_confirm.html'
|
||||
success_url = reverse_lazy('assets:asset-list')
|
||||
|
||||
|
||||
class AssetDetailView(DetailView):
|
||||
model = Asset
|
||||
context_object_name = 'asset'
|
||||
template_name = 'assets/asset_detail.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
asset_groups = self.object.groups.all()
|
||||
system_users = self.object.system_users.all()
|
||||
context = {
|
||||
'app': 'Assets',
|
||||
'action': 'Asset detail',
|
||||
'asset_groups_remain': [asset_group for asset_group in AssetGroup.objects.all()
|
||||
if asset_group not in asset_groups],
|
||||
'asset_groups': asset_groups,
|
||||
'system_users_all': SystemUser.objects.all(),
|
||||
'system_users': system_users,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(AssetDetailView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class AssetExportView(View):
|
||||
def get(self, request):
|
||||
spm = request.GET.get('spm', '')
|
||||
assets_id_default = [Asset.objects.first().id] if Asset.objects.first() else [1]
|
||||
assets_id = cache.get(spm, assets_id_default)
|
||||
fields = [
|
||||
field for field in Asset._meta.fields
|
||||
if field.name not in [
|
||||
'date_created'
|
||||
]
|
||||
]
|
||||
filename = 'assets-{}.csv'.format(
|
||||
timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H-%M-%S'))
|
||||
response = HttpResponse(content_type='text/csv')
|
||||
response['Content-Disposition'] = 'attachment; filename="%s"' % filename
|
||||
response.write(codecs.BOM_UTF8)
|
||||
assets = Asset.objects.filter(id__in=assets_id)
|
||||
writer = csv.writer(response, dialect='excel',
|
||||
quoting=csv.QUOTE_MINIMAL)
|
||||
|
||||
header = [field.verbose_name for field in fields]
|
||||
header.append(_('Asset groups'))
|
||||
writer.writerow(header)
|
||||
|
||||
for asset in assets:
|
||||
groups = ','.join([group.name for group in asset.groups.all()])
|
||||
data = [getattr(asset, field.name) for field in fields]
|
||||
data.append(groups)
|
||||
writer.writerow(data)
|
||||
return response
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
try:
|
||||
assets_id = json.loads(request.body).get('assets_id', [])
|
||||
except ValueError:
|
||||
return HttpResponse('Json object not valid', status=400)
|
||||
spm = uuid.uuid4().hex
|
||||
cache.set(spm, assets_id, 300)
|
||||
url = reverse_lazy('assets:asset-export') + '?spm=%s' % spm
|
||||
return JsonResponse({'redirect': url})
|
||||
|
||||
|
||||
class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
|
||||
form_class = forms.FileForm
|
||||
|
||||
def form_valid(self, form):
|
||||
f = form.cleaned_data['file']
|
||||
det_result = chardet.detect(f.read())
|
||||
f.seek(0) # reset file seek index
|
||||
file_data = f.read().decode(det_result['encoding']).strip(codecs.BOM_UTF8.decode())
|
||||
csv_file = StringIO(file_data)
|
||||
reader = csv.reader(csv_file)
|
||||
csv_data = [row for row in reader]
|
||||
fields = [
|
||||
field for field in Asset._meta.fields
|
||||
if field.name not in [
|
||||
'date_created'
|
||||
]
|
||||
]
|
||||
header_ = csv_data[0]
|
||||
mapping_reverse = {field.verbose_name: field.name for field in fields}
|
||||
mapping_reverse[_('Asset groups')] = 'groups'
|
||||
attr = [mapping_reverse.get(n, None) for n in header_]
|
||||
if None in attr:
|
||||
data = {'valid': False,
|
||||
'msg': 'Must be same format as '
|
||||
'template or export file'}
|
||||
return self.render_json_response(data)
|
||||
|
||||
created, updated, failed = [], [], []
|
||||
assets = []
|
||||
for row in csv_data[1:]:
|
||||
if set(row) == {''}:
|
||||
continue
|
||||
|
||||
asset_dict = dict(zip(attr, row))
|
||||
id_ = asset_dict.pop('id', 0)
|
||||
|
||||
try:
|
||||
id_ = int(id_)
|
||||
except ValueError:
|
||||
id_ = 0
|
||||
|
||||
asset = get_object_or_none(Asset, id=id_)
|
||||
for k, v in asset_dict.items():
|
||||
if k == 'idc':
|
||||
v = get_object_or_none(IDC, name=v)
|
||||
elif k == 'is_active':
|
||||
v = bool(v)
|
||||
elif k == 'admin_user':
|
||||
v = get_object_or_none(AdminUser, name=v)
|
||||
elif k in ['port', 'cabinet_pos', 'cpu_count', 'cpu_cores']:
|
||||
try:
|
||||
v = int(v)
|
||||
except ValueError:
|
||||
v = 0
|
||||
elif k == 'groups':
|
||||
groups_name = v.split(',')
|
||||
v = AssetGroup.objects.filter(name__in=groups_name)
|
||||
else:
|
||||
continue
|
||||
asset_dict[k] = v
|
||||
|
||||
if not asset:
|
||||
try:
|
||||
groups = asset_dict.pop('groups')
|
||||
if len(Asset.objects.filter(hostname=asset_dict.get('hostname'))):
|
||||
raise Exception(_('already exists'))
|
||||
asset = Asset.objects.create(**asset_dict)
|
||||
asset.groups.set(groups)
|
||||
created.append(asset_dict['hostname'])
|
||||
assets.append(asset)
|
||||
except Exception as e:
|
||||
failed.append('%s: %s' % (asset_dict['hostname'], str(e)))
|
||||
else:
|
||||
for k, v in asset_dict.items():
|
||||
if k == 'groups':
|
||||
asset.groups.set(v)
|
||||
continue
|
||||
if v:
|
||||
setattr(asset, k, v)
|
||||
try:
|
||||
asset.save()
|
||||
updated.append(asset_dict['hostname'])
|
||||
except Exception as e:
|
||||
failed.append('%s: %s' % (asset_dict['hostname'], str(e)))
|
||||
|
||||
if assets:
|
||||
update_assets_hardware_info.delay([asset._to_secret_json() for asset in assets])
|
||||
|
||||
|
||||
data = {
|
||||
'created': created,
|
||||
'created_info': 'Created {}'.format(len(created)),
|
||||
'updated': updated,
|
||||
'updated_info': 'Updated {}'.format(len(updated)),
|
||||
'failed': failed,
|
||||
'failed_info': 'Failed {}'.format(len(failed)),
|
||||
'valid': True,
|
||||
'msg': 'Created: {}. Updated: {}, Error: {}'.format(
|
||||
len(created), len(updated), len(failed))
|
||||
}
|
||||
return self.render_json_response(data)
|
||||
|
||||
|
||||
111
apps/assets/views/group.py
Normal file
111
apps/assets/views/group.py
Normal file
@@ -0,0 +1,111 @@
|
||||
# coding:utf-8
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic import TemplateView, ListView, View
|
||||
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic.detail import DetailView, SingleObjectMixin
|
||||
from django.shortcuts import get_object_or_404, reverse, redirect
|
||||
|
||||
from .. import forms
|
||||
from ..models import Asset, AssetGroup, AdminUser, IDC, SystemUser
|
||||
from ..hands import AdminUserRequiredMixin
|
||||
|
||||
|
||||
__all__ = ['AssetGroupCreateView', 'AssetGroupDetailView',
|
||||
'AssetGroupUpdateView', 'AssetGroupListView',
|
||||
'AssetGroupDeleteView',
|
||||
]
|
||||
|
||||
|
||||
class AssetGroupCreateView(AdminUserRequiredMixin, CreateView):
|
||||
model = AssetGroup
|
||||
form_class = forms.AssetGroupForm
|
||||
template_name = 'assets/asset_group_create.html'
|
||||
success_url = reverse_lazy('assets:asset-group-list')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Create asset group'),
|
||||
'assets_count': 0,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(AssetGroupCreateView, self).get_context_data(**kwargs)
|
||||
|
||||
def form_valid(self, form):
|
||||
asset_group = form.save()
|
||||
assets_id_list = self.request.POST.getlist('assets', [])
|
||||
assets = [get_object_or_404(Asset, id=int(asset_id))
|
||||
for asset_id in assets_id_list]
|
||||
asset_group.created_by = self.request.user.username or 'Admin'
|
||||
asset_group.assets.add(*tuple(assets))
|
||||
asset_group.save()
|
||||
return super(AssetGroupCreateView, self).form_valid(form)
|
||||
|
||||
|
||||
class AssetGroupListView(AdminUserRequiredMixin, TemplateView):
|
||||
template_name = 'assets/asset_group_list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Asset group list'),
|
||||
'assets': Asset.objects.all(),
|
||||
'system_users': SystemUser.objects.all(),
|
||||
'keyword': self.request.GET.get('keyword', '')
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(AssetGroupListView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AssetGroupDetailView(AdminUserRequiredMixin, DetailView):
|
||||
model = AssetGroup
|
||||
template_name = 'assets/asset_group_detail.html'
|
||||
context_object_name = 'asset_group'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
assets_remain = Asset.objects.exclude(id__in=self.object.assets.all())
|
||||
system_users = SystemUser.objects.all()
|
||||
system_users_remain = SystemUser.objects.exclude(id__in=system_users)
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Asset group detail'),
|
||||
'assets_remain': assets_remain,
|
||||
'assets': [asset for asset in Asset.objects.all()
|
||||
if asset not in assets_remain],
|
||||
'system_users': system_users,
|
||||
'system_users_remain': system_users_remain,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(AssetGroupDetailView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AssetGroupUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
model = AssetGroup
|
||||
form_class = forms.AssetGroupForm
|
||||
template_name = 'assets/asset_group_create.html'
|
||||
success_url = reverse_lazy('assets:asset-group-list')
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(queryset=AssetGroup.objects.all())
|
||||
return super(AssetGroupUpdateView, self).get(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
assets_all = self.object.assets.all()
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Create asset group'),
|
||||
'assets_on_list': assets_all,
|
||||
'assets_count': len(assets_all),
|
||||
'group_id': self.object.id,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(AssetGroupUpdateView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AssetGroupDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||
template_name = 'assets/delete_confirm.html'
|
||||
model = AssetGroup
|
||||
success_url = reverse_lazy('assets:asset-group-list')
|
||||
101
apps/assets/views/idc.py
Normal file
101
apps/assets/views/idc.py
Normal file
@@ -0,0 +1,101 @@
|
||||
# coding:utf-8
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic import TemplateView, ListView, View
|
||||
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic.detail import DetailView, SingleObjectMixin
|
||||
from .. import forms
|
||||
from ..models import Asset, AssetGroup, AdminUser, IDC, SystemUser
|
||||
from ..hands import AdminUserRequiredMixin
|
||||
|
||||
|
||||
__all__ = ['IDCListView', 'IDCCreateView', 'IDCUpdateView',
|
||||
'IDCDetailView', 'IDCDeleteView', 'IDCAssetsView']
|
||||
|
||||
|
||||
class IDCListView(AdminUserRequiredMixin, TemplateView):
|
||||
template_name = 'assets/idc_list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('IDC list'),
|
||||
# 'keyword': self.request.GET.get('keyword', '')
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(IDCListView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class IDCCreateView(AdminUserRequiredMixin, CreateView):
|
||||
model = IDC
|
||||
form_class = forms.IDCForm
|
||||
template_name = 'assets/idc_create_update.html'
|
||||
success_url = reverse_lazy('assets:idc-list')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('assets'),
|
||||
'action': _('Create IDC'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(IDCCreateView, self).get_context_data(**kwargs)
|
||||
|
||||
def form_valid(self, form):
|
||||
idc = form.save(commit=False)
|
||||
idc.created_by = self.request.user.username or 'System'
|
||||
idc.save()
|
||||
return super(IDCCreateView, self).form_valid(form)
|
||||
|
||||
|
||||
class IDCUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
model = IDC
|
||||
form_class = forms.IDCForm
|
||||
template_name = 'assets/idc_create_update.html'
|
||||
context_object_name = 'idc'
|
||||
success_url = reverse_lazy('assets:idc-list')
|
||||
|
||||
def form_valid(self, form):
|
||||
idc = form.save(commit=False)
|
||||
idc.save()
|
||||
return super(IDCUpdateView, self).form_valid(form)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('assets'),
|
||||
'action': _('Update IDC'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(IDCUpdateView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class IDCDetailView(AdminUserRequiredMixin, DetailView):
|
||||
model = IDC
|
||||
template_name = 'assets/idc_detail.html'
|
||||
context_object_name = 'idc'
|
||||
|
||||
|
||||
class IDCAssetsView(AdminUserRequiredMixin, DetailView):
|
||||
model = IDC
|
||||
template_name = 'assets/idc_assets.html'
|
||||
context_object_name = 'idc'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
assets_remain = Asset.objects.exclude(id__in=self.object.assets.all())
|
||||
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Asset detail'),
|
||||
'groups': AssetGroup.objects.all(),
|
||||
'system_users': SystemUser.objects.all(),
|
||||
'assets_remain': assets_remain,
|
||||
'assets': [asset for asset in Asset.objects.all() if asset not in assets_remain],
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(IDCAssetsView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class IDCDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||
model = IDC
|
||||
template_name = 'assets/delete_confirm.html'
|
||||
success_url = reverse_lazy('assets:idc-list')
|
||||
147
apps/assets/views/system_user.py
Normal file
147
apps/assets/views/system_user.py
Normal file
@@ -0,0 +1,147 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.conf import settings
|
||||
from django.db import transaction
|
||||
from django.views.generic import TemplateView, ListView
|
||||
from django.views.generic.edit import CreateView, DeleteView, UpdateView
|
||||
from django.urls import reverse_lazy
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.views.generic.detail import DetailView, SingleObjectMixin
|
||||
|
||||
from .. import forms
|
||||
from ..models import Asset, AssetGroup, SystemUser
|
||||
from ..hands import AdminUserRequiredMixin
|
||||
from perms.utils import associate_system_users_and_assets
|
||||
|
||||
|
||||
__all__ = ['SystemUserCreateView', 'SystemUserUpdateView',
|
||||
'SystemUserDetailView', 'SystemUserDeleteView',
|
||||
'SystemUserAssetView', 'SystemUserListView',
|
||||
]
|
||||
|
||||
|
||||
class SystemUserListView(AdminUserRequiredMixin, TemplateView):
|
||||
template_name = 'assets/system_user_list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('System user list'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(SystemUserListView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
model = SystemUser
|
||||
form_class = forms.SystemUserForm
|
||||
template_name = 'assets/system_user_create.html'
|
||||
success_url = reverse_lazy('assets:system-user-list')
|
||||
|
||||
@transaction.atomic
|
||||
def post(self, request, *args, **kwargs):
|
||||
return super(SystemUserCreateView, self).post(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Create system user'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(SystemUserCreateView, self).get_context_data(**kwargs)
|
||||
|
||||
def get_success_message(self, cleaned_data):
|
||||
url = reverse_lazy('assets:system-user-detail',
|
||||
kwargs={'pk': self.object.pk}),
|
||||
success_message = _(
|
||||
'Create system user <a href="{url}">{name}</a> '
|
||||
'successfully.'.format(url=url, name=self.object.name)
|
||||
)
|
||||
|
||||
return success_message
|
||||
|
||||
|
||||
class SystemUserUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
model = SystemUser
|
||||
form_class = forms.SystemUserUpdateForm
|
||||
template_name = 'assets/system_user_update.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Update system user')
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(SystemUserUpdateView, self).get_context_data(**kwargs)
|
||||
|
||||
def form_valid(self, form):
|
||||
response = super(SystemUserUpdateView, self).form_valid(form)
|
||||
system_user = self.object
|
||||
assets = system_user.assets.all()
|
||||
asset_groups = system_user.asset_groups.all()
|
||||
associate_system_users_and_assets([system_user], assets, asset_groups, force=True)
|
||||
return response
|
||||
|
||||
def get_success_url(self):
|
||||
success_url = reverse_lazy('assets:system-user-detail',
|
||||
kwargs={'pk': self.object.pk})
|
||||
return success_url
|
||||
|
||||
|
||||
class SystemUserDetailView(AdminUserRequiredMixin, DetailView):
|
||||
template_name = 'assets/system_user_detail.html'
|
||||
context_object_name = 'system_user'
|
||||
model = SystemUser
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('System user detail')
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(SystemUserDetailView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
class SystemUserDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||
model = SystemUser
|
||||
template_name = 'assets/delete_confirm.html'
|
||||
success_url = reverse_lazy('assets:system-user-list')
|
||||
|
||||
|
||||
class SystemUserAssetView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
|
||||
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
|
||||
template_name = 'assets/system_user_asset.html'
|
||||
context_object_name = 'system_user'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(queryset=SystemUser.objects.all())
|
||||
return super(SystemUserAssetView, self).get(request, *args, **kwargs)
|
||||
|
||||
def get_asset_groups(self):
|
||||
return self.object.asset_groups.all()
|
||||
|
||||
# Todo: queryset default order by connectivity, need ops support
|
||||
def get_queryset(self):
|
||||
return list(self.object.get_assets())
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
asset_groups = self.get_asset_groups()
|
||||
assets = self.get_queryset()
|
||||
context = {
|
||||
'app': 'assets',
|
||||
'action': 'System user asset',
|
||||
'assets_remain': [asset for asset in Asset.objects.all() if asset not in assets],
|
||||
'asset_groups': asset_groups,
|
||||
'asset_groups_remain': [asset_group for asset_group in AssetGroup.objects.all()
|
||||
if asset_group not in asset_groups]
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super(SystemUserAssetView, self).get_context_data(**kwargs)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
1
apps/audits/__init__.py
Normal file
1
apps/audits/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
92
apps/audits/api.py
Normal file
92
apps/audits/api.py
Normal file
@@ -0,0 +1,92 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
#
|
||||
|
||||
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from rest_framework import generics, viewsets
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
|
||||
from audits.backends import command_store, record_store
|
||||
from audits.backends.command.serializers import CommandLogSerializer
|
||||
from audits.backends.record.serializers import RecordSerializer
|
||||
from . import models, serializers
|
||||
from .hands import IsSuperUserOrAppUser, IsAppUser
|
||||
|
||||
|
||||
class ProxyLogReceiveView(generics.CreateAPIView):
|
||||
queryset = models.ProxyLog.objects.all()
|
||||
serializer_class = serializers.ProxyLogSerializer
|
||||
permission_classes = (IsAppUser,)
|
||||
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
kwargs['data']['terminal'] = self.request.user.terminal.name
|
||||
return super(ProxyLogReceiveView, self).get_serializer(*args, **kwargs)
|
||||
|
||||
|
||||
class ProxyLogViewSet(viewsets.ModelViewSet):
|
||||
"""User proxy to backend server need call this api.
|
||||
|
||||
params: {
|
||||
"username": "",
|
||||
"name": "",
|
||||
"hostname": "",
|
||||
"ip": "",
|
||||
"terminal": "",
|
||||
"login_type": "",
|
||||
"system_user": "",
|
||||
"was_failed": "",
|
||||
"date_start": ""
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
queryset = models.ProxyLog.objects.all()
|
||||
serializer_class = serializers.ProxyLogSerializer
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
|
||||
|
||||
class CommandLogViewSet(BulkModelViewSet):
|
||||
"""接受app发送来的command log, 格式如下
|
||||
{
|
||||
"proxy_log_id": 23,
|
||||
"user": "admin",
|
||||
"asset": "localhost",
|
||||
"system_user": "web",
|
||||
"command_no": 1,
|
||||
"command": "whoami",
|
||||
"output": "d2hvbWFp", # base64.b64encode(s)
|
||||
"timestamp": 1485238673.0
|
||||
}
|
||||
|
||||
"""
|
||||
queryset = command_store.all()
|
||||
serializer_class = CommandLogSerializer
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
|
||||
|
||||
class RecordLogViewSet(BulkModelViewSet):
|
||||
"""接受app发送来的record log, 格式如下
|
||||
{
|
||||
"proxy_log_id": 23,
|
||||
"output": "d2hvbWFp", # base64.b64encode(s)
|
||||
"timestamp": 1485238673.0
|
||||
}
|
||||
"""
|
||||
|
||||
serializer_class = RecordSerializer
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
|
||||
def get_queryset(self):
|
||||
filter_kwargs = {}
|
||||
proxy_log_id = self.request.query_params.get('proxy_log_id')
|
||||
data_from_ts = self.request.query_params.get('date_from_ts')
|
||||
if proxy_log_id:
|
||||
filter_kwargs['proxy_log_id'] = proxy_log_id
|
||||
if data_from_ts:
|
||||
filter_kwargs['date_from_ts'] = data_from_ts
|
||||
if filter_kwargs:
|
||||
return record_store.filter(**filter_kwargs)
|
||||
else:
|
||||
return record_store.all()
|
||||
|
||||
7
apps/audits/apps.py
Normal file
7
apps/audits/apps.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AuditsConfig(AppConfig):
|
||||
name = 'audits'
|
||||
10
apps/audits/backends/__init__.py
Normal file
10
apps/audits/backends/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from importlib import import_module
|
||||
from django.conf import settings
|
||||
|
||||
command_engine = import_module(settings.COMMAND_STORE_BACKEND)
|
||||
command_store = command_engine.CommandStore()
|
||||
record_engine = import_module(settings.RECORD_STORE_BACKEND)
|
||||
record_store = record_engine.RecordStore()
|
||||
from .command.serializers import CommandLogSerializer
|
||||
|
||||
|
||||
19
apps/audits/backends/command/base.py
Normal file
19
apps/audits/backends/command/base.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# coding: utf-8
|
||||
import abc
|
||||
|
||||
|
||||
class CommandBase(object):
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
@abc.abstractmethod
|
||||
def save(self, proxy_log_id, user, asset, system_user,
|
||||
command_no, command, output, timestamp):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def filter(self, date_from_ts=None, date_to_ts=None, user='',
|
||||
asset='', system_user='', command='', proxy_log_id=0):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
45
apps/audits/backends/command/db.py
Normal file
45
apps/audits/backends/command/db.py
Normal file
@@ -0,0 +1,45 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
|
||||
from .base import CommandBase
|
||||
from audits.models import CommandLog
|
||||
|
||||
|
||||
class CommandStore(CommandBase):
|
||||
model = CommandLog
|
||||
queryset = []
|
||||
|
||||
def save(self, proxy_log_id, user, asset, system_user,
|
||||
command_no, command, output, timestamp):
|
||||
self.model.objects.create(
|
||||
proxy_log_id=proxy_log_id, user=user, asset=asset,
|
||||
system_user=system_user, command_no=command_no,
|
||||
command=command, output=output, timestamp=timestamp
|
||||
)
|
||||
|
||||
def filter(self, date_from_ts=None, date_to_ts=None, user='',
|
||||
asset='', system_user='', command='', proxy_log_id=0):
|
||||
filter_kwargs = {}
|
||||
|
||||
if date_from_ts:
|
||||
filter_kwargs['timestamp__gte'] = date_from_ts
|
||||
if date_to_ts:
|
||||
filter_kwargs['timestamp__lte'] = date_to_ts
|
||||
if user:
|
||||
filter_kwargs['user'] = user
|
||||
if asset:
|
||||
filter_kwargs['asset'] = asset
|
||||
if system_user:
|
||||
filter_kwargs['system_user'] = system_user
|
||||
if command:
|
||||
filter_kwargs['command__icontains'] = command
|
||||
if proxy_log_id:
|
||||
filter_kwargs['proxy_log_id'] = proxy_log_id
|
||||
|
||||
if filter_kwargs:
|
||||
self.queryset = self.model.objects.filter(**filter_kwargs)
|
||||
return self.queryset
|
||||
|
||||
def all(self):
|
||||
"""返回所有数据"""
|
||||
return self.model.objects.iterator()
|
||||
|
||||
21
apps/audits/backends/command/serializers.py
Normal file
21
apps/audits/backends/command/serializers.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
import base64
|
||||
from rest_framework import serializers
|
||||
from audits.models import CommandLog
|
||||
from audits.backends import command_store
|
||||
|
||||
|
||||
class CommandLogSerializer(serializers.ModelSerializer):
|
||||
"""使用这个类作为基础Command Log Serializer类, 用来序列化"""
|
||||
|
||||
class Meta:
|
||||
model = CommandLog
|
||||
fields = '__all__'
|
||||
|
||||
def create(self, validated_data):
|
||||
try:
|
||||
output = validated_data['output']
|
||||
validated_data['output'] = base64.b64decode(output)
|
||||
except IndexError:
|
||||
pass
|
||||
return command_store.save(**dict(validated_data))
|
||||
2
apps/audits/backends/record/__init__.py
Normal file
2
apps/audits/backends/record/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
|
||||
14
apps/audits/backends/record/base.py
Normal file
14
apps/audits/backends/record/base.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# coding: utf-8
|
||||
import abc
|
||||
|
||||
|
||||
class RecordBase(object):
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
@abc.abstractmethod
|
||||
def save(self, proxy_log_id, output, timestamp):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def filter(self, date_from_ts=None, proxy_log_id=None):
|
||||
pass
|
||||
31
apps/audits/backends/record/db.py
Normal file
31
apps/audits/backends/record/db.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
|
||||
from .base import RecordBase
|
||||
from audits.models import RecordLog
|
||||
|
||||
|
||||
class RecordStore(RecordBase):
|
||||
model = RecordLog
|
||||
queryset = []
|
||||
|
||||
def save(self, proxy_log_id, output, timestamp):
|
||||
return self.model.objects.create(
|
||||
proxy_log_id=proxy_log_id, output=output, timestamp=timestamp
|
||||
)
|
||||
|
||||
def filter(self, date_from_ts=None, proxy_log_id=''):
|
||||
filter_kwargs = {}
|
||||
|
||||
if date_from_ts:
|
||||
filter_kwargs['timestamp__gte'] = date_from_ts
|
||||
if proxy_log_id:
|
||||
filter_kwargs['proxy_log_id'] = proxy_log_id
|
||||
|
||||
if filter_kwargs:
|
||||
self.queryset = self.model.objects.filter(**filter_kwargs)
|
||||
return self.queryset
|
||||
|
||||
def all(self):
|
||||
"""返回所有数据"""
|
||||
return self.model.objects.all()
|
||||
|
||||
20
apps/audits/backends/record/serializers.py
Normal file
20
apps/audits/backends/record/serializers.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
import base64
|
||||
from rest_framework import serializers
|
||||
from audits.models import RecordLog
|
||||
from audits.backends import record_store
|
||||
|
||||
|
||||
class RecordSerializer(serializers.ModelSerializer):
|
||||
"""使用这个类作为基础Command Log Serializer类, 用来序列化"""
|
||||
class Meta:
|
||||
model = RecordLog
|
||||
fields = '__all__'
|
||||
|
||||
def create(self, validated_data):
|
||||
try:
|
||||
output = validated_data['output']
|
||||
validated_data['output'] = base64.b64decode(output)
|
||||
except IndexError:
|
||||
pass
|
||||
return record_store.save(**dict(validated_data))
|
||||
8
apps/audits/hands.py
Normal file
8
apps/audits/hands.py
Normal file
@@ -0,0 +1,8 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
#
|
||||
|
||||
from users.utils import AdminUserRequiredMixin
|
||||
from users.models import User
|
||||
from assets.models import Asset, SystemUser
|
||||
from users.permissions import IsSuperUserOrAppUser, IsAppUser
|
||||
from applications.models import Terminal
|
||||
0
logs/test.log → apps/audits/migrations/__init__.py
Executable file → Normal file
0
logs/test.log → apps/audits/migrations/__init__.py
Executable file → Normal file
94
apps/audits/models.py
Normal file
94
apps/audits/models.py
Normal file
@@ -0,0 +1,94 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class LoginLog(models.Model):
|
||||
LOGIN_TYPE_CHOICE = (
|
||||
('W', 'Web'),
|
||||
('ST', 'SSH Terminal'),
|
||||
('WT', 'Web Terminal')
|
||||
)
|
||||
|
||||
username = models.CharField(max_length=20, verbose_name=_('Username'))
|
||||
name = models.CharField(max_length=20, blank=True, verbose_name=_('Name'))
|
||||
login_type = models.CharField(choices=LOGIN_TYPE_CHOICE, max_length=2,
|
||||
verbose_name=_('Login type'))
|
||||
login_ip = models.GenericIPAddressField(verbose_name=_('Login ip'))
|
||||
login_city = models.CharField(max_length=254, blank=True, null=True,
|
||||
verbose_name=_('Login city'))
|
||||
user_agent = models.CharField(max_length=254, blank=True, null=True,
|
||||
verbose_name=_('User agent'))
|
||||
date_login = models.DateTimeField(auto_now_add=True,
|
||||
verbose_name=_('Date login'))
|
||||
|
||||
class Meta:
|
||||
db_table = 'login_log'
|
||||
ordering = ['-date_login', 'username']
|
||||
|
||||
|
||||
class ProxyLog(models.Model):
|
||||
LOGIN_TYPE_CHOICE = (
|
||||
('ST', 'SSH Terminal'),
|
||||
('WT', 'Web Terminal'),
|
||||
)
|
||||
|
||||
user = models.CharField(max_length=32, verbose_name=_('User'))
|
||||
asset = models.CharField(max_length=32, verbose_name=_('Asset'))
|
||||
system_user = models.CharField(max_length=32, verbose_name=_('System user'))
|
||||
login_type = models.CharField(
|
||||
choices=LOGIN_TYPE_CHOICE, max_length=2, blank=True,
|
||||
null=True, verbose_name=_('Login type'))
|
||||
terminal = models.CharField(
|
||||
max_length=32, blank=True, null=True, verbose_name=_('Terminal'))
|
||||
is_failed = models.BooleanField(
|
||||
default=False, verbose_name=_('Did connect failed'))
|
||||
is_finished = models.BooleanField(
|
||||
default=False, verbose_name=_('Is finished'))
|
||||
date_start = models.DateTimeField(
|
||||
auto_created=True, verbose_name=_('Date start'))
|
||||
date_finished = models.DateTimeField(
|
||||
null=True, verbose_name=_('Date finished'))
|
||||
|
||||
def __unicode__(self):
|
||||
return '%s-%s-%s' % (self.user, self.asset, self.system_user)
|
||||
|
||||
def commands(self):
|
||||
from audits.backends import command_store
|
||||
return command_store.filter(proxy_log_id=self.id)
|
||||
|
||||
class Meta:
|
||||
ordering = ['-date_start', 'user']
|
||||
|
||||
|
||||
class CommandLog(models.Model):
|
||||
proxy_log_id = models.IntegerField(db_index=True)
|
||||
user = models.CharField(max_length=48, db_index=True)
|
||||
asset = models.CharField(max_length=128, db_index=True)
|
||||
system_user = models.CharField(max_length=48, db_index=True)
|
||||
command_no = models.IntegerField()
|
||||
command = models.TextField(max_length=767, blank=True)
|
||||
output = models.TextField(blank=True)
|
||||
timestamp = models.FloatField(db_index=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return '%s: %s' % (self.id, self.command)
|
||||
|
||||
class Meta:
|
||||
ordering = ['command_no', 'command']
|
||||
|
||||
|
||||
class RecordLog(models.Model):
|
||||
proxy_log_id = models.IntegerField(db_index=True)
|
||||
output = models.TextField(verbose_name=_('Output'))
|
||||
timestamp = models.FloatField(db_index=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return 'Record: %s' % self.proxy_log_id
|
||||
|
||||
class Meta:
|
||||
ordering = ['timestamp']
|
||||
28
apps/audits/serializers.py
Normal file
28
apps/audits/serializers.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.utils import timesince
|
||||
from . import models
|
||||
|
||||
|
||||
class ProxyLogSerializer(serializers.ModelSerializer):
|
||||
time = serializers.SerializerMethodField()
|
||||
command_length = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = models.ProxyLog
|
||||
fields = '__all__'
|
||||
|
||||
@staticmethod
|
||||
def get_time(obj):
|
||||
if not obj.is_finished:
|
||||
return ''
|
||||
else:
|
||||
return timesince(obj.date_start, since=obj.date_finished)
|
||||
|
||||
@staticmethod
|
||||
def get_command_length(obj):
|
||||
return 2
|
||||
|
||||
12
apps/audits/tasks.py
Normal file
12
apps/audits/tasks.py
Normal file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env python
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
#
|
||||
|
||||
from celery import shared_task
|
||||
from .utils import write_login_log
|
||||
|
||||
|
||||
@shared_task
|
||||
def write_login_log_async(*args, **kwargs):
|
||||
write_login_log(*args, **kwargs)
|
||||
|
||||
109
apps/audits/templates/audits/command_log_list.html
Normal file
109
apps/audits/templates/audits/command_log_list.html
Normal file
@@ -0,0 +1,109 @@
|
||||
{% extends '_base_list.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load common_tags %}
|
||||
{% block content_left_head %}
|
||||
<link href="{% static "css/plugins/footable/footable.core.css" %}" rel="stylesheet">
|
||||
<link href="{% static 'css/plugins/datepicker/datepicker3.css' %}" rel="stylesheet">
|
||||
<style>
|
||||
#search_btn {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_search %}
|
||||
<form id="search_form" method="get" action="" class="pull-right form-inline">
|
||||
<div class="form-group" id="date">
|
||||
<div class="input-daterange input-group" id="datepicker">
|
||||
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
|
||||
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_from" value="{{ date_from }}">
|
||||
<span class="input-group-addon">to</span>
|
||||
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_to" value="{{ date_to }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<select class="select2 form-control" name="username">
|
||||
<option value="">{% trans 'User' %}</option>
|
||||
{% for u in user_list %}
|
||||
<option value="{{ u.username }}" {% if username == u.username %} selected {% endif %}>{{ u.username }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<select class="select2 form-control" name="ip">
|
||||
<option value="">{% trans 'Asset' %}</option>
|
||||
{% for a in asset_list %}
|
||||
<option value="{{ a.ip }}" {% if ip == a.ip %} selected {% endif %}>{{ a.ip }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<select class="select2 form-control" name="system_user">
|
||||
<option value="">{% trans 'System user' %}</option>
|
||||
{% for s in system_user_list %}
|
||||
<option value="{{ s.username }}" {% if s.username == system_user %} selected {% endif %}>{{ s.username }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control input-sm" name="command" placeholder="Command" value="{{ command }}">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<div class="input-group-btn">
|
||||
<button id='search_btn' type="submit" class="btn btn-sm btn-primary">
|
||||
搜索
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
{% block table_container %}
|
||||
<table class="footable table table-stripped toggle-arrow-tiny" data-page="false">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-toggle="true">ID</th>
|
||||
<th>Command</th>
|
||||
<th>Username</th>
|
||||
<th>IP</th>
|
||||
<th>System user</th>
|
||||
<th>Proxy log</th>
|
||||
<th>Datetime</th>
|
||||
<th data-hide="all">Output</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for command in command_list %}
|
||||
<tr>
|
||||
<td>{{ command.id }}</td>
|
||||
<td>{{ command.command }}</td>
|
||||
<td>{{ command.user }}</td>
|
||||
<td>{{ command.asset }}</td>
|
||||
<td>{{ command.system_user }}</td>
|
||||
<td><a href="{% url 'audits:proxy-log-detail' pk=command.proxy_log_id %}">{{ command.proxy_log_id}}</a></td>
|
||||
<td>{{ command.timestamp|ts_to_date }}</td>
|
||||
<td><pre style="border: none; background: none">{{ command.output|to_html|safe }}</pre></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script src="{% static "js/plugins/footable/footable.all.min.js" %}"></script>
|
||||
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('.footable').footable();
|
||||
$('.select2').select2();
|
||||
$('#date .input-daterange').datepicker({
|
||||
dateFormat: 'mm/dd/yy',
|
||||
keyboardNavigation: false,
|
||||
forceParse: false,
|
||||
autoclose: true
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
101
apps/audits/templates/audits/login_log_list.html
Normal file
101
apps/audits/templates/audits/login_log_list.html
Normal file
@@ -0,0 +1,101 @@
|
||||
{% extends '_base_list.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load common_tags %}
|
||||
{% block content_left_head %}
|
||||
<link href="{% static 'css/plugins/datepicker/datepicker3.css' %}" rel="stylesheet">
|
||||
<style>
|
||||
#search_btn {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block table_search %}
|
||||
<form id="search_form" method="get" action="" class="pull-right form-inline">
|
||||
<div class="form-group" id="date">
|
||||
<div class="input-daterange input-group" id="datepicker">
|
||||
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
|
||||
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_from" value="{{ date_from }}">
|
||||
<span class="input-group-addon">to</span>
|
||||
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_to" value="{{ date_to }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<select class="select2 form-control" name="username">
|
||||
<option value="">{% trans 'Select user' %}</option>
|
||||
{% for user in user_list %}
|
||||
<option value="{{ user.username }}" {% if user.username == username %} selected {% endif %}>{{ user.username }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control input-sm" name="keyword" placeholder="Search" value="{{ keyword }}">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<div class="input-group-btn">
|
||||
<button id='search_btn' type="submit" class="btn btn-sm btn-primary">
|
||||
搜索
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_head %}
|
||||
<th class="text-center">{% trans 'ID' %}</th>
|
||||
<th class="text-center">{% trans 'Username' %}</th>
|
||||
<th class="text-center">{% trans 'Name' %}</th>
|
||||
<th class="text-center">{% trans 'Type' %}</th>
|
||||
<th class="text-center">{% trans 'UA' %}</th>
|
||||
<th class="text-center">{% trans 'IP' %}</th>
|
||||
<th class="text-center">{% trans 'City' %}</th>
|
||||
<th class="text-center">{% trans 'Date' %}</th>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_body %}
|
||||
{% for login_log in login_log_list %}
|
||||
<tr class="gradeX">
|
||||
<td class="text-center">
|
||||
{{ login_log.id }}
|
||||
{# <a href="{% url 'audits:proxy-log-detail' pk=login_log.id %}">{{ login_log.id }}</a>#}
|
||||
</td>
|
||||
<td class="text-center">{{ login_log.username }}</td>
|
||||
<td class="text-center">{{ login_log.name }}</td>
|
||||
<td class="text-center">{{ login_log.get_login_type_display }}</td>
|
||||
{% if login_log.login_type == 'W' %}
|
||||
<td class="text-center">
|
||||
<span href="javascript:void(0);" data-toggle="tooltips" title="{{ login_log.user_agent }}">{{ login_log.user_agent | truncatechars:20 }}</span>
|
||||
</td>
|
||||
{% else %}
|
||||
<td class="text-center">{{ login_log.terminal }}</td>
|
||||
{% endif %}
|
||||
<td class="text-center">{{ login_log.login_ip }}</td>
|
||||
<td class="text-center">{{ login_log.login_city }}</td>
|
||||
<td class="text-center">{{ login_log.date_login }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('table').DataTable({
|
||||
"searching": false,
|
||||
"bInfo" : false,
|
||||
"paging": false,
|
||||
"order": []
|
||||
});
|
||||
$('#date .input-daterange').datepicker({
|
||||
dateFormat: 'mm/dd/yy',
|
||||
keyboardNavigation: false,
|
||||
forceParse: false,
|
||||
autoclose: true
|
||||
});
|
||||
$('.select2').select2();
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="renderer" content="webkit">
|
||||
{% include '_head_css_js.html' %}
|
||||
<link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet">
|
||||
<link href="{% static "css/plugins/footable/footable.core.css" %}" rel="stylesheet">
|
||||
<script src="{% static 'js/jquery-2.1.1.js' %}"></script>
|
||||
<script src="{% static 'js/plugins/sweetalert/sweetalert.min.js' %}"></script>
|
||||
<script src="{% static 'js/bootstrap.min.js' %}"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="tab-content">
|
||||
<div class="ibox-content">
|
||||
<input type="text" class="form-control input-sm m-b-xs" id="filter"
|
||||
placeholder="Search in table">
|
||||
<table class="footable table table-stripped toggle-arrow-tiny" data-page-size="10" data-filter=#filter>
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-toggle="true">ID</th>
|
||||
<th>Command</th>
|
||||
<th data-hide="all">Output</th>
|
||||
<th>Datetime</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="table_body">
|
||||
{% for command in object_list %}
|
||||
<tr>
|
||||
<td>{{ command.command_no }}</td>
|
||||
<td>{{ command.command }}</td>
|
||||
<td>{{ command.output_decode |safe }}</td>
|
||||
<td>{{ command.datetime }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="5">
|
||||
<ul class="pagination pull-right"></ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<script src="{% static "js/plugins/footable/footable.all.min.js" %}"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('.footable').footable();
|
||||
});
|
||||
</script>
|
||||
</html>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user