Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
J
jumpserver
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ops
jumpserver
Commits
a5b9b4e1
Unverified
Commit
a5b9b4e1
authored
Dec 19, 2018
by
老广
Committed by
GitHub
Dec 19, 2018
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #2206 from jumpserver/dev
Dev
parents
ac7e3e7f
b3079a4a
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
64 changed files
with
824 additions
and
514 deletions
+824
-514
admin_user.py
apps/assets/api/admin_user.py
+22
-3
asset.py
apps/assets/api/asset.py
+27
-21
node.py
apps/assets/api/node.py
+90
-51
system_user.py
apps/assets/api/system_user.py
+8
-7
user.py
apps/assets/forms/user.py
+3
-3
0020_auto_20180816_1652.py
apps/assets/migrations/0020_auto_20180816_1652.py
+7
-7
0022_auto_20181012_1717.py
apps/assets/migrations/0022_auto_20181012_1717.py
+2
-2
asset.py
apps/assets/models/asset.py
+79
-64
base.py
apps/assets/models/base.py
+7
-0
node.py
apps/assets/models/node.py
+93
-27
user.py
apps/assets/models/user.py
+72
-27
asset.py
apps/assets/serializers/asset.py
+21
-1
node.py
apps/assets/serializers/node.py
+9
-59
system_user.py
apps/assets/serializers/system_user.py
+8
-5
signals_handler.py
apps/assets/signals_handler.py
+20
-6
tasks.py
apps/assets/tasks.py
+0
-0
_asset_list_modal.html
apps/assets/templates/assets/_asset_list_modal.html
+14
-21
admin_user_assets.html
apps/assets/templates/assets/admin_user_assets.html
+34
-11
asset_list.html
apps/assets/templates/assets/asset_list.html
+0
-0
label_create_update.html
apps/assets/templates/assets/label_create_update.html
+1
-0
system_user_asset.html
apps/assets/templates/assets/system_user_asset.html
+5
-3
system_user_list.html
apps/assets/templates/assets/system_user_list.html
+1
-1
api_urls.py
apps/assets/urls/api_urls.py
+10
-6
admin_user.py
apps/assets/views/admin_user.py
+1
-1
asset.py
apps/assets/views/asset.py
+0
-1
0002_ftplog_org_id.py
apps/audits/migrations/0002_ftplog_org_id.py
+1
-1
0003_auto_20180816_1652.py
apps/audits/migrations/0003_auto_20180816_1652.py
+1
-1
0004_operatelog_passwordchangelog_userloginlog.py
...rations/0004_operatelog_passwordchangelog_userloginlog.py
+1
-1
forms.py
apps/common/forms.py
+16
-4
models.py
apps/common/models.py
+2
-0
signals_handler.py
apps/common/signals_handler.py
+4
-5
tree.py
apps/common/tree.py
+2
-5
conf.py
apps/jumpserver/conf.py
+9
-2
settings.py
apps/jumpserver/settings.py
+9
-0
django.mo
apps/locale/zh/LC_MESSAGES/django.mo
+0
-0
django.po
apps/locale/zh/LC_MESSAGES/django.po
+0
-0
adhoc.py
apps/ops/api/adhoc.py
+3
-1
serializers.py
apps/ops/serializers.py
+1
-1
api_urls.py
apps/ops/urls/api_urls.py
+1
-0
adhoc.py
apps/ops/views/adhoc.py
+4
-1
mixins.py
apps/orgs/mixins.py
+1
-1
api.py
apps/perms/api.py
+26
-9
hands.py
apps/perms/hands.py
+1
-1
0008_auto_20180816_1652.py
apps/perms/migrations/0008_auto_20180816_1652.py
+2
-2
serializers.py
apps/perms/serializers.py
+29
-0
asset_permission_create_update.html
...perms/templates/perms/asset_permission_create_update.html
+1
-0
asset_permission_list.html
apps/perms/templates/perms/asset_permission_list.html
+13
-66
jumpserver.js
apps/static/js/jumpserver.js
+29
-0
session.py
apps/terminal/api/v1/session.py
+6
-35
0011_auto_20180807_1116.py
apps/terminal/migrations/0011_auto_20180807_1116.py
+2
-2
0012_auto_20180816_1652.py
apps/terminal/migrations/0012_auto_20180816_1652.py
+0
-10
models.py
apps/terminal/models.py
+32
-0
tasks.py
apps/terminal/tasks.py
+31
-1
utils.py
apps/terminal/utils.py
+6
-4
group.py
apps/users/api/group.py
+2
-2
user.py
apps/users/api/user.py
+3
-0
0014_auto_20180816_1652.py
apps/users/migrations/0014_auto_20180816_1652.py
+1
-1
user.py
apps/users/models/user.py
+12
-0
v1.py
apps/users/serializers/v1.py
+8
-21
user_list.html
apps/users/templates/users/user_list.html
+17
-7
utils.py
apps/users/utils.py
+2
-1
config_docker.py
config_docker.py
+7
-0
config_example.py
config_example.py
+3
-0
jms
jms
+2
-2
No files found.
apps/assets/api/admin_user.py
View file @
a5b9b4e1
...
...
@@ -14,6 +14,7 @@
# limitations under the License.
from
django.db
import
transaction
from
django.shortcuts
import
get_object_or_404
from
rest_framework
import
generics
from
rest_framework.response
import
Response
from
rest_framework_bulk
import
BulkModelViewSet
...
...
@@ -24,13 +25,14 @@ from common.utils import get_logger
from
..hands
import
IsOrgAdmin
from
..models
import
AdminUser
,
Asset
from
..
import
serializers
from
..tasks
import
test_admin_user_connect
abil
ity_manual
from
..tasks
import
test_admin_user_connect
iv
ity_manual
logger
=
get_logger
(
__file__
)
__all__
=
[
'AdminUserViewSet'
,
'ReplaceNodesAdminUserApi'
,
'AdminUserTestConnectiveApi'
,
'AdminUserAuthApi'
,
'AdminUserAssetsListView'
,
]
...
...
@@ -81,12 +83,29 @@ class ReplaceNodesAdminUserApi(generics.UpdateAPIView):
class
AdminUserTestConnectiveApi
(
generics
.
RetrieveAPIView
):
"""
Test asset admin user connectivity
Test asset admin user
assets_
connectivity
"""
queryset
=
AdminUser
.
objects
.
all
()
permission_classes
=
(
IsOrgAdmin
,)
def
retrieve
(
self
,
request
,
*
args
,
**
kwargs
):
admin_user
=
self
.
get_object
()
task
=
test_admin_user_connect
abil
ity_manual
.
delay
(
admin_user
)
task
=
test_admin_user_connect
iv
ity_manual
.
delay
(
admin_user
)
return
Response
({
"task"
:
task
.
id
})
class
AdminUserAssetsListView
(
generics
.
ListAPIView
):
permission_classes
=
(
IsOrgAdmin
,)
serializer_class
=
serializers
.
AssetSimpleSerializer
pagination_class
=
LimitOffsetPagination
filter_fields
=
(
"hostname"
,
"ip"
)
http_method_names
=
[
'get'
]
search_fields
=
filter_fields
def
get_object
(
self
):
pk
=
self
.
kwargs
.
get
(
'pk'
)
return
get_object_or_404
(
AdminUser
,
pk
=
pk
)
def
get_queryset
(
self
):
admin_user
=
self
.
get_object
()
return
admin_user
.
get_related_assets
()
apps/assets/api/asset.py
View file @
a5b9b4e1
...
...
@@ -17,7 +17,7 @@ from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from
..models
import
Asset
,
AdminUser
,
Node
from
..
import
serializers
from
..tasks
import
update_asset_hardware_info_manual
,
\
test_asset_connect
abil
ity_manual
test_asset_connect
iv
ity_manual
from
..utils
import
LabelFilter
...
...
@@ -41,40 +41,46 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
pagination_class
=
LimitOffsetPagination
permission_classes
=
(
IsOrgAdminOrAppUser
,)
def
filter_node
(
self
):
def
filter_node
(
self
,
queryset
):
node_id
=
self
.
request
.
query_params
.
get
(
"node_id"
)
if
not
node_id
:
return
return
queryset
node
=
get_object_or_404
(
Node
,
id
=
node_id
)
show_current_asset
=
self
.
request
.
query_params
.
get
(
"show_current_asset"
)
in
(
'1'
,
'true'
)
if
node
.
is_root
():
if
show_current_asset
:
self
.
queryset
=
self
.
queryset
.
filter
(
if
node
.
is_root
()
and
show_current_asset
:
queryset
=
queryset
.
filter
(
Q
(
nodes
=
node_id
)
|
Q
(
nodes__isnull
=
True
)
)
return
if
show_current_asset
:
self
.
queryset
=
self
.
queryset
.
filter
(
nodes
=
node
)
elif
node
.
is_root
()
and
not
show_current_asset
:
pass
elif
not
node
.
is_root
()
and
show_current_asset
:
queryset
=
queryset
.
filter
(
nodes
=
node
)
else
:
self
.
queryset
=
self
.
queryset
.
filter
(
queryset
=
queryset
.
filter
(
nodes__key__regex
=
'^{}(:[0-9]+)*$'
.
format
(
node
.
key
),
)
return
queryset
def
filter_admin_user_id
(
self
):
def
filter_admin_user_id
(
self
,
queryset
):
admin_user_id
=
self
.
request
.
query_params
.
get
(
'admin_user_id'
)
if
admin_user_id
:
if
not
admin_user_id
:
return
queryset
admin_user
=
get_object_or_404
(
AdminUser
,
id
=
admin_user_id
)
self
.
queryset
=
self
.
queryset
.
filter
(
admin_user
=
admin_user
)
queryset
=
queryset
.
filter
(
admin_user
=
admin_user
)
return
queryset
def
filter_queryset
(
self
,
queryset
):
queryset
=
super
()
.
filter_queryset
(
queryset
)
queryset
=
self
.
filter_node
(
queryset
)
queryset
=
self
.
filter_admin_user_id
(
queryset
)
return
queryset
def
get_queryset
(
self
):
self
.
queryset
=
super
()
.
get_queryset
()
\
.
prefetch_related
(
'labels'
,
'nodes'
)
\
.
select_related
(
'admin_user'
)
self
.
filter_admin_user_id
()
self
.
filter_node
()
return
self
.
queryset
.
distinct
()
queryset
=
super
()
.
get_queryset
()
.
distinct
()
queryset
=
self
.
get_serializer_class
()
.
setup_eager_loading
(
queryset
)
return
queryset
class
AssetListUpdateApi
(
IDInFilterMixin
,
ListBulkCreateUpdateDestroyAPIView
):
...
...
@@ -103,7 +109,7 @@ class AssetRefreshHardwareApi(generics.RetrieveAPIView):
class
AssetAdminUserTestApi
(
generics
.
RetrieveAPIView
):
"""
Test asset admin user connectivity
Test asset admin user
assets_
connectivity
"""
queryset
=
Asset
.
objects
.
all
()
permission_classes
=
(
IsOrgAdmin
,)
...
...
@@ -111,7 +117,7 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView):
def
retrieve
(
self
,
request
,
*
args
,
**
kwargs
):
asset_id
=
kwargs
.
get
(
'pk'
)
asset
=
get_object_or_404
(
Asset
,
pk
=
asset_id
)
task
=
test_asset_connect
abil
ity_manual
.
delay
(
asset
)
task
=
test_asset_connect
iv
ity_manual
.
delay
(
asset
)
return
Response
({
"task"
:
task
.
id
})
...
...
apps/assets/api/node.py
View file @
a5b9b4e1
...
...
@@ -17,15 +17,14 @@ from rest_framework import generics, mixins, viewsets
from
rest_framework.serializers
import
ValidationError
from
rest_framework.views
import
APIView
from
rest_framework.response
import
Response
from
rest_framework_bulk
import
BulkModelViewSet
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.shortcuts
import
get_object_or_404
from
django.db.models
import
Count
from
common.utils
import
get_logger
,
get_object_or_none
from
common.tree
import
TreeNodeSerializer
from
..hands
import
IsOrgAdmin
from
..models
import
Node
from
..tasks
import
update_assets_hardware_info_util
,
test_asset_connect
abil
ity_util
from
..tasks
import
update_assets_hardware_info_util
,
test_asset_connect
iv
ity_util
from
..
import
serializers
...
...
@@ -34,7 +33,8 @@ __all__ = [
'NodeViewSet'
,
'NodeChildrenApi'
,
'NodeAssetsApi'
,
'NodeAddAssetsApi'
,
'NodeRemoveAssetsApi'
,
'NodeReplaceAssetsApi'
,
'NodeAddChildrenApi'
,
'RefreshNodeHardwareInfoApi'
,
'TestNodeConnectiveApi'
'TestNodeConnectiveApi'
,
'NodeListAsTreeApi'
,
'NodeChildrenAsTreeApi'
,
]
...
...
@@ -43,22 +43,89 @@ class NodeViewSet(viewsets.ModelViewSet):
permission_classes
=
(
IsOrgAdmin
,)
serializer_class
=
serializers
.
NodeSerializer
def
perform_create
(
self
,
serializer
):
child_key
=
Node
.
root
()
.
get_next_child_key
()
serializer
.
validated_data
[
"key"
]
=
child_key
serializer
.
save
()
def
update
(
self
,
request
,
*
args
,
**
kwargs
):
node
=
self
.
get_object
()
if
node
.
is_root
():
node_value
=
node
.
value
post_value
=
request
.
data
.
get
(
'value'
)
if
node_value
!=
post_value
:
return
Response
(
{
"msg"
:
_
(
"You can't update the root node name"
)},
status
=
400
)
return
super
()
.
update
(
request
,
*
args
,
**
kwargs
)
class
NodeListAsTreeApi
(
generics
.
ListAPIView
):
"""
获取节点列表树
[
{
"id": "",
"name": "",
"pId": "",
"meta": ""
}
]
"""
permission_classes
=
(
IsOrgAdmin
,)
serializer_class
=
TreeNodeSerializer
def
get_queryset
(
self
):
queryset
=
[
node
.
as_tree_node
()
for
node
in
Node
.
objects
.
all
()]
return
queryset
def
filter_queryset
(
self
,
queryset
):
if
self
.
request
.
query_params
.
get
(
'refresh'
,
'0'
)
==
'1'
:
queryset
=
self
.
refresh_nodes
(
queryset
)
return
queryset
@staticmethod
def
refresh_nodes
(
queryset
):
Node
.
expire_nodes_assets_amount
()
Node
.
expire_nodes_full_value
()
return
queryset
class
NodeChildrenAsTreeApi
(
generics
.
ListAPIView
):
"""
节点子节点作为树返回,
[
{
"id": "",
"name": "",
"pId": "",
"meta": ""
}
]
"""
permission_classes
=
(
IsOrgAdmin
,)
serializer_class
=
TreeNodeSerializer
node
=
None
is_root
=
False
def
get_queryset
(
self
):
node_key
=
self
.
request
.
query_params
.
get
(
'key'
)
if
node_key
:
self
.
node
=
Node
.
objects
.
get
(
key
=
node_key
)
queryset
=
self
.
node
.
get_children
(
with_self
=
False
)
else
:
self
.
is_root
=
True
self
.
node
=
Node
.
root
()
queryset
=
list
(
self
.
node
.
get_children
(
with_self
=
True
))
nodes_invalid
=
Node
.
objects
.
exclude
(
key__startswith
=
self
.
node
.
key
)
queryset
.
extend
(
list
(
nodes_invalid
))
queryset
=
[
node
.
as_tree_node
()
for
node
in
queryset
]
return
queryset
def
filter_assets
(
self
,
queryset
):
include_assets
=
self
.
request
.
query_params
.
get
(
'assets'
,
'0'
)
==
'1'
if
not
include_assets
:
return
queryset
assets
=
self
.
node
.
get_assets
()
for
asset
in
assets
:
queryset
.
append
(
asset
.
as_tree_node
(
self
.
node
))
return
queryset
def
filter_queryset
(
self
,
queryset
):
queryset
=
self
.
filter_assets
(
queryset
)
queryset
=
self
.
filter_refresh_nodes
(
queryset
)
return
queryset
def
filter_refresh_nodes
(
self
,
queryset
):
if
self
.
request
.
query_params
.
get
(
'refresh'
,
'0'
)
==
'1'
:
Node
.
expire_nodes_assets_amount
()
Node
.
expire_nodes_full_value
()
return
queryset
class
NodeChildrenApi
(
mixins
.
ListModelMixin
,
generics
.
CreateAPIView
):
...
...
@@ -67,19 +134,10 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
serializer_class
=
serializers
.
NodeSerializer
instance
=
None
def
counter
(
self
):
values
=
[
child
.
value
[
child
.
value
.
rfind
(
' '
):]
for
child
in
self
.
get_object
()
.
get_children
()
if
child
.
value
.
startswith
(
"新节点 "
)
]
values
=
[
int
(
value
)
for
value
in
values
if
value
.
strip
()
.
isdigit
()]
count
=
max
(
values
)
+
1
if
values
else
1
return
count
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
instance
=
self
.
get_object
()
if
not
request
.
data
.
get
(
"value"
):
request
.
data
[
"value"
]
=
_
(
"New node {}"
)
.
format
(
self
.
counter
()
)
request
.
data
[
"value"
]
=
instance
.
get_next_child_preset_name
(
)
return
super
()
.
post
(
request
,
*
args
,
**
kwargs
)
def
create
(
self
,
request
,
*
args
,
**
kwargs
):
...
...
@@ -91,10 +149,7 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
'The same level node name cannot be the same'
)
node
=
instance
.
create_child
(
value
=
value
)
return
Response
(
{
"id"
:
node
.
id
,
"key"
:
node
.
key
,
"value"
:
node
.
value
},
status
=
201
,
)
return
Response
(
self
.
serializer_class
(
instance
=
node
)
.
data
,
status
=
201
)
def
get_object
(
self
):
pk
=
self
.
kwargs
.
get
(
'pk'
)
or
self
.
request
.
query_params
.
get
(
'id'
)
...
...
@@ -107,7 +162,6 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
def
get_queryset
(
self
):
queryset
=
[]
query_all
=
self
.
request
.
query_params
.
get
(
"all"
)
query_assets
=
self
.
request
.
query_params
.
get
(
'assets'
)
node
=
self
.
get_object
()
if
node
is
None
:
...
...
@@ -120,23 +174,8 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
else
:
children
=
node
.
get_children
()
queryset
.
extend
(
list
(
children
))
if
query_assets
:
assets
=
node
.
get_assets
()
for
asset
in
assets
:
node_fake
=
Node
()
node_fake
.
assets__count
=
0
node_fake
.
id
=
asset
.
id
node_fake
.
is_node
=
False
node_fake
.
key
=
node
.
key
+
':0'
node_fake
.
value
=
asset
.
hostname
queryset
.
append
(
node_fake
)
queryset
=
sorted
(
queryset
,
key
=
lambda
x
:
x
.
is_node
,
reverse
=
True
)
return
queryset
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
return
super
()
.
list
(
request
,
*
args
,
**
kwargs
)
class
NodeAssetsApi
(
generics
.
ListAPIView
):
permission_classes
=
(
IsOrgAdmin
,)
...
...
@@ -234,5 +273,5 @@ class TestNodeConnectiveApi(APIView):
assets
=
node
.
assets
.
all
()
# task_name = _("测试节点下资产是否可连接: {}".format(node.name))
task_name
=
_
(
"Test if the assets under the node are connectable: {}"
.
format
(
node
.
name
))
task
=
test_asset_connect
abil
ity_util
.
delay
(
assets
,
task_name
=
task_name
)
task
=
test_asset_connect
iv
ity_util
.
delay
(
assets
,
task_name
=
task_name
)
return
Response
({
"task"
:
task
.
id
})
apps/assets/api/system_user.py
View file @
a5b9b4e1
...
...
@@ -24,8 +24,8 @@ from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from
..models
import
SystemUser
,
Asset
from
..
import
serializers
from
..tasks
import
push_system_user_to_assets_manual
,
\
test_system_user_connect
abil
ity_manual
,
push_system_user_a_asset_manual
,
\
test_system_user_connect
abil
ity_a_asset
test_system_user_connect
iv
ity_manual
,
push_system_user_a_asset_manual
,
\
test_system_user_connect
iv
ity_a_asset
logger
=
get_logger
(
__file__
)
...
...
@@ -33,7 +33,7 @@ __all__ = [
'SystemUserViewSet'
,
'SystemUserAuthInfoApi'
,
'SystemUserPushApi'
,
'SystemUserTestConnectiveApi'
,
'SystemUserAssetsListView'
,
'SystemUserPushToAssetApi'
,
'SystemUserTestAssetConnect
abil
ityApi'
,
'SystemUserCommandFilterRuleListApi'
,
'SystemUserTestAssetConnect
iv
ityApi'
,
'SystemUserCommandFilterRuleListApi'
,
]
...
...
@@ -93,15 +93,16 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView):
def
retrieve
(
self
,
request
,
*
args
,
**
kwargs
):
system_user
=
self
.
get_object
()
task
=
test_system_user_connect
abil
ity_manual
.
delay
(
system_user
)
task
=
test_system_user_connect
iv
ity_manual
.
delay
(
system_user
)
return
Response
({
"task"
:
task
.
id
})
class
SystemUserAssetsListView
(
generics
.
ListAPIView
):
permission_classes
=
(
IsOrgAdmin
,)
serializer_class
=
serializers
.
AssetSerializer
serializer_class
=
serializers
.
AssetS
impleS
erializer
pagination_class
=
LimitOffsetPagination
filter_fields
=
(
"hostname"
,
"ip"
)
http_method_names
=
[
'get'
]
search_fields
=
filter_fields
def
get_object
(
self
):
...
...
@@ -125,7 +126,7 @@ class SystemUserPushToAssetApi(generics.RetrieveAPIView):
return
Response
({
"task"
:
task
.
id
})
class
SystemUserTestAssetConnect
abil
ityApi
(
generics
.
RetrieveAPIView
):
class
SystemUserTestAssetConnect
iv
ityApi
(
generics
.
RetrieveAPIView
):
queryset
=
SystemUser
.
objects
.
all
()
permission_classes
=
(
IsOrgAdmin
,)
...
...
@@ -133,7 +134,7 @@ class SystemUserTestAssetConnectabilityApi(generics.RetrieveAPIView):
system_user
=
self
.
get_object
()
asset_id
=
self
.
kwargs
.
get
(
'aid'
)
asset
=
get_object_or_404
(
Asset
,
id
=
asset_id
)
task
=
test_system_user_connect
abil
ity_a_asset
.
delay
(
system_user
,
asset
)
task
=
test_system_user_connect
iv
ity_a_asset
.
delay
(
system_user
,
asset
)
return
Response
({
"task"
:
task
.
id
})
...
...
apps/assets/forms/user.py
View file @
a5b9b4e1
...
...
@@ -99,8 +99,8 @@ class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm):
auto_generate_key
=
self
.
cleaned_data
.
get
(
'auto_generate_key'
,
False
)
private_key
,
public_key
=
super
()
.
gen_keys
()
if
login_mode
==
SystemUser
.
MANUAL_LOGIN
or
\
protocol
in
[
SystemUser
.
RDP_PROTOCOL
,
SystemUser
.
TELNET_PROTOCOL
]:
if
login_mode
==
SystemUser
.
LOGIN_MANUAL
or
\
protocol
in
[
SystemUser
.
PROTOCOL_RDP
,
SystemUser
.
PROTOCOL_TELNET
]:
system_user
.
auto_push
=
0
auto_generate_key
=
False
system_user
.
save
()
...
...
@@ -124,7 +124,7 @@ class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm):
validated
=
super
()
.
is_valid
()
username
=
self
.
cleaned_data
.
get
(
'username'
)
login_mode
=
self
.
cleaned_data
.
get
(
'login_mode'
)
if
login_mode
==
SystemUser
.
AUTO_LOGIN
and
not
username
:
if
login_mode
==
SystemUser
.
LOGIN_AUTO
and
not
username
:
self
.
add_error
(
"username"
,
_
(
'* Automatic login mode,'
' must fill in the username.'
)
...
...
apps/assets/migrations/0020_auto_20180816_1652.py
View file @
a5b9b4e1
...
...
@@ -13,36 +13,36 @@ class Migration(migrations.Migration):
migrations
.
AlterField
(
model_name
=
'adminuser'
,
name
=
'org_id'
,
field
=
models
.
CharField
(
blank
=
True
,
default
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
),
field
=
models
.
CharField
(
blank
=
True
,
d
b_index
=
True
,
d
efault
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
),
),
migrations
.
AlterField
(
model_name
=
'asset'
,
name
=
'org_id'
,
field
=
models
.
CharField
(
blank
=
True
,
default
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
),
field
=
models
.
CharField
(
blank
=
True
,
d
b_index
=
True
,
d
efault
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
),
),
migrations
.
AlterField
(
model_name
=
'domain'
,
name
=
'org_id'
,
field
=
models
.
CharField
(
blank
=
True
,
default
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
),
field
=
models
.
CharField
(
blank
=
True
,
d
b_index
=
True
,
d
efault
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
),
),
migrations
.
AlterField
(
model_name
=
'gateway'
,
name
=
'org_id'
,
field
=
models
.
CharField
(
blank
=
True
,
default
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
),
field
=
models
.
CharField
(
blank
=
True
,
d
b_index
=
True
,
d
efault
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
),
),
migrations
.
AlterField
(
model_name
=
'label'
,
name
=
'org_id'
,
field
=
models
.
CharField
(
blank
=
True
,
default
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
),
field
=
models
.
CharField
(
blank
=
True
,
d
b_index
=
True
,
d
efault
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
),
),
migrations
.
AlterField
(
model_name
=
'node'
,
name
=
'org_id'
,
field
=
models
.
CharField
(
blank
=
True
,
default
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
),
field
=
models
.
CharField
(
blank
=
True
,
d
b_index
=
True
,
d
efault
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
),
),
migrations
.
AlterField
(
model_name
=
'systemuser'
,
name
=
'org_id'
,
field
=
models
.
CharField
(
blank
=
True
,
default
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
),
field
=
models
.
CharField
(
blank
=
True
,
d
b_index
=
True
,
d
efault
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
),
),
]
apps/assets/migrations/0022_auto_20181012_1717.py
View file @
a5b9b4e1
...
...
@@ -16,7 +16,7 @@ class Migration(migrations.Migration):
migrations
.
CreateModel
(
name
=
'CommandFilter'
,
fields
=
[
(
'org_id'
,
models
.
CharField
(
blank
=
True
,
default
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
)),
(
'org_id'
,
models
.
CharField
(
blank
=
True
,
d
b_index
=
True
,
d
efault
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
)),
(
'id'
,
models
.
UUIDField
(
default
=
uuid
.
uuid4
,
primary_key
=
True
,
serialize
=
False
)),
(
'name'
,
models
.
CharField
(
max_length
=
64
,
verbose_name
=
'Name'
)),
(
'is_active'
,
models
.
BooleanField
(
default
=
True
,
verbose_name
=
'Is active'
)),
...
...
@@ -32,7 +32,7 @@ class Migration(migrations.Migration):
migrations
.
CreateModel
(
name
=
'CommandFilterRule'
,
fields
=
[
(
'org_id'
,
models
.
CharField
(
blank
=
True
,
default
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
)),
(
'org_id'
,
models
.
CharField
(
blank
=
True
,
d
b_index
=
True
,
d
efault
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
)),
(
'id'
,
models
.
UUIDField
(
default
=
uuid
.
uuid4
,
primary_key
=
True
,
serialize
=
False
)),
(
'type'
,
models
.
CharField
(
choices
=
[(
'regex'
,
'Regex'
),
(
'command'
,
'Command'
)],
default
=
'command'
,
max_length
=
16
,
verbose_name
=
'Type'
)),
(
'priority'
,
models
.
IntegerField
(
default
=
50
,
help_text
=
'1-100, the lower will be match first'
,
validators
=
[
django
.
core
.
validators
.
MinValueValidator
(
1
),
django
.
core
.
validators
.
MaxValueValidator
(
100
)],
verbose_name
=
'Priority'
)),
...
...
apps/assets/models/asset.py
View file @
a5b9b4e1
...
...
@@ -13,7 +13,6 @@ from django.db.models import Q
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.core.cache
import
cache
from
..const
import
ASSET_ADMIN_CONN_CACHE_KEY
from
.user
import
AdminUser
,
SystemUser
from
orgs.mixins
import
OrgModelMixin
,
OrgManager
...
...
@@ -75,63 +74,48 @@ class Asset(OrgModelMixin):
protocol
=
models
.
CharField
(
max_length
=
128
,
default
=
SSH_PROTOCOL
,
choices
=
PROTOCOL_CHOICES
,
verbose_name
=
_
(
'Protocol'
))
port
=
models
.
IntegerField
(
default
=
22
,
verbose_name
=
_
(
'Port'
))
platform
=
models
.
CharField
(
max_length
=
128
,
choices
=
PLATFORM_CHOICES
,
default
=
'Linux'
,
verbose_name
=
_
(
'Platform'
))
domain
=
models
.
ForeignKey
(
"assets.Domain"
,
null
=
True
,
blank
=
True
,
related_name
=
'assets'
,
verbose_name
=
_
(
"Domain"
),
on_delete
=
models
.
SET_NULL
)
nodes
=
models
.
ManyToManyField
(
'assets.Node'
,
default
=
default_node
,
related_name
=
'assets'
,
verbose_name
=
_
(
"Nodes"
))
domain
=
models
.
ForeignKey
(
"assets.Domain"
,
null
=
True
,
blank
=
True
,
related_name
=
'assets'
,
verbose_name
=
_
(
"Domain"
),
on_delete
=
models
.
SET_NULL
)
nodes
=
models
.
ManyToManyField
(
'assets.Node'
,
default
=
default_node
,
related_name
=
'assets'
,
verbose_name
=
_
(
"Nodes"
))
is_active
=
models
.
BooleanField
(
default
=
True
,
verbose_name
=
_
(
'Is active'
))
# Auth
admin_user
=
models
.
ForeignKey
(
'assets.AdminUser'
,
on_delete
=
models
.
PROTECT
,
null
=
True
,
verbose_name
=
_
(
"Admin user"
))
admin_user
=
models
.
ForeignKey
(
'assets.AdminUser'
,
on_delete
=
models
.
PROTECT
,
null
=
True
,
verbose_name
=
_
(
"Admin user"
))
# Some information
public_ip
=
models
.
GenericIPAddressField
(
max_length
=
32
,
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'Public IP'
))
number
=
models
.
CharField
(
max_length
=
32
,
null
=
True
,
blank
=
True
,
verbose_name
=
_
(
'Asset number'
))
# Collect
vendor
=
models
.
CharField
(
max_length
=
64
,
null
=
True
,
blank
=
True
,
verbose_name
=
_
(
'Vendor'
))
model
=
models
.
CharField
(
max_length
=
54
,
null
=
True
,
blank
=
True
,
verbose_name
=
_
(
'Model'
))
sn
=
models
.
CharField
(
max_length
=
128
,
null
=
True
,
blank
=
True
,
verbose_name
=
_
(
'Serial number'
))
cpu_model
=
models
.
CharField
(
max_length
=
64
,
null
=
True
,
blank
=
True
,
verbose_name
=
_
(
'CPU model'
))
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'
))
cpu_vcpus
=
models
.
IntegerField
(
null
=
True
,
verbose_name
=
_
(
'CPU vcpus'
))
memory
=
models
.
CharField
(
max_length
=
64
,
null
=
True
,
blank
=
True
,
verbose_name
=
_
(
'Memory'
))
disk_total
=
models
.
CharField
(
max_length
=
1024
,
null
=
True
,
blank
=
True
,
verbose_name
=
_
(
'Disk total'
))
disk_info
=
models
.
CharField
(
max_length
=
1024
,
null
=
True
,
blank
=
True
,
verbose_name
=
_
(
'Disk info'
))
os
=
models
.
CharField
(
max_length
=
128
,
null
=
True
,
blank
=
True
,
verbose_name
=
_
(
'OS'
))
os_version
=
models
.
CharField
(
max_length
=
16
,
null
=
True
,
blank
=
True
,
verbose_name
=
_
(
'OS version'
))
os_arch
=
models
.
CharField
(
max_length
=
16
,
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'OS arch'
))
hostname_raw
=
models
.
CharField
(
max_length
=
128
,
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'Hostname raw'
))
labels
=
models
.
ManyToManyField
(
'assets.Label'
,
blank
=
True
,
related_name
=
'assets'
,
verbose_name
=
_
(
"Labels"
))
created_by
=
models
.
CharField
(
max_length
=
32
,
null
=
True
,
blank
=
True
,
verbose_name
=
_
(
'Created by'
))
date_created
=
models
.
DateTimeField
(
auto_now_add
=
True
,
null
=
True
,
blank
=
True
,
verbose_name
=
_
(
'Date created'
))
comment
=
models
.
TextField
(
max_length
=
128
,
default
=
''
,
blank
=
True
,
verbose_name
=
_
(
'Comment'
))
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'
))
os
=
models
.
CharField
(
max_length
=
128
,
null
=
True
,
blank
=
True
,
verbose_name
=
_
(
'OS'
))
os_version
=
models
.
CharField
(
max_length
=
16
,
null
=
True
,
blank
=
True
,
verbose_name
=
_
(
'OS version'
))
os_arch
=
models
.
CharField
(
max_length
=
16
,
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'OS arch'
))
hostname_raw
=
models
.
CharField
(
max_length
=
128
,
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'Hostname raw'
))
labels
=
models
.
ManyToManyField
(
'assets.Label'
,
blank
=
True
,
related_name
=
'assets'
,
verbose_name
=
_
(
"Labels"
))
created_by
=
models
.
CharField
(
max_length
=
32
,
null
=
True
,
blank
=
True
,
verbose_name
=
_
(
'Created by'
))
date_created
=
models
.
DateTimeField
(
auto_now_add
=
True
,
null
=
True
,
blank
=
True
,
verbose_name
=
_
(
'Date created'
))
comment
=
models
.
TextField
(
max_length
=
128
,
default
=
''
,
blank
=
True
,
verbose_name
=
_
(
'Comment'
))
objects
=
OrgManager
.
from_queryset
(
AssetQuerySet
)()
CONNECTIVITY_CACHE_KEY
=
'_JMS_ASSET_CONNECTIVITY_{}'
UNREACHABLE
,
REACHABLE
,
UNKNOWN
=
range
(
0
,
3
)
CONNECTIVITY_CHOICES
=
(
(
UNREACHABLE
,
_
(
"Unreachable"
)),
(
REACHABLE
,
_
(
'Reachable'
)),
(
UNKNOWN
,
_
(
"Unknown"
)),
)
def
__str__
(
self
):
return
'{0.hostname}({0.ip})'
.
format
(
self
)
...
...
@@ -197,25 +181,17 @@ class Asset(OrgModelMixin):
return
''
@property
def
is_connective
(
self
):
def
connectivity
(
self
):
if
not
self
.
is_unixlike
():
return
True
val
=
cache
.
get
(
ASSET_ADMIN_CONN_CACHE_KEY
.
format
(
self
.
hostname
))
if
val
==
1
:
return
True
else
:
return
False
return
self
.
UNKNOWN
key
=
self
.
CONNECTIVITY_CACHE_KEY
.
format
(
str
(
self
.
id
))
cached
=
cache
.
get
(
key
,
None
)
return
cached
if
cached
is
not
None
else
self
.
UNKNOWN
def
to_json
(
self
):
info
=
{
'id'
:
self
.
id
,
'hostname'
:
self
.
hostname
,
'ip'
:
self
.
ip
,
'port'
:
self
.
port
,
}
if
self
.
domain
and
self
.
domain
.
gateway_set
.
all
():
info
[
"gateways"
]
=
[
d
.
id
for
d
in
self
.
domain
.
gateway_set
.
all
()]
return
info
@connectivity.setter
def
connectivity
(
self
,
value
):
key
=
self
.
CONNECTIVITY_CACHE_KEY
.
format
(
str
(
self
.
id
))
cache
.
set
(
key
,
value
,
3600
*
2
)
def
get_auth_info
(
self
):
if
self
.
admin_user
:
...
...
@@ -236,11 +212,20 @@ class Asset(OrgModelMixin):
fake_node
.
is_node
=
False
return
fake_node
def
to_json
(
self
):
info
=
{
'id'
:
self
.
id
,
'hostname'
:
self
.
hostname
,
'ip'
:
self
.
ip
,
'port'
:
self
.
port
,
}
if
self
.
domain
and
self
.
domain
.
gateway_set
.
all
():
info
[
"gateways"
]
=
[
d
.
id
for
d
in
self
.
domain
.
gateway_set
.
all
()]
return
info
def
_to_secret_json
(
self
):
"""
Ansible use it create inventory, First using asset user,
otherwise using cluster admin user
Ansible use it create inventory
Todo: May be move to ops implements it
"""
data
=
self
.
to_json
()
...
...
@@ -255,6 +240,36 @@ class Asset(OrgModelMixin):
})
return
data
def
as_tree_node
(
self
,
parent_node
):
from
common.tree
import
TreeNode
icon_skin
=
'file'
if
self
.
platform
.
lower
()
==
'windows'
:
icon_skin
=
'windows'
elif
self
.
platform
.
lower
()
==
'linux'
:
icon_skin
=
'linux'
data
=
{
'id'
:
str
(
self
.
id
),
'name'
:
self
.
hostname
,
'title'
:
self
.
ip
,
'pId'
:
parent_node
.
key
,
'isParent'
:
False
,
'open'
:
False
,
'iconSkin'
:
icon_skin
,
'meta'
:
{
'type'
:
'asset'
,
'asset'
:
{
'id'
:
self
.
id
,
'hostname'
:
self
.
hostname
,
'ip'
:
self
.
ip
,
'port'
:
self
.
port
,
'platform'
:
self
.
platform
,
'protocol'
:
self
.
protocol
,
}
}
}
tree_node
=
TreeNode
(
**
data
)
return
tree_node
class
Meta
:
unique_together
=
[(
'org_id'
,
'hostname'
)]
verbose_name
=
_
(
"Asset"
)
...
...
apps/assets/models/base.py
View file @
a5b9b4e1
...
...
@@ -29,6 +29,13 @@ class AssetUser(OrgModelMixin):
date_updated
=
models
.
DateTimeField
(
auto_now
=
True
)
created_by
=
models
.
CharField
(
max_length
=
128
,
null
=
True
,
verbose_name
=
_
(
'Created by'
))
UNREACHABLE
,
REACHABLE
,
UNKNOWN
=
range
(
0
,
3
)
CONNECTIVITY_CHOICES
=
(
(
UNREACHABLE
,
_
(
"Unreachable"
)),
(
REACHABLE
,
_
(
'Reachable'
)),
(
UNKNOWN
,
_
(
"Unknown"
)),
)
@property
def
password
(
self
):
if
self
.
_password
:
...
...
apps/assets/models/node.py
View file @
a5b9b4e1
...
...
@@ -5,6 +5,7 @@ import uuid
from
django.db
import
models
,
transaction
from
django.db.models
import
Q
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.utils.translation
import
ugettext
from
django.core.cache
import
cache
from
orgs.mixins
import
OrgModelMixin
...
...
@@ -22,7 +23,9 @@ class Node(OrgModelMixin):
date_create
=
models
.
DateTimeField
(
auto_now_add
=
True
)
is_node
=
True
_full_value_cache_key_prefix
=
'_NODE_VALUE_{}'
_assets_amount
=
None
_full_value_cache_key
=
'_NODE_VALUE_{}'
_assets_amount_cache_key
=
'_NODE_ASSETS_AMOUNT_{}'
class
Meta
:
verbose_name
=
_
(
"Node"
)
...
...
@@ -49,30 +52,65 @@ class Node(OrgModelMixin):
def
name
(
self
):
return
self
.
value
@property
def
assets_amount
(
self
):
"""
获取节点下所有资产数量速度太慢,所以需要重写,使用cache等方案
:return:
"""
if
self
.
_assets_amount
is
not
None
:
return
self
.
_assets_amount
cache_key
=
self
.
_assets_amount_cache_key
.
format
(
self
.
key
)
cached
=
cache
.
get
(
cache_key
)
if
cached
is
not
None
:
return
cached
assets_amount
=
self
.
get_all_assets
()
.
count
()
cache
.
set
(
cache_key
,
assets_amount
,
3600
)
return
assets_amount
@assets_amount.setter
def
assets_amount
(
self
,
value
):
self
.
_assets_amount
=
value
def
expire_assets_amount
(
self
):
ancestor_keys
=
self
.
get_ancestor_keys
(
with_self
=
True
)
cache_keys
=
[
self
.
_assets_amount_cache_key
.
format
(
k
)
for
k
in
ancestor_keys
]
cache
.
delete_many
(
cache_keys
)
@classmethod
def
expire_nodes_assets_amount
(
cls
,
nodes
=
None
):
if
nodes
:
for
node
in
nodes
:
node
.
expire_assets_amount
()
return
key
=
cls
.
_assets_amount_cache_key
.
format
(
'*'
)
cache
.
delete_pattern
(
key
)
@property
def
full_value
(
self
):
key
=
self
.
_full_value_cache_key
_prefix
.
format
(
self
.
key
)
key
=
self
.
_full_value_cache_key
.
format
(
self
.
key
)
cached
=
cache
.
get
(
key
)
if
cached
:
return
cached
value
=
self
.
get_full_value
()
self
.
cache_full_value
(
value
)
return
value
def
get_full_value
(
self
):
# ancestor = [a.value for a in self.get_ancestor(with_self=True)]
if
self
.
is_root
():
return
self
.
value
parent_full_value
=
self
.
parent
.
full_value
value
=
parent_full_value
+
' / '
+
self
.
value
return
value
def
cache_full_value
(
self
,
value
):
key
=
self
.
_full_value_cache_key_prefix
.
format
(
self
.
key
)
key
=
self
.
_full_value_cache_key
.
format
(
self
.
key
)
cache
.
set
(
key
,
value
,
3600
)
return
value
def
expire_full_value
(
self
):
key
=
self
.
_full_value_cache_key_prefix
.
format
(
self
.
key
)
key
=
self
.
_full_value_cache_key
.
format
(
self
.
key
)
cache
.
delete_pattern
(
key
+
'*'
)
@classmethod
def
expire_nodes_full_value
(
cls
,
nodes
=
None
):
if
nodes
:
for
node
in
nodes
:
node
.
expire_full_value
()
return
key
=
cls
.
_full_value_cache_key
.
format
(
'*'
)
cache
.
delete_pattern
(
key
+
'*'
)
@property
...
...
@@ -85,6 +123,17 @@ class Node(OrgModelMixin):
self
.
save
()
return
"{}:{}"
.
format
(
self
.
key
,
mark
)
def
get_next_child_preset_name
(
self
):
name
=
ugettext
(
"New node"
)
values
=
[
child
.
value
[
child
.
value
.
rfind
(
' '
):]
for
child
in
self
.
get_children
()
if
child
.
value
.
startswith
(
name
)
]
values
=
[
int
(
value
)
for
value
in
values
if
value
.
strip
()
.
isdigit
()]
count
=
max
(
values
)
+
1
if
values
else
1
return
'{} {}'
.
format
(
name
,
count
)
def
create_child
(
self
,
value
):
with
transaction
.
atomic
():
child_key
=
self
.
get_next_child_key
()
...
...
@@ -134,7 +183,7 @@ class Node(OrgModelMixin):
pattern
=
r'^{0}$|^{0}:'
.
format
(
self
.
key
)
args
=
[]
kwargs
=
{}
if
self
.
is_
default_node
():
if
self
.
is_
root
():
args
.
append
(
Q
(
nodes__key__regex
=
pattern
)
|
Q
(
nodes
=
None
))
else
:
kwargs
[
'nodes__key__regex'
]
=
pattern
...
...
@@ -182,17 +231,18 @@ class Node(OrgModelMixin):
child
.
save
()
self
.
save
()
def
get_ancestor
(
self
,
with_self
=
False
):
if
self
.
is_root
():
root
=
self
.
__class__
.
root
()
return
[
root
]
_key
=
self
.
key
.
split
(
':'
)
def
get_ancestor_keys
(
self
,
with_self
=
False
):
parent_keys
=
[]
key_list
=
self
.
key
.
split
(
":"
)
if
not
with_self
:
_key
.
pop
()
ancestor_keys
=
[]
for
i
in
range
(
len
(
_key
)):
ancestor_keys
.
append
(
':'
.
join
(
_key
))
_key
.
pop
()
key_list
.
pop
()
for
i
in
range
(
len
(
key_list
)):
parent_keys
.
append
(
":"
.
join
(
key_list
))
key_list
.
pop
()
return
parent_keys
def
get_ancestor
(
self
,
with_self
=
False
):
ancestor_keys
=
self
.
get_ancestor_keys
(
with_self
=
with_self
)
ancestor
=
self
.
__class__
.
objects
.
filter
(
key__in
=
ancestor_keys
)
.
order_by
(
'key'
)
...
...
@@ -227,9 +277,25 @@ class Node(OrgModelMixin):
defaults
=
{
'value'
:
'Default'
}
return
cls
.
objects
.
get_or_create
(
defaults
=
defaults
,
key
=
'1'
)
@classmethod
def
get_tree_name_ref
(
cls
):
pass
def
as_tree_node
(
self
):
from
common.tree
import
TreeNode
from
..serializers
import
NodeSerializer
name
=
'{} ({})'
.
format
(
self
.
value
,
self
.
assets_amount
)
node_serializer
=
NodeSerializer
(
instance
=
self
)
data
=
{
'id'
:
self
.
key
,
'name'
:
name
,
'title'
:
name
,
'pId'
:
self
.
parent_key
,
'isParent'
:
True
,
'open'
:
self
.
is_root
(),
'meta'
:
{
'node'
:
node_serializer
.
data
,
'type'
:
'node'
}
}
tree_node
=
TreeNode
(
**
data
)
return
tree_node
@classmethod
def
generate_fake
(
cls
,
count
=
100
):
...
...
apps/assets/models/user.py
View file @
a5b9b4e1
...
...
@@ -14,7 +14,7 @@ from ..const import SYSTEM_USER_CONN_CACHE_KEY
from
.base
import
AssetUser
__all__
=
[
'AdminUser'
,
'SystemUser'
,
]
__all__
=
[
'AdminUser'
,
'SystemUser'
]
logger
=
logging
.
getLogger
(
__name__
)
signer
=
get_signer
()
...
...
@@ -31,6 +31,7 @@ class AdminUser(AssetUser):
become_method
=
models
.
CharField
(
choices
=
BECOME_METHOD_CHOICES
,
default
=
'sudo'
,
max_length
=
4
)
become_user
=
models
.
CharField
(
default
=
'root'
,
max_length
=
64
)
_become_pass
=
models
.
CharField
(
default
=
''
,
max_length
=
128
)
CONNECTIVE_CACHE_KEY
=
'_JMS_ADMIN_USER_CONNECTIVE_{}'
def
__str__
(
self
):
return
self
.
name
...
...
@@ -67,6 +68,23 @@ class AdminUser(AssetUser):
def
assets_amount
(
self
):
return
self
.
get_related_assets
()
.
count
()
@property
def
connectivity
(
self
):
from
.asset
import
Asset
assets
=
self
.
get_related_assets
()
.
values_list
(
'id'
,
'hostname'
,
flat
=
True
)
data
=
{
'unreachable'
:
[],
'reachable'
:
[],
}
for
asset_id
,
hostname
in
assets
:
key
=
Asset
.
CONNECTIVITY_CACHE_KEY
.
format
(
str
(
self
.
id
))
value
=
cache
.
get
(
key
,
Asset
.
UNKNOWN
)
if
value
==
Asset
.
REACHABLE
:
data
[
'reachable'
]
.
append
(
hostname
)
elif
value
==
Asset
.
UNREACHABLE
:
data
[
'unreachable'
]
.
append
(
hostname
)
return
data
class
Meta
:
ordering
=
[
'name'
]
unique_together
=
[(
'name'
,
'org_id'
)]
...
...
@@ -94,34 +112,34 @@ class AdminUser(AssetUser):
class
SystemUser
(
AssetUser
):
SSH_PROTOCOL
=
'ssh'
RDP_PROTOCOL
=
'rdp'
TELNET_PROTOCOL
=
'telnet'
PROTOCOL_SSH
=
'ssh'
PROTOCOL_RDP
=
'rdp'
PROTOCOL_TELNET
=
'telnet'
PROTOCOL_CHOICES
=
(
(
SSH_PROTOCOL
,
'ssh'
),
(
RDP_PROTOCOL
,
'rdp'
),
(
TELNET_PROTOCOL
,
'telnet (beta)'
),
(
PROTOCOL_SSH
,
'ssh'
),
(
PROTOCOL_RDP
,
'rdp'
),
(
PROTOCOL_TELNET
,
'telnet (beta)'
),
)
AUTO_LOGIN
=
'auto'
MANUAL_LOGIN
=
'manual'
LOGIN_AUTO
=
'auto'
LOGIN_MANUAL
=
'manual'
LOGIN_MODE_CHOICES
=
(
(
AUTO_LOGIN
,
_
(
'Automatic login'
)),
(
MANUAL_LOGIN
,
_
(
'Manually login'
))
(
LOGIN_AUTO
,
_
(
'Automatic login'
)),
(
LOGIN_MANUAL
,
_
(
'Manually login'
))
)
nodes
=
models
.
ManyToManyField
(
'assets.Node'
,
blank
=
True
,
verbose_name
=
_
(
"Nodes"
))
assets
=
models
.
ManyToManyField
(
'assets.Asset'
,
blank
=
True
,
verbose_name
=
_
(
"Assets"
))
priority
=
models
.
IntegerField
(
default
=
20
,
verbose_name
=
_
(
"Priority"
),
validators
=
[
MinValueValidator
(
1
),
MaxValueValidator
(
100
)])
priority
=
models
.
IntegerField
(
default
=
20
,
verbose_name
=
_
(
"Priority"
),
validators
=
[
MinValueValidator
(
1
),
MaxValueValidator
(
100
)])
protocol
=
models
.
CharField
(
max_length
=
16
,
choices
=
PROTOCOL_CHOICES
,
default
=
'ssh'
,
verbose_name
=
_
(
'Protocol'
))
auto_push
=
models
.
BooleanField
(
default
=
True
,
verbose_name
=
_
(
'Auto push'
))
sudo
=
models
.
TextField
(
default
=
'/bin/whoami'
,
verbose_name
=
_
(
'Sudo'
))
shell
=
models
.
CharField
(
max_length
=
64
,
default
=
'/bin/bash'
,
verbose_name
=
_
(
'Shell'
))
login_mode
=
models
.
CharField
(
choices
=
LOGIN_MODE_CHOICES
,
default
=
AUTO_LOGIN
,
max_length
=
10
,
verbose_name
=
_
(
'Login mode'
))
login_mode
=
models
.
CharField
(
choices
=
LOGIN_MODE_CHOICES
,
default
=
LOGIN_AUTO
,
max_length
=
10
,
verbose_name
=
_
(
'Login mode'
))
cmd_filters
=
models
.
ManyToManyField
(
'CommandFilter'
,
related_name
=
'system_users'
,
verbose_name
=
_
(
"Command filter"
),
blank
=
True
)
cache_key
=
"__SYSTEM_USER_CACHED_{}"
SYSTEM_USER_CACHE_KEY
=
"__SYSTEM_USER_CACHED_{}"
CONNECTIVE_CACHE_KEY
=
'_JMS_SYSTEM_USER_CONNECTIVE_{}'
def
__str__
(
self
):
return
'{0.name}({0.username})'
.
format
(
self
)
...
...
@@ -136,34 +154,61 @@ class SystemUser(AssetUser):
'auto_push'
:
self
.
auto_push
,
}
def
get_assets
(
self
):
def
get_
related_
assets
(
self
):
assets
=
set
(
self
.
assets
.
all
())
return
assets
@property
def
assets_connective
(
self
):
_result
=
cache
.
get
(
SYSTEM_USER_CONN_CACHE_KEY
.
format
(
self
.
name
),
{})
return
_result
def
connectivity
(
self
):
cache_key
=
self
.
CONNECTIVE_CACHE_KEY
.
format
(
str
(
self
.
id
))
value
=
cache
.
get
(
cache_key
,
None
)
if
not
value
or
'unreachable'
not
in
value
:
return
{
'unreachable'
:
[],
'reachable'
:
[]}
else
:
return
value
@connectivity.setter
def
connectivity
(
self
,
value
):
data
=
self
.
connectivity
unreachable
=
data
[
'unreachable'
]
reachable
=
data
[
'reachable'
]
for
host
in
value
.
get
(
'dark'
,
{})
.
keys
():
if
host
not
in
unreachable
:
unreachable
.
append
(
host
)
if
host
in
reachable
:
reachable
.
remove
(
host
)
for
host
in
value
.
get
(
'contacted'
):
if
host
not
in
reachable
:
reachable
.
append
(
host
)
if
host
in
unreachable
:
unreachable
.
remove
(
host
)
cache_key
=
self
.
CONNECTIVE_CACHE_KEY
.
format
(
str
(
self
.
id
))
cache
.
set
(
cache_key
,
data
,
3600
)
@property
def
assets_unreachable
(
self
):
return
self
.
connectivity
.
get
(
'unreachable'
)
@property
def
unreachable_assets
(
self
):
return
list
(
self
.
assets_connective
.
get
(
'dark'
,
{})
.
keys
()
)
def
assets_reachable
(
self
):
return
self
.
connectivity
.
get
(
'reachable'
)
@property
def
reachable_assets
(
self
):
return
self
.
assets_connective
.
get
(
'contacted'
,
[]
)
def
login_mode_display
(
self
):
return
self
.
get_login_mode_display
(
)
def
is_need_push
(
self
):
if
self
.
auto_push
and
self
.
protocol
==
self
.
__class__
.
SSH_PROTOCOL
:
if
self
.
auto_push
and
self
.
protocol
==
self
.
PROTOCOL_SSH
:
return
True
else
:
return
False
def
set_cache
(
self
):
cache
.
set
(
self
.
cache_key
.
format
(
self
.
id
),
self
,
3600
)
cache
.
set
(
self
.
SYSTEM_USER_CACHE_KEY
.
format
(
self
.
id
),
self
,
3600
)
def
expire_cache
(
self
):
cache
.
delete
(
self
.
cache_key
.
format
(
self
.
id
))
cache
.
delete
(
self
.
SYSTEM_USER_CACHE_KEY
.
format
(
self
.
id
))
@property
def
cmd_filter_rules
(
self
):
...
...
@@ -184,7 +229,7 @@ class SystemUser(AssetUser):
@classmethod
def
get_system_user_by_id_or_cached
(
cls
,
sid
):
cached
=
cache
.
get
(
cls
.
cache_key
.
format
(
sid
))
cached
=
cache
.
get
(
cls
.
SYSTEM_USER_CACHE_KEY
.
format
(
sid
))
if
cached
:
return
cached
try
:
...
...
apps/assets/serializers/asset.py
View file @
a5b9b4e1
...
...
@@ -9,6 +9,7 @@ from .system_user import AssetSystemUserSerializer
__all__
=
[
'AssetSerializer'
,
'AssetGrantedSerializer'
,
'MyAssetGrantedSerializer'
,
'AssetAsNodeSerializer'
,
'AssetSimpleSerializer'
,
]
...
...
@@ -22,14 +23,27 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
fields
=
'__all__'
validators
=
[]
@classmethod
def
setup_eager_loading
(
cls
,
queryset
):
""" Perform necessary eager loading of data. """
queryset
=
queryset
.
prefetch_related
(
'labels'
,
'nodes'
)
\
.
select_related
(
'admin_user'
)
return
queryset
def
get_field_names
(
self
,
declared_fields
,
info
):
fields
=
super
()
.
get_field_names
(
declared_fields
,
info
)
fields
.
extend
([
'hardware_info'
,
'
is_connective
'
,
'org_name'
'hardware_info'
,
'
connectivity
'
,
'org_name'
])
return
fields
class
AssetAsNodeSerializer
(
serializers
.
ModelSerializer
):
class
Meta
:
model
=
Asset
fields
=
[
'id'
,
'hostname'
,
'ip'
,
'port'
,
'platform'
,
'protocol'
]
class
AssetGrantedSerializer
(
serializers
.
ModelSerializer
):
"""
被授权资产的数据结构
...
...
@@ -64,3 +78,9 @@ class MyAssetGrantedSerializer(AssetGrantedSerializer):
"is_active"
,
"system_users_join"
,
"org_name"
,
"os"
,
"platform"
,
"comment"
,
"org_id"
,
"protocol"
)
class
AssetSimpleSerializer
(
serializers
.
ModelSerializer
):
class
Meta
:
model
=
Asset
fields
=
[
'id'
,
'hostname'
,
'port'
,
'ip'
,
'connectivity'
]
apps/assets/serializers/node.py
View file @
a5b9b4e1
...
...
@@ -8,84 +8,33 @@ from .asset import AssetGrantedSerializer
__all__
=
[
'NodeSerializer'
,
"Node
GrantedSerializer"
,
"Node
AddChildrenSerializer"
,
'NodeSerializer'
,
"NodeAddChildrenSerializer"
,
"NodeAssetsSerializer"
,
]
class
NodeGrantedSerializer
(
BulkSerializerMixin
,
serializers
.
ModelSerializer
):
"""
授权资产组
"""
assets_granted
=
AssetGrantedSerializer
(
many
=
True
,
read_only
=
True
)
assets_amount
=
serializers
.
SerializerMethodField
()
parent
=
serializers
.
SerializerMethodField
()
name
=
serializers
.
SerializerMethodField
()
class
Meta
:
model
=
Node
fields
=
[
'id'
,
'key'
,
'name'
,
'value'
,
'parent'
,
'assets_granted'
,
'assets_amount'
,
'org_id'
,
]
@staticmethod
def
get_assets_amount
(
obj
):
return
len
(
obj
.
assets_granted
)
@staticmethod
def
get_name
(
obj
):
return
obj
.
name
@staticmethod
def
get_parent
(
obj
):
return
obj
.
parent
.
id
class
NodeSerializer
(
serializers
.
ModelSerializer
):
assets_amount
=
serializers
.
SerializerMethodField
()
tree_id
=
serializers
.
SerializerMethodField
()
tree_parent
=
serializers
.
SerializerMethodField
()
assets_amount
=
serializers
.
IntegerField
(
read_only
=
True
)
class
Meta
:
model
=
Node
fields
=
[
'id'
,
'key'
,
'value'
,
'assets_amount'
,
'is_node'
,
'org_id'
,
'tree_id'
,
'tree_parent'
,
'id'
,
'key'
,
'value'
,
'assets_amount'
,
'org_id'
,
]
read_only_fields
=
[
'id'
,
'key'
,
'assets_amount'
,
'org_id'
,
]
list_serializer_class
=
BulkListSerializer
def
validate
(
self
,
data
):
value
=
data
.
get
(
'value'
)
def
validate_value
(
self
,
data
):
instance
=
self
.
instance
if
self
.
instance
else
Node
.
root
()
children
=
instance
.
parent
.
get_children
()
.
exclude
(
key
=
instance
.
key
)
values
=
[
child
.
value
for
child
in
children
]
if
value
in
values
:
if
data
in
values
:
raise
serializers
.
ValidationError
(
'The same level node name cannot be the same'
)
return
data
@staticmethod
def
get_assets_amount
(
obj
):
if
hasattr
(
obj
,
'assets_amount'
):
return
obj
.
assets_amount
return
obj
.
get_all_assets
()
.
count
()
@staticmethod
def
get_tree_id
(
obj
):
return
obj
.
key
@staticmethod
def
get_tree_parent
(
obj
):
return
obj
.
parent_key
def
get_fields
(
self
):
fields
=
super
()
.
get_fields
()
field
=
fields
[
"key"
]
field
.
required
=
False
return
fields
class
NodeAssetsSerializer
(
serializers
.
ModelSerializer
):
assets
=
serializers
.
PrimaryKeyRelatedField
(
many
=
True
,
queryset
=
Asset
.
objects
.
all
())
...
...
@@ -97,3 +46,4 @@ class NodeAssetsSerializer(serializers.ModelSerializer):
class
NodeAddChildrenSerializer
(
serializers
.
Serializer
):
nodes
=
serializers
.
ListField
()
apps/assets/serializers/system_user.py
View file @
a5b9b4e1
from
rest_framework
import
serializers
from
..models
import
SystemUser
from
..models
import
SystemUser
,
Asset
from
.base
import
AuthSerializer
...
...
@@ -21,17 +21,17 @@ class SystemUserSerializer(serializers.ModelSerializer):
def
get_field_names
(
self
,
declared_fields
,
info
):
fields
=
super
(
SystemUserSerializer
,
self
)
.
get_field_names
(
declared_fields
,
info
)
fields
.
extend
([
'
get_
login_mode_display'
,
'login_mode_display'
,
])
return
fields
@staticmethod
def
get_unreachable_assets
(
obj
):
return
obj
.
unreachable_assets
return
obj
.
assets_unreachable
@staticmethod
def
get_reachable_assets
(
obj
):
return
obj
.
reachable_assets
return
obj
.
assets_reachable
def
get_unreachable_amount
(
self
,
obj
):
return
len
(
self
.
get_unreachable_assets
(
obj
))
...
...
@@ -41,7 +41,7 @@ class SystemUserSerializer(serializers.ModelSerializer):
@staticmethod
def
get_assets_amount
(
obj
):
return
len
(
obj
.
get_assets
())
return
len
(
obj
.
get_
related_
assets
())
class
SystemUserAuthSerializer
(
AuthSerializer
):
...
...
@@ -76,3 +76,6 @@ class SystemUserSimpleSerializer(serializers.ModelSerializer):
class
Meta
:
model
=
SystemUser
fields
=
(
'id'
,
'name'
,
'username'
)
apps/assets/signals_handler.py
View file @
a5b9b4e1
# -*- coding: utf-8 -*-
#
from
collections
import
defaultdict
from
django.db.models.signals
import
post_save
,
m2m_changed
from
django.db.models.signals
import
post_save
,
m2m_changed
,
post_delete
from
django.dispatch
import
receiver
from
common.utils
import
get_logger
from
.models
import
Asset
,
SystemUser
,
Node
from
.tasks
import
update_assets_hardware_info_util
,
\
test_asset_connect
abil
ity_util
,
push_system_user_to_assets
test_asset_connect
iv
ity_util
,
push_system_user_to_assets
logger
=
get_logger
(
__file__
)
...
...
@@ -19,8 +19,8 @@ def update_asset_hardware_info_on_created(asset):
def
test_asset_conn_on_created
(
asset
):
logger
.
debug
(
"Test asset `{}` connect
abil
ity"
.
format
(
asset
))
test_asset_connect
abil
ity_util
.
delay
([
asset
])
logger
.
debug
(
"Test asset `{}` connect
iv
ity"
.
format
(
asset
))
test_asset_connect
iv
ity_util
.
delay
([
asset
])
def
set_asset_root_node
(
asset
):
...
...
@@ -35,6 +35,17 @@ def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
update_asset_hardware_info_on_created
(
instance
)
test_asset_conn_on_created
(
instance
)
# 过期节点资产数量
nodes
=
instance
.
nodes
.
all
()
Node
.
expire_nodes_assets_amount
(
nodes
)
@receiver
(
post_delete
,
sender
=
Asset
,
dispatch_uid
=
"my_unique_identifier"
)
def
on_asset_delete
(
sender
,
instance
=
None
,
**
kwargs
):
# 过期节点资产数量
nodes
=
instance
.
nodes
.
all
()
Node
.
expire_nodes_assets_amount
(
nodes
)
@receiver
(
post_save
,
sender
=
SystemUser
,
dispatch_uid
=
"my_unique_identifier"
)
def
on_system_user_update
(
sender
,
instance
=
None
,
created
=
True
,
**
kwargs
):
...
...
@@ -63,10 +74,11 @@ def on_system_user_assets_change(sender, instance=None, **kwargs):
@receiver
(
m2m_changed
,
sender
=
Asset
.
nodes
.
through
)
def
on_asset_node_changed
(
sender
,
instance
=
None
,
**
kwargs
):
logger
.
debug
(
"Asset node change signal received"
)
if
isinstance
(
instance
,
Asset
):
if
kwargs
[
'action'
]
==
'post_add'
:
logger
.
debug
(
"Asset node change signal received"
)
nodes
=
kwargs
[
'model'
]
.
objects
.
filter
(
pk__in
=
kwargs
[
'pk_set'
])
Node
.
expire_nodes_assets_amount
(
nodes
)
system_users_assets
=
defaultdict
(
set
)
system_users
=
SystemUser
.
objects
.
filter
(
nodes__in
=
nodes
)
# 清理节点缓存
...
...
@@ -79,9 +91,11 @@ def on_asset_node_changed(sender, instance=None, **kwargs):
@receiver
(
m2m_changed
,
sender
=
Asset
.
nodes
.
through
)
def
on_node_assets_changed
(
sender
,
instance
=
None
,
**
kwargs
):
if
isinstance
(
instance
,
Node
):
logger
.
debug
(
"Node assets change signal received"
)
# 当节点和资产关系发生改变时,过期资产数量缓存
instance
.
expire_assets_amount
()
assets
=
kwargs
[
'model'
]
.
objects
.
filter
(
pk__in
=
kwargs
[
'pk_set'
])
if
kwargs
[
'action'
]
==
'post_add'
:
logger
.
debug
(
"Node assets change signal received"
)
# 重新关联系统用户和资产的关系
system_users
=
SystemUser
.
objects
.
filter
(
nodes
=
instance
)
for
system_user
in
system_users
:
...
...
apps/assets/tasks.py
View file @
a5b9b4e1
This diff is collapsed.
Click to expand it.
apps/assets/templates/assets/_asset_list_modal.html
View file @
a5b9b4e1
...
...
@@ -57,6 +57,10 @@
<script>
var
zTree2
,
asset_table2
=
0
;
function
initTable2
()
{
if
(
asset_table2
){
return
}
var
options
=
{
ele
:
$
(
'#asset_list_modal_table'
),
ajax_url
:
'{% url "api-assets:asset-list" %}?show_current_asset=1'
,
...
...
@@ -71,14 +75,14 @@ function initTable2() {
function
onSelected2
(
event
,
treeNode
)
{
var
url
=
asset_table2
.
ajax
.
url
();
url
=
setUrlParam
(
url
,
"node_id"
,
treeNode
.
node_id
);
setCookie
(
'node_selected'
,
treeNode
.
id
);
url
=
setUrlParam
(
url
,
"node_id"
,
treeNode
.
meta
.
node
.
id
);
asset_table2
.
ajax
.
url
(
url
);
asset_table2
.
ajax
.
reload
();
}
function
initTree2
()
{
var
url
=
'{% url '
api
-
assets
:
node
-
children
-
tree
' %}?assets=0'
;
var
setting
=
{
view
:
{
dblClickExpand
:
false
,
...
...
@@ -89,33 +93,22 @@ function initTree2() {
enable
:
true
}
},
async
:
{
enable
:
true
,
url
:
url
,
autoParam
:
[
"id=key"
,
"name=n"
,
"level=lv"
],
type
:
'get'
},
callback
:
{
onSelected
:
onSelected2
}
};
var
zNodes
=
[];
$
.
get
(
"{% url 'api-assets:node-list' %}"
,
function
(
data
,
status
){
$
.
each
(
data
,
function
(
index
,
value
)
{
value
[
"node_id"
]
=
value
[
"id"
];
value
[
"id"
]
=
value
[
"tree_id"
];
value
[
"pId"
]
=
value
[
"tree_parent"
];
{
#
value
[
"open"
]
=
true
;
#
}
if
(
value
[
"key"
]
===
"0"
)
{
value
[
"open"
]
=
true
;
}
value
[
"name"
]
=
value
[
"value"
]
+
' ('
+
value
[
'assets_amount'
]
+
')'
;
});
zNodes
=
data
;
$
.
fn
.
zTree
.
init
(
$
(
"#assetTree2"
),
setting
,
zNodes
);
zTree2
=
$
.
fn
.
zTree
.
getZTreeObj
(
"assetTree2"
);
var
root
=
zTree2
.
getNodes
()[
0
];
zTree2
.
expandNode
(
root
);
});
zTree2
=
$
.
fn
.
zTree
.
init
(
$
(
"#assetTree2"
),
setting
);
}
$
(
document
).
ready
(
function
(){
}).
on
(
'show.bs.modal'
,
function
()
{
initTable2
();
initTree2
();
})
...
...
apps/assets/templates/assets/admin_user_assets.html
View file @
a5b9b4e1
...
...
@@ -45,13 +45,11 @@
<table
class=
"table table-striped table-bordered table-hover"
id=
"asset_list_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 'Reachable' %}
</th>
<th>
{% trans 'Action' %}
</th>
</tr>
</thead>
<tbody>
...
...
@@ -91,26 +89,36 @@
<script>
function
initTable
()
{
var
reachable
=
{{
admin_user
.
REACHABLE
}};
var
unreachable
=
{{
admin_user
.
UNREACHABLE
}};
var
options
=
{
ele
:
$
(
'#asset_list_table'
),
buttons
:
[],
order
:
[],
columnDefs
:
[
{
targets
:
1
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
{
targets
:
0
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
var
detail_btn
=
'<a href="{% url "assets:asset-detail" pk=DEFAULT_PK %}" data-aid="'
+
rowData
.
id
+
'">'
+
cellData
+
'</a>'
;
$
(
td
).
html
(
detail_btn
.
replace
(
'{{ DEFAULT_PK }}'
,
rowData
.
id
));
}},
{
targets
:
4
,
createdCell
:
function
(
td
,
cellData
)
{
if
(
!
cellData
)
{
{
targets
:
3
,
createdCell
:
function
(
td
,
cellData
)
{
if
(
cellData
===
unreachable
)
{
$
(
td
).
html
(
'<i class="fa fa-times text-danger"></i>'
)
}
else
{
}
else
if
(
cellData
===
reachable
)
{
$
(
td
).
html
(
'<i class="fa fa-check text-navy"></i>'
)
}
else
{
$
(
td
).
html
(
''
)
}
}}],
ajax_url
:
'{% url "api-assets:asset-list" %}?admin_user_id={{ admin_user.id }}'
,
}},
{
targets
:
4
,
createdCell
:
function
(
td
,
cellData
)
{
var
test_btn
=
' <a class="btn btn-xs btn-info btn-test-asset" data-uid="{{ DEFAULT_PK }}" >{% trans "Test" %}</a>'
.
replace
(
"{{ DEFAULT_PK }}"
,
cellData
);
$
(
td
).
html
(
test_btn
);
}}
],
ajax_url
:
'{% url "api-assets:admin-user-assets" pk=admin_user.id %}'
,
columns
:
[
{
data
:
function
(){
return
""
}},
{
data
:
"hostname"
},
{
data
:
"ip"
},
{
data
:
"port"
},
{
data
:
"
is_connective"
}],
{
data
:
"hostname"
},
{
data
:
"ip"
},
{
data
:
"port"
},
{
data
:
"
connectivity"
},
{
data
:
"id"
}],
op_html
:
$
(
'#actions'
).
html
()
};
jumpserver
.
initServerSideDataTable
(
options
);
...
...
@@ -119,6 +127,21 @@ function initTable() {
$
(
document
).
ready
(
function
()
{
initTable
();
})
.
on
(
'click'
,
'.btn-test-asset'
,
function
()
{
var
asset_id
=
$
(
this
).
data
(
'uid'
);
var
the_url
=
"{% url 'api-assets:asset-alive-test' pk=DEFAULT_PK %}"
.
replace
(
'{{ DEFAULT_PK }}'
,
asset_id
);
var
success
=
function
(
data
)
{
var
task_id
=
data
.
task
;
var
url
=
'{% url "ops:celery-task-log" pk=DEFAULT_PK %}'
.
replace
(
"{{ DEFAULT_PK }}"
,
task_id
);
window
.
open
(
url
,
''
,
'width=800,height=600,left=400,top=400'
)
};
APIUpdateAttr
({
url
:
the_url
,
method
:
'GET'
,
success
:
success
,
flash_message
:
false
});
})
.
on
(
'click'
,
'.btn-test-connective'
,
function
()
{
var
the_url
=
"{% url 'api-assets:admin-user-connective' pk=admin_user.id %}"
;
var
success
=
function
(
data
)
{
...
...
apps/assets/templates/assets/asset_list.html
View file @
a5b9b4e1
This diff is collapsed.
Click to expand it.
apps/assets/templates/assets/label_create_update.html
View file @
a5b9b4e1
...
...
@@ -32,6 +32,7 @@ $(document).ready(function () {
}).
on
(
'click'
,
'.select2-selection__rendered'
,
function
(
e
)
{
e
.
preventDefault
();
$
(
"#asset_list_modal"
).
modal
();
initSelectedAssets2Table
();
})
.
on
(
'click'
,
'#btn_asset_modal_confirm'
,
function
()
{
var
assets
=
asset_table2
.
selected
;
...
...
apps/assets/templates/assets/system_user_asset.html
View file @
a5b9b4e1
...
...
@@ -136,7 +136,7 @@
{% block custom_foot_js %}
<script>
function
initAssetsTable
()
{
var
unreachable
=
{{
system_user
.
unreachable_assets
|
safe
}};
var
connectivity
=
{{
system_user
.
connectivity
|
safe
}};
var
options
=
{
ele
:
$
(
'#system_user_list'
),
buttons
:
[],
...
...
@@ -147,10 +147,12 @@ function initAssetsTable() {
$
(
td
).
html
(
detail_btn
.
replace
(
'{{ DEFAULT_PK }}'
,
rowData
.
id
));
}},
{
targets
:
3
,
createdCell
:
function
(
td
,
cellData
)
{
if
(
unreachable
.
indexOf
(
cellData
)
>=
0
)
{
if
(
connectivity
.
unreachable
.
indexOf
(
cellData
)
>=
0
)
{
$
(
td
).
html
(
'<i class="fa fa-times text-danger"></i>'
)
}
else
{
}
else
if
(
connectivity
.
reachable
.
indexOf
(
cellData
)
>=
0
)
{
$
(
td
).
html
(
'<i class="fa fa-check text-navy"></i>'
)
}
else
{
$
(
td
).
html
(
''
)
}
}},
{
targets
:
4
,
createdCell
:
function
(
td
,
cellData
)
{
...
...
apps/assets/templates/assets/system_user_list.html
View file @
a5b9b4e1
...
...
@@ -95,7 +95,7 @@ function initTable() {
}}],
ajax_url
:
'{% url "api-assets:system-user-list" %}'
,
columns
:
[
{
data
:
"id"
},
{
data
:
"name"
},
{
data
:
"username"
},
{
data
:
"protocol"
},
{
data
:
"
get_
login_mode_display"
},
{
data
:
"assets_amount"
},
{
data
:
"id"
},
{
data
:
"name"
},
{
data
:
"username"
},
{
data
:
"protocol"
},
{
data
:
"login_mode_display"
},
{
data
:
"assets_amount"
},
{
data
:
"reachable_amount"
},
{
data
:
"unreachable_amount"
},
{
data
:
"id"
},
{
data
:
"comment"
},
{
data
:
"id"
}
],
op_html
:
$
(
'#actions'
).
html
()
...
...
apps/assets/urls/api_urls.py
View file @
a5b9b4e1
...
...
@@ -24,35 +24,39 @@ cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-r
urlpatterns
=
[
path
(
'assets-bulk/'
,
api
.
AssetListUpdateApi
.
as_view
(),
name
=
'asset-bulk-update'
),
path
(
'system-user/<uuid:pk>/auth-info/'
,
api
.
SystemUserAuthInfoApi
.
as_view
(),
name
=
'system-user-auth-info'
),
path
(
'system-user/<uuid:pk>/assets/'
,
api
.
SystemUserAssetsListView
.
as_view
(),
name
=
'system-user-assets'
),
path
(
'assets/<uuid:pk>/refresh/'
,
api
.
AssetRefreshHardwareApi
.
as_view
(),
name
=
'asset-refresh'
),
path
(
'assets/<uuid:pk>/alive/'
,
api
.
AssetAdminUserTestApi
.
as_view
(),
name
=
'asset-alive-test'
),
path
(
'assets/<uuid:pk>/gateway/'
,
api
.
AssetGatewayApi
.
as_view
(),
name
=
'asset-gateway'
),
path
(
'admin-user/<uuid:pk>/nodes/'
,
api
.
ReplaceNodesAdminUserApi
.
as_view
(),
name
=
'replace-nodes-admin-user'
),
path
(
'admin-user/<uuid:pk>/auth/'
,
api
.
AdminUserAuthApi
.
as_view
(),
name
=
'admin-user-auth'
),
path
(
'admin-user/<uuid:pk>/connective/'
,
api
.
AdminUserTestConnectiveApi
.
as_view
(),
name
=
'admin-user-connective'
),
path
(
'admin-user/<uuid:pk>/assets/'
,
api
.
AdminUserAssetsListView
.
as_view
(),
name
=
'admin-user-assets'
),
path
(
'system-user/<uuid:pk>/auth-info/'
,
api
.
SystemUserAuthInfoApi
.
as_view
(),
name
=
'system-user-auth-info'
),
path
(
'system-user/<uuid:pk>/assets/'
,
api
.
SystemUserAssetsListView
.
as_view
(),
name
=
'system-user-assets'
),
path
(
'system-user/<uuid:pk>/push/'
,
api
.
SystemUserPushApi
.
as_view
(),
name
=
'system-user-push'
),
path
(
'system-user/<uuid:pk>/asset/<uuid:aid>/push/'
,
api
.
SystemUserPushToAssetApi
.
as_view
(),
name
=
'system-user-push-to-asset'
),
path
(
'system-user/<uuid:pk>/asset/<uuid:aid>/test/'
,
api
.
SystemUserTestAssetConnect
abil
ityApi
.
as_view
(),
name
=
'system-user-test-to-asset'
),
api
.
SystemUserTestAssetConnect
iv
ityApi
.
as_view
(),
name
=
'system-user-test-to-asset'
),
path
(
'system-user/<uuid:pk>/connective/'
,
api
.
SystemUserTestConnectiveApi
.
as_view
(),
name
=
'system-user-connective'
),
path
(
'system-user/<uuid:pk>/cmd-filter-rules/'
,
api
.
SystemUserCommandFilterRuleListApi
.
as_view
(),
name
=
'system-user-cmd-filter-rule-list'
),
path
(
'nodes/tree/'
,
api
.
NodeListAsTreeApi
.
as_view
(),
name
=
'node-tree'
),
path
(
'nodes/children/tree/'
,
api
.
NodeChildrenAsTreeApi
.
as_view
(),
name
=
'node-children-tree'
),
path
(
'nodes/<uuid:pk>/children/'
,
api
.
NodeChildrenApi
.
as_view
(),
name
=
'node-children'
),
path
(
'nodes/children/'
,
api
.
NodeChildrenApi
.
as_view
(),
name
=
'node-children-2'
),
...
...
apps/assets/views/admin_user.py
View file @
a5b9b4e1
...
...
@@ -102,7 +102,7 @@ class AdminUserAssetsView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
'app'
:
_
(
'Assets'
),
'action'
:
_
(
'Admin user detail'
),
"total_amount"
:
len
(
self
.
queryset
),
'unreachable_amount'
:
len
([
asset
for
asset
in
self
.
queryset
if
asset
.
is_connective
is
False
])
'unreachable_amount'
:
len
([
asset
for
asset
in
self
.
queryset
if
asset
.
connectivity
is
False
])
}
kwargs
.
update
(
context
)
return
super
()
.
get_context_data
(
**
kwargs
)
...
...
apps/assets/views/asset.py
View file @
a5b9b4e1
...
...
@@ -216,7 +216,6 @@ class AssetExportView(LoginRequiredMixin, View):
return
HttpResponse
(
'Json object not valid'
,
status
=
400
)
if
not
assets_id
:
print
(
node_id
)
node
=
get_object_or_none
(
Node
,
id
=
node_id
)
if
node_id
else
Node
.
root
()
assets
=
node
.
get_all_assets
()
for
asset
in
assets
:
...
...
apps/audits/migrations/0002_ftplog_org_id.py
View file @
a5b9b4e1
...
...
@@ -13,6 +13,6 @@ class Migration(migrations.Migration):
migrations
.
AddField
(
model_name
=
'ftplog'
,
name
=
'org_id'
,
field
=
models
.
CharField
(
blank
=
True
,
default
=
None
,
max_length
=
36
,
null
=
True
),
field
=
models
.
CharField
(
blank
=
True
,
d
b_index
=
True
,
d
efault
=
None
,
max_length
=
36
,
null
=
True
),
),
]
apps/audits/migrations/0003_auto_20180816_1652.py
View file @
a5b9b4e1
...
...
@@ -13,6 +13,6 @@ class Migration(migrations.Migration):
migrations
.
AlterField
(
model_name
=
'ftplog'
,
name
=
'org_id'
,
field
=
models
.
CharField
(
blank
=
True
,
default
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
),
field
=
models
.
CharField
(
blank
=
True
,
d
b_index
=
True
,
d
efault
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
),
),
]
apps/audits/migrations/0004_operatelog_passwordchangelog_userloginlog.py
View file @
a5b9b4e1
...
...
@@ -15,7 +15,7 @@ class Migration(migrations.Migration):
migrations
.
CreateModel
(
name
=
'OperateLog'
,
fields
=
[
(
'org_id'
,
models
.
CharField
(
blank
=
True
,
default
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
)),
(
'org_id'
,
models
.
CharField
(
blank
=
True
,
d
b_index
=
True
,
d
efault
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
)),
(
'id'
,
models
.
UUIDField
(
default
=
uuid
.
uuid4
,
primary_key
=
True
,
serialize
=
False
)),
(
'user'
,
models
.
CharField
(
max_length
=
128
,
verbose_name
=
'User'
)),
(
'action'
,
models
.
CharField
(
choices
=
[(
'create'
,
'Create'
),
(
'update'
,
'Update'
),
(
'delete'
,
'Delete'
)],
max_length
=
16
,
verbose_name
=
'Action'
)),
...
...
apps/common/forms.py
View file @
a5b9b4e1
...
...
@@ -15,8 +15,6 @@ class BaseForm(forms.Form):
super
()
.
__init__
(
*
args
,
**
kwargs
)
for
name
,
field
in
self
.
fields
.
items
():
value
=
getattr
(
settings
,
name
,
None
)
# django_value = getattr(settings, name) if hasattr(settings, name) else None
if
value
is
None
:
# and django_value is None:
continue
...
...
@@ -24,8 +22,6 @@ class BaseForm(forms.Form):
if
isinstance
(
value
,
dict
):
value
=
json
.
dumps
(
value
)
initial_value
=
value
# elif django_value is False or django_value:
# initial_value = django_value
else
:
initial_value
=
''
field
.
initial
=
initial_value
...
...
@@ -134,6 +130,14 @@ class TerminalSettingForm(BaseForm):
(
'hostname'
,
_
(
'Hostname'
)),
(
'ip'
,
_
(
'IP'
)),
)
PAGE_SIZE_CHOICES
=
(
(
'all'
,
_
(
'All'
)),
(
'auto'
,
_
(
'Auto'
)),
(
10
,
10
),
(
15
,
15
),
(
25
,
25
),
(
50
,
50
),
)
TERMINAL_PASSWORD_AUTH
=
forms
.
BooleanField
(
initial
=
True
,
required
=
False
,
label
=
_
(
"Password auth"
)
)
...
...
@@ -146,6 +150,14 @@ class TerminalSettingForm(BaseForm):
TERMINAL_ASSET_LIST_SORT_BY
=
forms
.
ChoiceField
(
choices
=
SORT_BY_CHOICES
,
initial
=
'hostname'
,
label
=
_
(
"List sort by"
)
)
TERMINAL_ASSET_LIST_PAGE_SIZE
=
forms
.
ChoiceField
(
choices
=
PAGE_SIZE_CHOICES
,
initial
=
'auto'
,
label
=
_
(
"List page size"
),
)
TERMINAL_SESSION_KEEP_DURATION
=
forms
.
IntegerField
(
label
=
_
(
"Session keep duration"
),
help_text
=
_
(
"Units: days, Session, record, command will be delete "
"if more than duration, only in database"
)
)
class
TerminalCommandStorage
(
BaseForm
):
...
...
apps/common/models.py
View file @
a5b9b4e1
...
...
@@ -45,6 +45,8 @@ class Setting(models.Model):
def
cleaned_value
(
self
):
try
:
value
=
self
.
value
if
not
isinstance
(
value
,
(
str
,
bytes
)):
return
value
if
self
.
encrypted
:
value
=
signer
.
unsign
(
value
)
value
=
json
.
loads
(
value
)
...
...
apps/common/signals_handler.py
View file @
a5b9b4e1
...
...
@@ -26,21 +26,20 @@ def refresh_settings_on_changed(sender, instance=None, **kwargs):
def
refresh_all_settings_on_django_ready
(
sender
,
**
kwargs
):
logger
.
debug
(
"Receive django ready signal"
)
logger
.
debug
(
" - fresh all settings"
)
CACHE_KEY_PREFIX
=
'_SETTING_'
cache_key_prefix
=
'_SETTING_'
def
monkey_patch_getattr
(
self
,
name
):
key
=
CACHE_KEY_PREFIX
+
name
key
=
cache_key_prefix
+
name
cached
=
cache
.
get
(
key
)
if
cached
is
not
None
:
return
cached
if
self
.
_wrapped
is
empty
:
self
.
_setup
(
name
)
val
=
getattr
(
self
.
_wrapped
,
name
)
# self.__dict__[name] = val # Never set it
return
val
def
monkey_patch_setattr
(
self
,
name
,
value
):
key
=
CACHE_KEY_PREFIX
+
name
key
=
cache_key_prefix
+
name
cache
.
set
(
key
,
value
,
None
)
if
name
==
'_wrapped'
:
self
.
__dict__
.
clear
()
...
...
@@ -51,7 +50,7 @@ def refresh_all_settings_on_django_ready(sender, **kwargs):
def
monkey_patch_delattr
(
self
,
name
):
super
(
LazySettings
,
self
)
.
__delattr__
(
name
)
self
.
__dict__
.
pop
(
name
,
None
)
key
=
CACHE_KEY_PREFIX
+
name
key
=
cache_key_prefix
+
name
cache
.
delete
(
key
)
try
:
...
...
apps/common/tree.py
View file @
a5b9b4e1
...
...
@@ -47,16 +47,13 @@ class TreeNode:
def
__gt__
(
self
,
other
):
if
self
.
isParent
and
not
other
.
isParent
:
return
False
elif
not
self
.
isParent
and
other
.
isParent
:
return
True
return
self
.
id
>
other
.
id
def
__eq__
(
self
,
other
):
return
self
.
id
==
other
.
id
def
__lt__
(
self
,
other
):
if
self
.
isParent
and
not
other
.
isParent
:
return
True
return
self
.
id
<
other
.
id
class
Tree
:
def
__init__
(
self
):
...
...
apps/jumpserver/conf.py
View file @
a5b9b4e1
...
...
@@ -304,7 +304,7 @@ defaults = {
'REDIS_DB_CELERY'
:
3
,
'REDIS_DB_CACHE'
:
4
,
'CAPTCHA_TEST_MODE'
:
None
,
'TOKEN_EXPIRATION'
:
3600
,
'TOKEN_EXPIRATION'
:
3600
*
24
,
'DISPLAY_PER_PAGE'
:
25
,
'DEFAULT_EXPIRED_YEARS'
:
70
,
'SESSION_COOKIE_DOMAIN'
:
None
,
...
...
@@ -312,7 +312,14 @@ defaults = {
'SESSION_COOKIE_AGE'
:
3600
*
24
,
'SESSION_EXPIRE_AT_BROWSER_CLOSE'
:
False
,
'AUTH_OPENID'
:
False
,
'EMAIL_SUFFIX'
:
'jumpserver.org'
'OTP_ISSUER_NAME'
:
'Jumpserver'
,
'EMAIL_SUFFIX'
:
'jumpserver.org'
,
'TERMINAL_PASSWORD_AUTH'
:
True
,
'TERMINAL_PUBLIC_KEY_AUTH'
:
True
,
'TERMINAL_HEARTBEAT_INTERVAL'
:
5
,
'TERMINAL_ASSET_LIST_SORT_BY'
:
'hostname'
,
'TERMINAL_ASSET_LIST_PAGE_SIZE'
:
'auto'
,
'TERMINAL_SESSION_KEEP_DURATION'
:
9999
,
}
...
...
apps/jumpserver/settings.py
View file @
a5b9b4e1
...
...
@@ -356,6 +356,7 @@ FILE_UPLOAD_DIRECTORY_PERMISSIONS = 0o755
# OTP settings
OTP_ISSUER_NAME
=
CONFIG
.
OTP_ISSUER_NAME
OTP_VALID_WINDOW
=
CONFIG
.
OTP_VALID_WINDOW
# Auth LDAP settings
AUTH_LDAP
=
False
...
...
@@ -466,6 +467,7 @@ DEFAULT_TERMINAL_REPLAY_STORAGE = {
TERMINAL_REPLAY_STORAGE
=
{
}
SECURITY_MFA_AUTH
=
False
SECURITY_LOGIN_LIMIT_COUNT
=
7
SECURITY_LOGIN_LIMIT_TIME
=
30
# Unit: minute
...
...
@@ -484,6 +486,13 @@ SECURITY_PASSWORD_RULES = [
'SECURITY_PASSWORD_SPECIAL_CHAR'
]
TERMINAL_PASSWORD_AUTH
=
CONFIG
.
TERMINAL_PASSWORD_AUTH
TERMINAL_PUBLIC_KEY_AUTH
=
CONFIG
.
TERMINAL_PUBLIC_KEY_AUTH
TERMINAL_HEARTBEAT_INTERVAL
=
CONFIG
.
TERMINAL_HEARTBEAT_INTERVAL
TERMINAL_ASSET_LIST_SORT_BY
=
CONFIG
.
TERMINAL_ASSET_LIST_SORT_BY
TERMINAL_ASSET_LIST_PAGE_SIZE
=
CONFIG
.
TERMINAL_ASSET_LIST_PAGE_SIZE
TERMINAL_SESSION_KEEP_DURATION
=
CONFIG
.
TERMINAL_SESSION_KEEP_DURATION
# Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html
BOOTSTRAP3
=
{
'horizontal_label_class'
:
'col-md-2'
,
...
...
apps/locale/zh/LC_MESSAGES/django.mo
View file @
a5b9b4e1
No preview for this file type
apps/locale/zh/LC_MESSAGES/django.po
View file @
a5b9b4e1
This diff is collapsed.
Click to expand it.
apps/ops/api/adhoc.py
View file @
a5b9b4e1
...
...
@@ -24,8 +24,10 @@ class TaskViewSet(viewsets.ModelViewSet):
def
get_queryset
(
self
):
queryset
=
super
()
.
get_queryset
()
if
current_org
:
if
current_org
.
is_real
()
:
queryset
=
queryset
.
filter
(
created_by
=
current_org
.
id
)
else
:
queryset
=
queryset
.
filter
(
created_by
=
''
)
return
queryset
...
...
apps/ops/serializers.py
View file @
a5b9b4e1
...
...
@@ -53,7 +53,7 @@ class AdHocRunHistorySerializer(serializers.ModelSerializer):
@staticmethod
def
get_stat
(
obj
):
return
{
"total"
:
len
(
obj
.
adhoc
.
hosts
),
"total"
:
obj
.
adhoc
.
hosts
.
count
(
),
"success"
:
len
(
obj
.
summary
.
get
(
"contacted"
,
[])),
"failed"
:
len
(
obj
.
summary
.
get
(
"dark"
,
[])),
}
...
...
apps/ops/urls/api_urls.py
View file @
a5b9b4e1
...
...
@@ -11,6 +11,7 @@ app_name = "ops"
router
=
DefaultRouter
()
router
.
register
(
r'tasks'
,
api
.
TaskViewSet
,
'task'
)
router
.
register
(
r'adhoc'
,
api
.
AdHocViewSet
,
'adhoc'
)
router
.
register
(
r'history'
,
api
.
AdHocRunHistoryViewSet
,
'history'
)
router
.
register
(
r'command-executions'
,
api
.
CommandExecutionViewSet
,
'command-execution'
)
urlpatterns
=
[
...
...
apps/ops/views/adhoc.py
View file @
a5b9b4e1
...
...
@@ -62,8 +62,11 @@ class TaskDetailView(AdminUserRequiredMixin, DetailView):
def
get_queryset
(
self
):
queryset
=
super
()
.
get_queryset
()
if
current_org
:
# Todo: 需要整理默认组织等东西
if
current_org
.
is_real
():
queryset
=
queryset
.
filter
(
created_by
=
current_org
.
id
)
else
:
queryset
=
queryset
.
filter
(
created_by
=
''
)
return
queryset
def
get_context_data
(
self
,
**
kwargs
):
...
...
apps/orgs/mixins.py
View file @
a5b9b4e1
...
...
@@ -74,7 +74,7 @@ class OrgManager(models.Manager):
class
OrgModelMixin
(
models
.
Model
):
org_id
=
models
.
CharField
(
max_length
=
36
,
blank
=
True
,
default
=
''
,
verbose_name
=
_
(
"Organization"
))
org_id
=
models
.
CharField
(
max_length
=
36
,
blank
=
True
,
default
=
''
,
verbose_name
=
_
(
"Organization"
)
,
db_index
=
True
)
objects
=
OrgManager
()
sep
=
'@'
...
...
apps/perms/api.py
View file @
a5b9b4e1
...
...
@@ -16,7 +16,7 @@ from orgs.utils import set_to_root_org
from
.utils
import
AssetPermissionUtil
from
.models
import
AssetPermission
from
.hands
import
AssetGrantedSerializer
,
User
,
UserGroup
,
Asset
,
Node
,
\
NodeGrantedSerializer
,
SystemUser
,
NodeSerializer
SystemUser
,
NodeSerializer
from
.
import
serializers
from
.mixins
import
AssetsFilterMixin
...
...
@@ -150,7 +150,7 @@ class UserGrantedNodesWithAssetsApi(AssetsFilterMixin, ListAPIView):
用户授权的节点并带着节点下资产的api
"""
permission_classes
=
(
IsOrgAdminOrAppUser
,)
serializer_class
=
NodeGrantedSerializer
serializer_class
=
serializers
.
NodeGrantedSerializer
def
change_org_if_need
(
self
):
if
self
.
request
.
user
.
is_superuser
or
\
...
...
@@ -228,15 +228,22 @@ class UserGrantedNodesWithAssetsAsTreeApi(ListAPIView):
@staticmethod
def
parse_asset_to_tree_node
(
node
,
asset
,
system_users
):
system_users_protocol_matched
=
[
s
for
s
in
system_users
if
s
.
protocol
==
asset
.
protocol
]
system_user_serializer
=
serializers
.
GrantedSystemUserSerializer
(
system_users_protocol_matched
,
many
=
True
)
asset_serializer
=
serializers
.
GrantedAssetSerializer
(
asset
)
icon_skin
=
'file'
if
asset
.
platform
.
lower
()
==
'windows'
:
icon_skin
=
'windows'
elif
asset
.
platform
.
lower
()
==
'linux'
:
icon_skin
=
'linux'
system_users
=
[]
for
system_user
in
system_users_protocol_matched
:
system_users
.
append
({
'id'
:
system_user
.
id
,
'name'
:
system_user
.
name
,
'username'
:
system_user
.
username
,
'protocol'
:
system_user
.
protocol
,
'priority'
:
system_user
.
priority
,
'login_mode'
:
system_user
.
login_mode
,
'comment'
:
system_user
.
comment
,
})
data
=
{
'id'
:
str
(
asset
.
id
),
'name'
:
asset
.
hostname
,
...
...
@@ -246,9 +253,19 @@ class UserGrantedNodesWithAssetsAsTreeApi(ListAPIView):
'open'
:
False
,
'iconSkin'
:
icon_skin
,
'meta'
:
{
'system_users'
:
system_user
_serializer
.
data
,
'system_users'
:
system_user
s
,
'type'
:
'asset'
,
'asset'
:
asset_serializer
.
data
'asset'
:
{
'id'
:
asset
.
id
,
'hostname'
:
asset
.
hostname
,
'ip'
:
asset
.
ip
,
'port'
:
asset
.
port
,
'protocol'
:
asset
.
protocol
,
'platform'
:
asset
.
platform
,
'domain'
:
None
if
not
asset
.
domain
else
asset
.
domain
.
id
,
'is_active'
:
asset
.
is_active
,
'comment'
:
asset
.
comment
},
}
}
tree_node
=
TreeNode
(
**
data
)
...
...
@@ -360,7 +377,7 @@ class UserGroupGrantedNodesApi(ListAPIView):
class
UserGroupGrantedNodesWithAssetsApi
(
ListAPIView
):
permission_classes
=
(
IsOrgAdmin
,)
serializer_class
=
NodeGrantedSerializer
serializer_class
=
serializers
.
NodeGrantedSerializer
def
get_queryset
(
self
):
user_group_id
=
self
.
kwargs
.
get
(
'pk'
,
''
)
...
...
apps/perms/hands.py
View file @
a5b9b4e1
...
...
@@ -4,7 +4,7 @@
from
common.permissions
import
AdminUserRequiredMixin
from
users.models
import
User
,
UserGroup
from
assets.models
import
Asset
,
SystemUser
,
Node
from
assets.serializers
import
AssetGrantedSerializer
,
Node
GrantedSerializer
,
Node
Serializer
from
assets.serializers
import
AssetGrantedSerializer
,
NodeSerializer
apps/perms/migrations/0008_auto_20180816_1652.py
View file @
a5b9b4e1
...
...
@@ -13,11 +13,11 @@ class Migration(migrations.Migration):
migrations
.
AlterField
(
model_name
=
'assetpermission'
,
name
=
'org_id'
,
field
=
models
.
CharField
(
blank
=
True
,
default
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
),
field
=
models
.
CharField
(
blank
=
True
,
d
b_index
=
True
,
d
efault
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
),
),
migrations
.
AlterField
(
model_name
=
'nodepermission'
,
name
=
'org_id'
,
field
=
models
.
CharField
(
blank
=
True
,
default
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
),
field
=
models
.
CharField
(
blank
=
True
,
d
b_index
=
True
,
d
efault
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
),
),
]
apps/perms/serializers.py
View file @
a5b9b4e1
...
...
@@ -83,6 +83,35 @@ class AssetPermissionNodeSerializer(serializers.ModelSerializer):
return
obj
.
parent_key
class
NodeGrantedSerializer
(
serializers
.
ModelSerializer
):
"""
授权资产组
"""
assets_granted
=
AssetGrantedSerializer
(
many
=
True
,
read_only
=
True
)
assets_amount
=
serializers
.
SerializerMethodField
()
parent
=
serializers
.
SerializerMethodField
()
name
=
serializers
.
SerializerMethodField
()
class
Meta
:
model
=
Node
fields
=
[
'id'
,
'key'
,
'name'
,
'value'
,
'parent'
,
'assets_granted'
,
'assets_amount'
,
'org_id'
,
]
@staticmethod
def
get_assets_amount
(
obj
):
return
len
(
obj
.
assets_granted
)
@staticmethod
def
get_name
(
obj
):
return
obj
.
name
@staticmethod
def
get_parent
(
obj
):
return
obj
.
parent
.
id
class
GrantedNodeSerializer
(
serializers
.
ModelSerializer
):
class
Meta
:
model
=
Node
...
...
apps/perms/templates/perms/asset_permission_create_update.html
View file @
a5b9b4e1
...
...
@@ -113,6 +113,7 @@ $(document).ready(function () {
e
.
preventDefault
();
e
.
stopPropagation
();
$
(
"#asset_list_modal"
).
modal
();
initSelectedAssets2Table
();
}
})
})
...
...
apps/perms/templates/perms/asset_permission_list.html
View file @
a5b9b4e1
...
...
@@ -78,58 +78,24 @@ var zTree, table, show = 0;
function
onSelected
(
event
,
treeNode
)
{
setCookie
(
'node_selected'
,
treeNode
.
id
);
var
url
=
table
.
ajax
.
url
();
if
(
treeNode
.
is_node
)
{
if
(
treeNode
.
meta
.
type
===
'node'
)
{
url
=
setUrlParam
(
url
,
'asset'
,
""
);
url
=
setUrlParam
(
url
,
'node'
,
treeNode
.
node_
id
)
url
=
setUrlParam
(
url
,
'node'
,
treeNode
.
meta
.
node
.
id
)
}
else
{
url
=
setUrlParam
(
url
,
'node'
,
""
);
url
=
setUrlParam
(
url
,
'asset'
,
treeNode
.
node_
id
)
url
=
setUrlParam
(
url
,
'asset'
,
treeNode
.
meta
.
asset
.
id
)
}
setCookie
(
'node_selected'
,
treeNode
.
node_id
);
table
.
ajax
.
url
(
url
);
table
.
ajax
.
reload
();
}
function
selectQueryNode
()
{
var
query_node_id
=
$
.
getUrlParam
(
"node"
);
var
cookie_node_id
=
getCookie
(
'node_selected'
);
var
node
;
var
node_id
;
if
(
query_node_id
!==
null
)
{
node_id
=
query_node_id
}
else
if
(
cookie_node_id
!==
null
)
{
node_id
=
cookie_node_id
;
}
node
=
zTree
.
getNodesByParam
(
"id"
,
node_id
,
null
);
if
(
node
){
zTree
.
selectNode
(
node
[
0
]);
node
.
open
=
true
;
}
}
function
filter
(
treeId
,
parentNode
,
childNodes
)
{
$
.
each
(
childNodes
,
function
(
index
,
value
)
{
value
[
"node_id"
]
=
value
[
"id"
];
value
[
"id"
]
=
value
[
"tree_id"
];
if
(
value
[
"tree_id"
]
!==
value
[
"tree_parent"
])
{
value
[
"pId"
]
=
value
[
"tree_parent"
];
}
else
{
value
[
"isParent"
]
=
true
;
}
value
[
'name'
]
=
value
[
'value'
];
value
[
"iconSkin"
]
=
value
[
"is_node"
]
?
null
:
'file'
;
{
#
value
[
"pId"
]
=
value
[
"parent"
];
#
}
{
#
value
[
"name"
]
=
value
[
"value"
];
#
}
value
[
"isParent"
]
=
value
[
"is_node"
];
});
return
childNodes
;
}
function
beforeAsync
(
treeId
,
treeNode
)
{
return
treeNode
.
is_node
if
(
treeNode
)
{
return
treeNode
.
meta
.
type
===
'node'
}
return
true
}
function
makeLabel
(
data
)
{
...
...
@@ -235,9 +201,8 @@ function initTree() {
},
async
:
{
enable
:
true
,
url
:
"{% url 'api-assets:node-children-2' %}?assets=1&all="
,
autoParam
:[
"node_id=id"
,
"name=n"
,
"level=lv"
],
dataFilter
:
filter
,
url
:
"{% url 'api-assets:node-children-tree' %}?assets=1"
,
autoParam
:[
"id=key"
,
"name=n"
,
"level=lv"
],
type
:
'get'
},
callback
:
{
...
...
@@ -245,25 +210,7 @@ function initTree() {
beforeAsync
:
beforeAsync
}
};
var
zNodes
=
[];
$
.
get
(
"{% url 'api-assets:node-children-2' %}?assets=1"
,
function
(
data
,
status
){
$
.
each
(
data
,
function
(
index
,
value
)
{
value
[
"node_id"
]
=
value
[
"id"
];
value
[
"id"
]
=
value
[
"tree_id"
];
if
(
value
[
"tree_id"
]
!==
value
[
"tree_parent"
])
{
value
[
"pId"
]
=
value
[
"tree_parent"
];
}
value
[
"isParent"
]
=
value
[
"is_node"
];
value
[
'name'
]
=
value
[
'value'
];
value
[
"iconSkin"
]
=
value
[
"is_node"
]
?
null
:
'file'
;
});
zNodes
=
data
;
$
.
fn
.
zTree
.
init
(
$
(
"#assetTree"
),
setting
,
zNodes
);
zTree
=
$
.
fn
.
zTree
.
getZTreeObj
(
"assetTree"
);
var
root
=
zTree
.
getNodes
()[
0
];
zTree
.
expandNode
(
root
);
});
zTree
=
$
.
fn
.
zTree
.
init
(
$
(
"#assetTree"
),
setting
);
}
function
toggle
()
{
...
...
@@ -299,10 +246,10 @@ $(document).ready(function(){
var
_nodes
=
[];
var
_assets
=
[];
$
.
each
(
nodes
,
function
(
id
,
node
)
{
if
(
node
.
is_node
)
{
_nodes
.
push
(
node
.
node_
id
)
if
(
node
.
meta
.
type
===
'node'
)
{
_nodes
.
push
(
node
.
meta
.
node
.
id
)
}
else
{
_assets
.
push
(
node
.
node_
id
)
_assets
.
push
(
node
.
meta
.
asset
.
id
)
}
});
url
+=
"?assets="
+
_assets
.
join
(
","
)
+
"&nodes="
+
_nodes
.
join
(
","
);
...
...
apps/static/js/jumpserver.js
View file @
a5b9b4e1
...
...
@@ -812,3 +812,32 @@ function initPopover($container, $progress, $idPassword, $el, password_check_rul
$idPassword
.
pwstrength
(
options
);
popoverPasswordRules
(
password_check_rules
,
$el
);
}
// 解决input框中的资产和弹出表格中资产的显示不一致
function
initSelectedAssets2Table
(){
var
inputAssets
=
$
(
'#id_assets'
).
val
();
var
selectedAssets
=
asset_table2
.
selected
.
concat
();
// input assets无,table assets选中,则取消勾选(再次click)
if
(
selectedAssets
.
length
!==
0
){
$
.
each
(
selectedAssets
,
function
(
index
,
assetId
){
if
(
$
.
inArray
(
assetId
,
inputAssets
)
===
-
1
){
$
(
'#'
+
assetId
).
trigger
(
'click'
);
// 取消勾选
}
});
}
// input assets有,table assets没选,则选中(click)
if
(
inputAssets
!==
null
){
asset_table2
.
selected
=
inputAssets
;
$
.
each
(
inputAssets
,
function
(
index
,
assetId
){
var
dom
=
document
.
getElementById
(
assetId
);
if
(
dom
!==
null
){
var
selected
=
dom
.
parentElement
.
parentElement
.
className
.
indexOf
(
'selected'
)
}
if
(
selected
===
-
1
){
$
(
'#'
+
assetId
).
trigger
(
'click'
);
}
});
}
}
apps/terminal/api/v1/session.py
View file @
a5b9b4e1
...
...
@@ -94,44 +94,15 @@ class SessionReplayViewSet(viewsets.ViewSet):
serializer_class
=
serializers
.
ReplaySerializer
permission_classes
=
(
IsOrgAdminOrAppUser
,)
session
=
None
upload_to
=
'replay'
# 仅添加到本地存储中
def
get_session_path
(
self
,
version
=
2
):
"""
获取session日志的文件路径
:param version: 原来后缀是 .gz,为了统一新版本改为 .replay.gz
:return:
"""
suffix
=
'.replay.gz'
if
version
==
1
:
suffix
=
'.gz'
date
=
self
.
session
.
date_start
.
strftime
(
'
%
Y-
%
m-
%
d'
)
return
os
.
path
.
join
(
date
,
str
(
self
.
session
.
id
)
+
suffix
)
def
get_local_path
(
self
,
version
=
2
):
session_path
=
self
.
get_session_path
(
version
=
version
)
if
version
==
2
:
local_path
=
os
.
path
.
join
(
self
.
upload_to
,
session_path
)
else
:
local_path
=
session_path
return
local_path
def
save_to_storage
(
self
,
f
):
local_path
=
self
.
get_local_path
()
try
:
name
=
default_storage
.
save
(
local_path
,
f
)
return
name
,
None
except
OSError
as
e
:
return
None
,
e
def
create
(
self
,
request
,
*
args
,
**
kwargs
):
session_id
=
kwargs
.
get
(
'pk'
)
se
lf
.
se
ssion
=
get_object_or_404
(
Session
,
id
=
session_id
)
session
=
get_object_or_404
(
Session
,
id
=
session_id
)
serializer
=
self
.
serializer_class
(
data
=
request
.
data
)
if
serializer
.
is_valid
():
file
=
serializer
.
validated_data
[
'file'
]
name
,
err
=
se
lf
.
save_to_storage
(
file
)
name
,
err
=
se
ssion
.
save_to_storage
(
file
)
if
not
name
:
msg
=
"Failed save replay `{}`: {}"
.
format
(
session_id
,
err
)
logger
.
error
(
msg
)
...
...
@@ -145,7 +116,7 @@ class SessionReplayViewSet(viewsets.ViewSet):
def
retrieve
(
self
,
request
,
*
args
,
**
kwargs
):
session_id
=
kwargs
.
get
(
'pk'
)
se
lf
.
se
ssion
=
get_object_or_404
(
Session
,
id
=
session_id
)
session
=
get_object_or_404
(
Session
,
id
=
session_id
)
data
=
{
'type'
:
'guacamole'
if
self
.
session
.
protocol
==
'rdp'
else
'json'
,
...
...
@@ -153,9 +124,9 @@ class SessionReplayViewSet(viewsets.ViewSet):
}
# 新版本和老版本的文件后缀不同
session_path
=
se
lf
.
get_session
_path
()
# 存在外部存储上的路径
local_path
=
se
lf
.
get_local_path
()
local_path_v1
=
se
lf
.
get_local_path
(
version
=
1
)
session_path
=
se
ssion
.
get_rel_replay
_path
()
# 存在外部存储上的路径
local_path
=
se
ssion
.
get_local_path
()
local_path_v1
=
se
ssion
.
get_local_path
(
version
=
1
)
# 去default storage中查找
for
_local_path
in
(
local_path
,
local_path_v1
,
session_path
):
...
...
apps/terminal/migrations/0011_auto_20180807_1116.py
View file @
a5b9b4e1
...
...
@@ -13,11 +13,11 @@ class Migration(migrations.Migration):
migrations
.
AddField
(
model_name
=
'command'
,
name
=
'org_id'
,
field
=
models
.
CharField
(
blank
=
True
,
d
efault
=
None
,
max_length
=
36
,
null
=
True
),
field
=
models
.
CharField
(
blank
=
True
,
d
b_index
=
True
,
default
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
),
),
migrations
.
AddField
(
model_name
=
'session'
,
name
=
'org_id'
,
field
=
models
.
CharField
(
blank
=
True
,
d
efault
=
None
,
max_length
=
36
,
null
=
True
),
field
=
models
.
CharField
(
blank
=
True
,
d
b_index
=
True
,
default
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
),
),
]
apps/terminal/migrations/0012_auto_20180816_1652.py
View file @
a5b9b4e1
...
...
@@ -10,14 +10,4 @@ class Migration(migrations.Migration):
]
operations
=
[
migrations
.
AlterField
(
model_name
=
'command'
,
name
=
'org_id'
,
field
=
models
.
CharField
(
blank
=
True
,
default
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
),
),
migrations
.
AlterField
(
model_name
=
'session'
,
name
=
'org_id'
,
field
=
models
.
CharField
(
blank
=
True
,
default
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
),
),
]
apps/terminal/models.py
View file @
a5b9b4e1
from
__future__
import
unicode_literals
import
os
import
uuid
from
django.db
import
models
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.utils
import
timezone
from
django.conf
import
settings
from
django.core.files.storage
import
default_storage
from
users.models
import
User
from
orgs.mixins
import
OrgModelMixin
...
...
@@ -148,6 +150,36 @@ class Session(OrgModelMixin):
date_start
=
models
.
DateTimeField
(
verbose_name
=
_
(
"Date start"
),
db_index
=
True
,
default
=
timezone
.
now
)
date_end
=
models
.
DateTimeField
(
verbose_name
=
_
(
"Date end"
),
null
=
True
)
upload_to
=
'replay'
def
get_rel_replay_path
(
self
,
version
=
2
):
"""
获取session日志的文件路径
:param version: 原来后缀是 .gz,为了统一新版本改为 .replay.gz
:return:
"""
suffix
=
'.replay.gz'
if
version
==
1
:
suffix
=
'.gz'
date
=
self
.
date_start
.
strftime
(
'
%
Y-
%
m-
%
d'
)
return
os
.
path
.
join
(
date
,
str
(
self
.
id
)
+
suffix
)
def
get_local_path
(
self
,
version
=
2
):
rel_path
=
self
.
get_rel_replay_path
(
version
=
version
)
if
version
==
2
:
local_path
=
os
.
path
.
join
(
self
.
upload_to
,
rel_path
)
else
:
local_path
=
rel_path
return
local_path
def
save_to_storage
(
self
,
f
):
local_path
=
self
.
get_local_path
()
try
:
name
=
default_storage
.
save
(
local_path
,
f
)
return
name
,
None
except
OSError
as
e
:
return
None
,
e
class
Meta
:
db_table
=
"terminal_session"
ordering
=
[
"-date_start"
]
...
...
apps/terminal/tasks.py
View file @
a5b9b4e1
...
...
@@ -4,15 +4,20 @@
import
datetime
from
celery
import
shared_task
from
celery.utils.log
import
get_task_logger
from
django.utils
import
timezone
from
django.conf
import
settings
from
django.core.files.storage
import
default_storage
from
ops.celery.utils
import
register_as_period_task
,
after_app_ready_start
,
\
after_app_shutdown_clean
from
.models
import
Status
,
Session
from
.models
import
Status
,
Session
,
Command
CACHE_REFRESH_INTERVAL
=
10
RUNNING
=
False
logger
=
get_task_logger
(
__name__
)
@shared_task
...
...
@@ -34,3 +39,28 @@ def clean_orphan_session():
if
not
session
.
terminal
or
not
session
.
terminal
.
is_active
:
session
.
is_finished
=
True
session
.
save
()
@shared_task
@register_as_period_task
(
interval
=
3600
*
24
)
@after_app_ready_start
@after_app_shutdown_clean
def
clean_expired_session_period
():
logger
.
info
(
"Start clean expired session record, commands and replay"
)
days
=
settings
.
TERMINAL_SESSION_KEEP_DURATION
dt
=
timezone
.
now
()
-
timezone
.
timedelta
(
days
=
days
)
expired_sessions
=
Session
.
objects
.
filter
(
date_start__lt
=
dt
)
for
session
in
expired_sessions
:
logger
.
info
(
"Clean session: {}"
.
format
(
session
.
id
))
Command
.
objects
.
filter
(
session
=
str
(
session
.
id
))
.
delete
()
# 删除录像文件
session_path
=
session
.
get_rel_replay_path
()
local_path
=
session
.
get_local_path
()
local_path_v1
=
session
.
get_local_path
(
version
=
1
)
# 去default storage中查找
for
_local_path
in
(
local_path
,
local_path_v1
,
session_path
):
if
default_storage
.
exists
(
_local_path
):
default_storage
.
delete
(
_local_path
)
# 删除session记录
session
.
delete
()
apps/terminal/utils.py
View file @
a5b9b4e1
# -*- coding: utf-8 -*-
#
from
django.core.cache
import
cache
from
.models
import
Session
from
assets.models
import
Asset
,
SystemUser
from
users.models
import
User
from
.const
import
USERS_CACHE_KEY
,
ASSETS_CACHE_KEY
,
SYSTEM_USER_CACHE_KEY
def
get_session_asset_list
():
return
set
(
list
(
Session
.
objects
.
values_list
(
'asset'
,
flat
=
True
))
)
return
Asset
.
objects
.
values_list
(
'hostname'
,
flat
=
True
)
def
get_session_user_list
():
return
set
(
list
(
Session
.
objects
.
values_list
(
'user'
,
flat
=
True
))
)
return
User
.
objects
.
values_list
(
'username'
,
flat
=
True
)
def
get_session_system_user_list
():
return
set
(
list
(
Session
.
objects
.
values_list
(
'system_user'
,
flat
=
True
))
)
return
SystemUser
.
objects
.
values_list
(
'username'
,
flat
=
True
)
def
get_user_list_from_cache
():
...
...
apps/users/api/group.py
View file @
a5b9b4e1
...
...
@@ -6,7 +6,7 @@ from rest_framework_bulk import BulkModelViewSet
from
rest_framework.pagination
import
LimitOffsetPagination
from
..serializers
import
UserGroupSerializer
,
\
UserGroupUpdateMem
e
berSerializer
UserGroupUpdateMemberSerializer
from
..models
import
UserGroup
from
common.permissions
import
IsOrgAdmin
from
common.mixins
import
IDInFilterMixin
...
...
@@ -26,5 +26,5 @@ class UserGroupViewSet(IDInFilterMixin, BulkModelViewSet):
class
UserGroupUpdateUserApi
(
generics
.
RetrieveUpdateAPIView
):
queryset
=
UserGroup
.
objects
.
all
()
serializer_class
=
UserGroupUpdateMem
e
berSerializer
serializer_class
=
UserGroupUpdateMemberSerializer
permission_classes
=
(
IsOrgAdmin
,)
apps/users/api/user.py
View file @
a5b9b4e1
...
...
@@ -46,6 +46,9 @@ class UserViewSet(IDInFilterMixin, BulkModelViewSet):
self
.
permission_classes
=
(
IsOrgAdminOrAppUser
,)
return
super
()
.
get_permissions
()
def
allow_bulk_destroy
(
self
,
qs
,
filtered
):
return
qs
.
count
()
==
filtered
.
count
()
class
UserChangePasswordApi
(
generics
.
RetrieveUpdateAPIView
):
permission_classes
=
(
IsOrgAdmin
,)
...
...
apps/users/migrations/0014_auto_20180816_1652.py
View file @
a5b9b4e1
...
...
@@ -13,6 +13,6 @@ class Migration(migrations.Migration):
migrations
.
AlterField
(
model_name
=
'usergroup'
,
name
=
'org_id'
,
field
=
models
.
CharField
(
blank
=
True
,
default
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
),
field
=
models
.
CharField
(
blank
=
True
,
d
b_index
=
True
,
d
efault
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
),
),
]
apps/users/models/user.py
View file @
a5b9b4e1
...
...
@@ -142,6 +142,18 @@ class User(AbstractUser):
return
True
return
False
@property
def
groups_display
(
self
):
return
' '
.
join
(
self
.
groups
.
all
()
.
values_list
(
'name'
,
flat
=
True
))
@property
def
role_display
(
self
):
return
self
.
get_role_display
()
@property
def
source_display
(
self
):
return
self
.
get_source_display
()
@property
def
is_expired
(
self
):
if
self
.
date_expired
and
self
.
date_expired
<
timezone
.
now
():
...
...
apps/users/serializers/v1.py
View file @
a5b9b4e1
...
...
@@ -13,31 +13,18 @@ signer = get_signer()
class
UserSerializer
(
BulkSerializerMixin
,
serializers
.
ModelSerializer
):
groups_display
=
serializers
.
SerializerMethodField
()
groups
=
serializers
.
PrimaryKeyRelatedField
(
many
=
True
,
queryset
=
UserGroup
.
objects
.
all
(),
required
=
False
)
class
Meta
:
model
=
User
list_serializer_class
=
BulkListSerializer
exclude
=
[
'first_name'
,
'last_name'
,
'password'
,
'_private_key'
,
'_public_key'
,
'_otp_secret_key'
,
'user_permissions'
fields
=
[
'id'
,
'name'
,
'username'
,
'email'
,
'groups'
,
'groups_display'
,
'role'
,
'role_display'
,
'avatar_url'
,
'wechat'
,
'phone'
,
'otp_level'
,
'comment'
,
'source'
,
'source_display'
,
'is_valid'
,
'is_expired'
,
'is_active'
,
'created_by'
,
'is_first_login'
,
'date_password_last_updated'
,
'date_expired'
,
]
# validators = []
def
get_field_names
(
self
,
declared_fields
,
info
):
fields
=
super
(
UserSerializer
,
self
)
.
get_field_names
(
declared_fields
,
info
)
fields
.
extend
([
'groups_display'
,
'get_role_display'
,
'get_source_display'
,
'is_valid'
])
return
fields
@staticmethod
def
get_groups_display
(
obj
):
return
" "
.
join
([
group
.
name
for
group
in
obj
.
groups
.
all
()])
class
UserPKUpdateSerializer
(
serializers
.
ModelSerializer
):
...
...
@@ -74,7 +61,7 @@ class UserGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer):
return
[
user
.
name
for
user
in
obj
.
users
.
all
()]
class
UserGroupUpdateMem
e
berSerializer
(
serializers
.
ModelSerializer
):
class
UserGroupUpdateMemberSerializer
(
serializers
.
ModelSerializer
):
users
=
serializers
.
PrimaryKeyRelatedField
(
many
=
True
,
queryset
=
User
.
objects
.
all
())
class
Meta
:
...
...
apps/users/templates/users/user_list.html
View file @
a5b9b4e1
...
...
@@ -25,7 +25,7 @@
<th
class=
"text-center"
>
{% trans 'Role' %}
</th>
<th
class=
"text-center"
>
{% trans 'User group' %}
</th>
<th
class=
"text-center"
>
{% trans 'Source' %}
</th>
<th
class=
"text-center"
>
{% trans '
Active
' %}
</th>
<th
class=
"text-center"
>
{% trans '
Validity
' %}
</th>
<th
class=
"text-center"
>
{% trans 'Action' %}
</th>
</tr>
</thead>
...
...
@@ -66,11 +66,14 @@ function initTable() {
var
innerHtml
=
cellData
.
length
>
20
?
cellData
.
substring
(
0
,
20
)
+
'...'
:
cellData
;
$
(
td
).
html
(
'<span href="javascript:void(0);" data-toggle="tooltip" title="'
+
cellData
+
'">'
+
innerHtml
+
'</span>'
);
}},
{
targets
:
6
,
createdCell
:
function
(
td
,
cellData
)
{
if
(
!
cellData
)
{
$
(
td
).
html
(
'<i class="fa fa-times text-danger"></i>'
)
}
else
{
{
targets
:
6
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
if
(
cellData
)
{
$
(
td
).
html
(
'<i class="fa fa-check text-navy"></i>'
)
}
else
if
(
!
rowData
.
is_active
)
{
$
(
td
).
html
(
'<i class="fa fa-times text-danger inactive"></i>'
)
}
else
if
(
rowData
.
is_expired
)
{
$
(
td
).
html
(
'<i class="fa fa-times text-danger expired"></i>'
)
}
}},
{
targets
:
7
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
...
...
@@ -91,9 +94,9 @@ function initTable() {
ajax_url
:
'{% url "api-users:user-list" %}'
,
columns
:
[
{
data
:
"id"
},
{
data
:
"name"
},
{
data
:
"username"
},
{
data
:
"
get_
role_display"
,
orderable
:
false
},
{
data
:
"role_display"
,
orderable
:
false
},
{
data
:
"groups_display"
,
orderable
:
false
},
{
data
:
"
get_
source_display"
,
orderable
:
false
},
{
data
:
"source_display"
,
orderable
:
false
},
{
data
:
"is_valid"
,
orderable
:
false
},
{
data
:
"id"
,
orderable
:
false
}
],
...
...
@@ -246,6 +249,13 @@ $(document).ready(function(){
var
uid
=
$this
.
data
(
'uid'
);
var
the_url
=
'{% url "api-users:user-detail" pk=DEFAULT_PK %}'
.
replace
(
"{{ DEFAULT_PK }}"
,
uid
);
objectDelete
(
$this
,
name
,
the_url
);
}).
on
(
'click'
,
'.expired'
,
function
()
{
var
msg
=
'{% trans "User is expired" %}'
;
toastr
.
error
(
msg
)
}).
on
(
'click'
,
'.inactive'
,
function
()
{
var
msg
=
'{% trans '
User
is
inactive
' %}'
;
toastr
.
error
(
msg
)
})
</script>
{% endblock %}
...
...
apps/users/utils.py
View file @
a5b9b4e1
...
...
@@ -292,7 +292,8 @@ def check_otp_code(otp_secret_key, otp_code):
if
not
otp_secret_key
or
not
otp_code
:
return
False
totp
=
pyotp
.
TOTP
(
otp_secret_key
)
return
totp
.
verify
(
otp_code
)
otp_valid_window
=
settings
.
OTP_VALID_WINDOW
or
0
return
totp
.
verify
(
otp
=
otp_code
,
valid_window
=
otp_valid_window
)
def
get_password_check_rules
():
...
...
config_docker.py
View file @
a5b9b4e1
...
...
@@ -100,6 +100,9 @@ class Config:
}
AUTH_LDAP_START_TLS
=
False
#
# OTP_VALID_WINDOW = 0
def
__init__
(
self
):
pass
...
...
@@ -200,6 +203,10 @@ class DockerConfig(Config):
AUTH_LDAP_START_TLS
=
False
#
OTP_VALID_WINDOW
=
int
(
os
.
environ
.
get
(
"OTP_VALID_WINDOW"
))
if
os
.
environ
.
get
(
"OTP_VALID_WINDOW"
)
else
0
# Default using Config settings, you can write if/else for different env
config
=
DockerConfig
()
config_example.py
View file @
a5b9b4e1
...
...
@@ -90,6 +90,9 @@ class Config:
# AUTH_OPENID_CLIENT_ID = 'client-id'
# AUTH_OPENID_CLIENT_SECRET = 'client-secret'
#
# OTP_VALID_WINDOW = 0
def
__init__
(
self
):
pass
...
...
jms
View file @
a5b9b4e1
...
...
@@ -26,8 +26,8 @@ LOG_DIR = os.path.join(BASE_DIR, 'logs')
TMP_DIR
=
os
.
path
.
join
(
BASE_DIR
,
'tmp'
)
HTTP_HOST
=
CONFIG
.
HTTP_BIND_HOST
or
'127.0.0.1'
HTTP_PORT
=
CONFIG
.
HTTP_LISTEN_PORT
or
8080
DEBUG
=
CONFIG
.
DEBUG
LOG_LEVEL
=
CONFIG
.
LOG_LEVEL
DEBUG
=
CONFIG
.
DEBUG
or
False
LOG_LEVEL
=
CONFIG
.
LOG_LEVEL
or
'INFO'
START_TIMEOUT
=
40
WORKERS
=
4
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment