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
1fe18e80
Unverified
Commit
1fe18e80
authored
Aug 23, 2019
by
老广
Committed by
GitHub
Aug 23, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Bugfix (#3153)
* [Update] 修改一部分bug * [Update] 修改一些
parent
de1a08a5
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
146 additions
and
175 deletions
+146
-175
asset.py
apps/assets/api/asset.py
+3
-3
node.py
apps/assets/api/node.py
+7
-6
asset.py
apps/assets/models/asset.py
+5
-28
node.py
apps/assets/models/node.py
+68
-64
user.py
apps/assets/models/user.py
+2
-0
signals_handler.py
apps/assets/signals_handler.py
+49
-41
asset.py
apps/assets/views/asset.py
+2
-2
_access_key_modal.html
...ntication/templates/authentication/_access_key_modal.html
+2
-2
signals_handler.py
apps/orgs/signals_handler.py
+1
-1
asset_permission.py
apps/perms/models/asset_permission.py
+1
-0
signals_handler.py
apps/perms/signals_handler.py
+1
-10
asset_permission.py
apps/perms/utils/asset_permission.py
+3
-17
asset_permission.py
apps/perms/views/asset_permission.py
+2
-1
No files found.
apps/assets/api/asset.py
View file @
1fe18e80
...
@@ -63,15 +63,15 @@ class AssetViewSet(LabelFilter, OrgBulkModelViewSet):
...
@@ -63,15 +63,15 @@ class AssetViewSet(LabelFilter, OrgBulkModelViewSet):
show_current_asset
=
self
.
request
.
query_params
.
get
(
"show_current_asset"
)
in
(
'1'
,
'true'
)
show_current_asset
=
self
.
request
.
query_params
.
get
(
"show_current_asset"
)
in
(
'1'
,
'true'
)
# 当前节点是顶层节点, 并且仅显示直接资产
# 当前节点是顶层节点, 并且仅显示直接资产
if
node
.
is_root
()
and
show_current_asset
:
if
node
.
is_
org_
root
()
and
show_current_asset
:
queryset
=
queryset
.
filter
(
queryset
=
queryset
.
filter
(
Q
(
nodes
=
node_id
)
|
Q
(
nodes__isnull
=
True
)
Q
(
nodes
=
node_id
)
|
Q
(
nodes__isnull
=
True
)
)
.
distinct
()
)
.
distinct
()
# 当前节点是顶层节点,显示所有资产
# 当前节点是顶层节点,显示所有资产
elif
node
.
is_root
()
and
not
show_current_asset
:
elif
node
.
is_
org_
root
()
and
not
show_current_asset
:
return
queryset
return
queryset
# 当前节点不是鼎城节点,只显示直接资产
# 当前节点不是鼎城节点,只显示直接资产
elif
not
node
.
is_root
()
and
show_current_asset
:
elif
not
node
.
is_
org_
root
()
and
show_current_asset
:
queryset
=
queryset
.
filter
(
nodes
=
node
)
queryset
=
queryset
.
filter
(
nodes
=
node
)
else
:
else
:
children
=
node
.
get_all_children
(
with_self
=
True
)
children
=
node
.
get_all_children
(
with_self
=
True
)
...
...
apps/assets/api/node.py
View file @
1fe18e80
...
@@ -50,13 +50,13 @@ class NodeViewSet(OrgModelViewSet):
...
@@ -50,13 +50,13 @@ class NodeViewSet(OrgModelViewSet):
# 仅支持根节点指直接创建,子节点下的节点需要通过children接口创建
# 仅支持根节点指直接创建,子节点下的节点需要通过children接口创建
def
perform_create
(
self
,
serializer
):
def
perform_create
(
self
,
serializer
):
child_key
=
Node
.
root
()
.
get_next_child_key
()
child_key
=
Node
.
org_
root
()
.
get_next_child_key
()
serializer
.
validated_data
[
"key"
]
=
child_key
serializer
.
validated_data
[
"key"
]
=
child_key
serializer
.
save
()
serializer
.
save
()
def
perform_update
(
self
,
serializer
):
def
perform_update
(
self
,
serializer
):
node
=
self
.
get_object
()
node
=
self
.
get_object
()
if
node
.
is_root
()
and
node
.
value
!=
serializer
.
validated_data
[
'value'
]:
if
node
.
is_
org_
root
()
and
node
.
value
!=
serializer
.
validated_data
[
'value'
]:
msg
=
_
(
"You can't update the root node name"
)
msg
=
_
(
"You can't update the root node name"
)
raise
ValidationError
({
"error"
:
msg
})
raise
ValidationError
({
"error"
:
msg
})
return
super
()
.
perform_update
(
serializer
)
return
super
()
.
perform_update
(
serializer
)
...
@@ -97,6 +97,7 @@ class NodeChildrenApi(generics.ListCreateAPIView):
...
@@ -97,6 +97,7 @@ class NodeChildrenApi(generics.ListCreateAPIView):
permission_classes
=
(
IsOrgAdmin
,)
permission_classes
=
(
IsOrgAdmin
,)
serializer_class
=
serializers
.
NodeSerializer
serializer_class
=
serializers
.
NodeSerializer
instance
=
None
instance
=
None
is_initial
=
False
def
initial
(
self
,
request
,
*
args
,
**
kwargs
):
def
initial
(
self
,
request
,
*
args
,
**
kwargs
):
self
.
instance
=
self
.
get_object
()
self
.
instance
=
self
.
get_object
()
...
@@ -117,7 +118,8 @@ class NodeChildrenApi(generics.ListCreateAPIView):
...
@@ -117,7 +118,8 @@ class NodeChildrenApi(generics.ListCreateAPIView):
pk
=
self
.
kwargs
.
get
(
'pk'
)
or
self
.
request
.
query_params
.
get
(
'id'
)
pk
=
self
.
kwargs
.
get
(
'pk'
)
or
self
.
request
.
query_params
.
get
(
'id'
)
key
=
self
.
request
.
query_params
.
get
(
"key"
)
key
=
self
.
request
.
query_params
.
get
(
"key"
)
if
not
pk
and
not
key
:
if
not
pk
and
not
key
:
node
=
Node
.
root
()
node
=
Node
.
org_root
()
self
.
is_initial
=
True
return
node
return
node
if
pk
:
if
pk
:
node
=
get_object_or_404
(
Node
,
pk
=
pk
)
node
=
get_object_or_404
(
Node
,
pk
=
pk
)
...
@@ -130,7 +132,7 @@ class NodeChildrenApi(generics.ListCreateAPIView):
...
@@ -130,7 +132,7 @@ class NodeChildrenApi(generics.ListCreateAPIView):
if
not
self
.
instance
:
if
not
self
.
instance
:
return
Node
.
objects
.
none
()
return
Node
.
objects
.
none
()
if
self
.
i
nstance
.
is_root
()
:
if
self
.
i
s_initial
:
with_self
=
True
with_self
=
True
else
:
else
:
with_self
=
False
with_self
=
False
...
@@ -234,7 +236,7 @@ class NodeRemoveAssetsApi(generics.UpdateAPIView):
...
@@ -234,7 +236,7 @@ class NodeRemoveAssetsApi(generics.UpdateAPIView):
def
perform_update
(
self
,
serializer
):
def
perform_update
(
self
,
serializer
):
assets
=
serializer
.
validated_data
.
get
(
'assets'
)
assets
=
serializer
.
validated_data
.
get
(
'assets'
)
instance
=
self
.
get_object
()
instance
=
self
.
get_object
()
if
instance
!=
Node
.
root
():
if
instance
!=
Node
.
org_
root
():
instance
.
assets
.
remove
(
*
tuple
(
assets
))
instance
.
assets
.
remove
(
*
tuple
(
assets
))
else
:
else
:
assets
=
[
asset
for
asset
in
assets
if
asset
.
nodes
.
count
()
>
1
]
assets
=
[
asset
for
asset
in
assets
if
asset
.
nodes
.
count
()
>
1
]
...
@@ -287,5 +289,4 @@ class RefreshAssetsAmount(APIView):
...
@@ -287,5 +289,4 @@ class RefreshAssetsAmount(APIView):
model
=
Node
model
=
Node
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
self
.
model
.
expire_nodes_assets_amount
()
return
Response
(
"Ok"
)
return
Response
(
"Ok"
)
apps/assets/models/asset.py
View file @
1fe18e80
...
@@ -6,8 +6,7 @@ import uuid
...
@@ -6,8 +6,7 @@ import uuid
import
logging
import
logging
import
random
import
random
from
functools
import
reduce
from
functools
import
reduce
from
collections
import
OrderedDict
,
defaultdict
from
collections
import
OrderedDict
from
django.core.cache
import
cache
from
django.db
import
models
from
django.db
import
models
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.utils.translation
import
ugettext_lazy
as
_
...
@@ -32,7 +31,7 @@ def default_cluster():
...
@@ -32,7 +31,7 @@ def default_cluster():
def
default_node
():
def
default_node
():
try
:
try
:
from
.node
import
Node
from
.node
import
Node
root
=
Node
.
root
()
root
=
Node
.
org_
root
()
return
root
return
root
except
:
except
:
return
None
return
None
...
@@ -103,33 +102,11 @@ class NodesRelationMixin:
...
@@ -103,33 +102,11 @@ class NodesRelationMixin:
id
=
""
id
=
""
_all_nodes_keys
=
None
_all_nodes_keys
=
None
@classmethod
def
get_all_nodes_keys
(
cls
):
"""
:return: {asset.id: [node.key, ]}
"""
from
.node
import
Node
cache_key
=
cls
.
ALL_ASSET_NODES_CACHE_KEY
cached
=
cache
.
get
(
cache_key
)
if
cached
:
return
cached
assets
=
Asset
.
objects
.
all
()
.
only
(
'id'
)
.
prefetch_related
(
models
.
Prefetch
(
'nodes'
,
queryset
=
Node
.
objects
.
all
()
.
only
(
'key'
))
)
assets_nodes_keys
=
{}
for
asset
in
assets
:
assets_nodes_keys
[
asset
.
id
]
=
[
n
.
key
for
n
in
asset
.
nodes
.
all
()]
cache
.
set
(
cache_key
,
assets_nodes_keys
,
cls
.
CACHE_TIME
)
return
assets_nodes_keys
@classmethod
def
expire_all_nodes_keys_cache
(
cls
):
cache_key
=
cls
.
ALL_ASSET_NODES_CACHE_KEY
cache
.
delete
(
cache_key
)
def
get_nodes
(
self
):
def
get_nodes
(
self
):
from
.node
import
Node
from
.node
import
Node
nodes
=
self
.
nodes
.
all
()
or
[
Node
.
root
()]
nodes
=
self
.
nodes
.
all
()
if
not
nodes
:
nodes
=
Node
.
objects
.
filter
(
id
=
Node
.
org_root
()
.
id
)
return
nodes
return
nodes
def
get_all_nodes
(
self
,
flat
=
False
):
def
get_all_nodes
(
self
,
flat
=
False
):
...
...
apps/assets/models/node.py
View file @
1fe18e80
...
@@ -24,40 +24,34 @@ class NodeQuerySet(models.QuerySet):
...
@@ -24,40 +24,34 @@ class NodeQuerySet(models.QuerySet):
class
TreeMixin
:
class
TreeMixin
:
t
ime_tree_updated
=
None
t
ree_created_time
=
None
t
ime_tree_updated
_cache_key
=
'NODE_TREE_CREATED_AT'
t
ree_updated_time
_cache_key
=
'NODE_TREE_CREATED_AT'
tree_cache_time
=
3600
tree_
update_time_
cache_time
=
3600
_tree_service
=
None
_tree_service
=
None
@classmethod
@classmethod
def
tree
(
cls
):
def
tree
(
cls
):
# Todo: 有待优化, 因为每次刷新都会导致其他节点的tree失效
# Todo: 有待优化, 因为每次刷新都会导致其他节点的tree失效 完成
# Todo: ungroup node
# TOdo: 游离的资产,在树上显示的数量不对
# TOdo: 游离的资产,在树上显示的数量不对
# Todo: api key页面有bug
# Todo: ungroup node
# Todo: api key页面有bug 完成
from
..utils
import
TreeService
from
..utils
import
TreeService
cache_updated_time
=
cls
.
get_cache_time
()
tree_updated_time
=
cache
.
get
(
cls
.
tree_updated_time_cache_key
,
0
)
if
not
cls
.
time_tree_updated
or
\
if
not
cls
.
tree_created_time
or
\
cache_updated_time
!=
cls
.
time_tree_updated
:
tree_updated_time
>
cls
.
tree_created_time
:
t
=
TreeService
.
new
()
print
(
"New tree"
)
cls
.
update_cache_tree
(
t
)
tree
=
TreeService
.
new
()
return
t
cls
.
tree_created_time
=
time
.
time
()
cls
.
_tree_service
=
tree
return
tree
return
cls
.
_tree_service
return
cls
.
_tree_service
@classmethod
def
get_cache_time
(
cls
):
return
cache
.
get
(
cls
.
time_tree_updated_cache_key
)
@classmethod
def
update_cache_tree
(
cls
,
t
):
cls
.
_tree_service
=
t
now
=
time
.
time
()
cls
.
time_tree_updated
=
now
cache
.
set
(
cls
.
time_tree_updated_cache_key
,
now
,
cls
.
tree_cache_time
)
@classmethod
@classmethod
def
expire_cache_tree
(
cls
):
def
expire_cache_tree
(
cls
):
cache
.
delete
(
cls
.
time_tree_updated_cache_key
)
key
=
cls
.
tree_updated_time_cache_key
ttl
=
cls
.
tree_update_time_cache_time
value
=
time
.
time
()
cache
.
set
(
key
,
value
,
ttl
)
@classmethod
@classmethod
def
refresh_tree
(
cls
):
def
refresh_tree
(
cls
):
...
@@ -74,6 +68,20 @@ class FamilyMixin:
...
@@ -74,6 +68,20 @@ class FamilyMixin:
__all_children
=
None
__all_children
=
None
is_node
=
True
is_node
=
True
@staticmethod
def
clean_children_keys
(
nodes_keys
):
nodes_keys
=
sorted
(
list
(
nodes_keys
),
key
=
lambda
x
:
(
len
(
x
),
x
))
nodes_keys_clean
=
[]
for
key
in
nodes_keys
[::
-
1
]:
found
=
False
for
k
in
nodes_keys
:
if
key
.
startswith
(
k
+
':'
):
found
=
True
break
if
not
found
:
nodes_keys_clean
.
append
(
key
)
return
nodes_keys_clean
@property
@property
def
children
(
self
):
def
children
(
self
):
return
self
.
get_children
(
with_self
=
False
)
return
self
.
get_children
(
with_self
=
False
)
...
@@ -108,7 +116,7 @@ class FamilyMixin:
...
@@ -108,7 +116,7 @@ class FamilyMixin:
@property
@property
def
parent
(
self
):
def
parent
(
self
):
if
self
.
is_root
():
if
self
.
is_
org_
root
():
return
self
return
self
parent_key
=
self
.
parent_key
parent_key
=
self
.
parent_key
return
Node
.
objects
.
get
(
key
=
parent_key
)
return
Node
.
objects
.
get
(
key
=
parent_key
)
...
@@ -122,10 +130,10 @@ class FamilyMixin:
...
@@ -122,10 +130,10 @@ class FamilyMixin:
old_key
=
self
.
key
old_key
=
self
.
key
with
transaction
.
atomic
():
with
transaction
.
atomic
():
self
.
key
=
parent
.
get_next_child_key
()
self
.
key
=
parent
.
get_next_child_key
()
self
.
save
()
for
child
in
children
:
for
child
in
children
:
child
.
key
=
child
.
key
.
replace
(
old_key
,
self
.
key
,
1
)
child
.
key
=
child
.
key
.
replace
(
old_key
,
self
.
key
,
1
)
child
.
save
()
child
.
save
()
self
.
save
()
def
get_siblings
(
self
,
with_self
=
False
):
def
get_siblings
(
self
,
with_self
=
False
):
key
=
':'
.
join
(
self
.
key
.
split
(
':'
)[:
-
1
])
key
=
':'
.
join
(
self
.
key
.
split
(
':'
)[:
-
1
])
...
@@ -182,21 +190,17 @@ class FullValueMixin:
...
@@ -182,21 +190,17 @@ class FullValueMixin:
@property
@property
def
full_value
(
self
):
def
full_value
(
self
):
if
self
.
is_root
():
if
self
.
is_
org_
root
():
return
self
.
value
return
self
.
value
if
self
.
_full_value
is
not
None
:
if
self
.
_full_value
is
not
None
:
return
self
.
_full_value
return
self
.
_full_value
print
(
"Get full value"
)
value
=
self
.
_tree
.
get_node_full_tag
(
self
.
key
)
value
=
self
.
_tree
.
get_node_full_tag
(
self
.
key
)
return
value
return
value
class
NodeAssetsMixin
:
class
NodeAssetsMixin
:
_assets_amount_cache_key
=
'_NODE_ASSETS_AMOUNT_{}'
_assets_cache_key
=
'_NODE_ASSETS_{}'
_assets_amount
=
None
_assets_amount
=
None
key
=
''
key
=
''
cache_time
=
3600
*
24
*
7
id
=
None
id
=
None
@property
@property
...
@@ -210,24 +214,19 @@ class NodeAssetsMixin:
...
@@ -210,24 +214,19 @@ class NodeAssetsMixin:
amount
=
self
.
_tree
.
assets_amount
(
self
.
key
)
amount
=
self
.
_tree
.
assets_amount
(
self
.
key
)
return
amount
return
amount
# TOdo: 是否依赖tree
def
get_all_assets
(
self
):
def
get_all_assets
(
self
):
from
.asset
import
Asset
from
.asset
import
Asset
if
self
.
is_root
():
if
self
.
is_
org_
root
():
return
Asset
.
objects
.
filter
(
org_id
=
self
.
org_id
)
return
Asset
.
objects
.
filter
(
org_id
=
self
.
org_id
)
assets_ids
=
self
.
_tree
.
all_assets
(
self
.
key
)
pattern
=
'^{0}$|^{0}:'
.
format
(
self
.
key
)
return
Asset
.
objects
.
filter
(
id__in
=
assets_ids
)
return
Asset
.
objects
.
filter
(
nodes__key__regex
=
pattern
)
.
distinct
()
def
assets_ids
(
self
):
assets_ids
=
self
.
_tree
.
assets
(
self
.
key
)
return
assets_ids
def
get_assets
(
self
):
def
get_assets
(
self
):
from
.asset
import
Asset
from
.asset
import
Asset
if
self
.
is_
default_node
():
if
self
.
is_
org_root
():
assets
=
Asset
.
objects
.
filter
(
Q
(
nodes
__id
=
self
.
id
)
|
Q
(
nodes__isnull
=
True
))
assets
=
Asset
.
objects
.
filter
(
Q
(
nodes
=
self
)
|
Q
(
nodes__isnull
=
True
))
else
:
else
:
assets
=
Asset
.
objects
.
filter
(
id
=
self
.
assets_ids
()
)
assets
=
Asset
.
objects
.
filter
(
nodes
=
self
)
return
assets
.
distinct
()
return
assets
.
distinct
()
def
get_valid_assets
(
self
):
def
get_valid_assets
(
self
):
...
@@ -236,6 +235,16 @@ class NodeAssetsMixin:
...
@@ -236,6 +235,16 @@ class NodeAssetsMixin:
def
get_all_valid_assets
(
self
):
def
get_all_valid_assets
(
self
):
return
self
.
get_all_assets
()
.
valid
()
return
self
.
get_all_assets
()
.
valid
()
@classmethod
def
get_nodes_all_assets
(
cls
,
nodes_keys
):
from
.asset
import
Asset
nodes_keys
=
cls
.
clean_children_keys
(
nodes_keys
)
pattern
=
set
()
for
key
in
nodes_keys
:
pattern
.
add
(
r'^{0}$|^{0}:'
.
format
(
key
))
pattern
=
'|'
.
join
(
list
(
pattern
))
return
Asset
.
objects
.
filter
(
nodes__key__regex
=
pattern
)
class
Node
(
OrgModelMixin
,
TreeMixin
,
FamilyMixin
,
FullValueMixin
,
NodeAssetsMixin
):
class
Node
(
OrgModelMixin
,
TreeMixin
,
FamilyMixin
,
FullValueMixin
,
NodeAssetsMixin
):
id
=
models
.
UUIDField
(
default
=
uuid
.
uuid4
,
primary_key
=
True
)
id
=
models
.
UUIDField
(
default
=
uuid
.
uuid4
,
primary_key
=
True
)
...
@@ -261,22 +270,13 @@ class Node(OrgModelMixin, TreeMixin, FamilyMixin, FullValueMixin, NodeAssetsMixi
...
@@ -261,22 +270,13 @@ class Node(OrgModelMixin, TreeMixin, FamilyMixin, FullValueMixin, NodeAssetsMixi
return
self
.
id
==
other
.
id
return
self
.
id
==
other
.
id
def
__gt__
(
self
,
other
):
def
__gt__
(
self
,
other
):
# if self.is_root() and not other.is_root():
# return False
# elif not self.is_root() and other.is_root():
# return True
self_key
=
[
int
(
k
)
for
k
in
self
.
key
.
split
(
':'
)]
self_key
=
[
int
(
k
)
for
k
in
self
.
key
.
split
(
':'
)]
other_key
=
[
int
(
k
)
for
k
in
other
.
key
.
split
(
':'
)]
other_key
=
[
int
(
k
)
for
k
in
other
.
key
.
split
(
':'
)]
self_parent_key
=
self_key
[:
-
1
]
self_parent_key
=
self_key
[:
-
1
]
other_parent_key
=
other_key
[:
-
1
]
other_parent_key
=
other_key
[:
-
1
]
if
self_parent_key
and
other_parent_key
and
\
if
self_parent_key
and
self_parent_key
==
other_parent_key
:
self_parent_key
==
other_parent_key
:
return
self
.
value
>
other
.
value
return
self
.
value
>
other
.
value
# if len(self_parent_key) < len(other_parent_key):
# return True
# elif len(self_parent_key) > len(other_parent_key):
# return False
return
self_key
>
other_key
return
self_key
>
other_key
def
__lt__
(
self
,
other
):
def
__lt__
(
self
,
other
):
...
@@ -310,7 +310,9 @@ class Node(OrgModelMixin, TreeMixin, FamilyMixin, FullValueMixin, NodeAssetsMixi
...
@@ -310,7 +310,9 @@ class Node(OrgModelMixin, TreeMixin, FamilyMixin, FullValueMixin, NodeAssetsMixi
def
create_child
(
self
,
value
,
_id
=
None
):
def
create_child
(
self
,
value
,
_id
=
None
):
with
transaction
.
atomic
():
with
transaction
.
atomic
():
child_key
=
self
.
get_next_child_key
()
child_key
=
self
.
get_next_child_key
()
child
=
self
.
__class__
.
objects
.
create
(
id
=
_id
,
key
=
child_key
,
value
=
value
)
child
=
self
.
__class__
.
objects
.
create
(
id
=
_id
,
key
=
child_key
,
value
=
value
)
return
child
return
child
@classmethod
@classmethod
...
@@ -318,37 +320,39 @@ class Node(OrgModelMixin, TreeMixin, FamilyMixin, FullValueMixin, NodeAssetsMixi
...
@@ -318,37 +320,39 @@ class Node(OrgModelMixin, TreeMixin, FamilyMixin, FullValueMixin, NodeAssetsMixi
cls
.
refresh_tree
()
cls
.
refresh_tree
()
def
is_default_node
(
self
):
def
is_default_node
(
self
):
return
self
.
is_root
()
and
self
.
key
==
'1'
return
self
.
key
==
'1'
def
is_root
(
self
):
def
is_
org_
root
(
self
):
if
self
.
key
.
isdigit
():
if
self
.
key
.
isdigit
():
return
True
return
True
else
:
else
:
return
False
return
False
@classmethod
@classmethod
def
create_root_node
(
cls
):
def
create_
org_
root_node
(
cls
):
# 如果使用current_org 在set_current_org时会死循环
# 如果使用current_org 在set_current_org时会死循环
_current
_org
=
get_current_org
()
ori
_org
=
get_current_org
()
with
transaction
.
atomic
():
with
transaction
.
atomic
():
if
not
_current
_org
.
is_real
():
if
not
ori
_org
.
is_real
():
return
cls
.
default_node
()
return
cls
.
default_node
()
set_current_org
(
Organization
.
root
())
set_current_org
(
Organization
.
root
())
org_nodes_roots
=
cls
.
objects
.
filter
(
key__regex
=
r'^[0-9]+$'
)
org_nodes_roots
=
cls
.
objects
.
filter
(
key__regex
=
r'^[0-9]+$'
)
org_nodes_roots_keys
=
org_nodes_roots
.
values_list
(
'key'
,
flat
=
True
)
or
[
'1'
]
org_nodes_roots_keys
=
org_nodes_roots
.
values_list
(
'key'
,
flat
=
True
)
if
not
org_nodes_roots_keys
:
org_nodes_roots_keys
=
[
'1'
]
key
=
max
([
int
(
k
)
for
k
in
org_nodes_roots_keys
])
key
=
max
([
int
(
k
)
for
k
in
org_nodes_roots_keys
])
key
=
str
(
key
+
1
)
if
key
!=
0
else
'2'
key
=
str
(
key
+
1
)
if
key
!=
0
else
'2'
set_current_org
(
_current
_org
)
set_current_org
(
ori
_org
)
root
=
cls
.
objects
.
create
(
key
=
key
,
value
=
_current
_org
.
name
)
root
=
cls
.
objects
.
create
(
key
=
key
,
value
=
ori
_org
.
name
)
return
root
return
root
@classmethod
@classmethod
def
root
(
cls
):
def
org_
root
(
cls
):
root
=
cls
.
objects
.
filter
(
key__regex
=
r'^[0-9]+$'
)
root
=
cls
.
objects
.
filter
(
key__regex
=
r'^[0-9]+$'
)
if
root
:
if
root
:
return
root
[
0
]
return
root
[
0
]
else
:
else
:
return
cls
.
create_root_node
()
return
cls
.
create_
org_
root_node
()
@classmethod
@classmethod
def
default_node
(
cls
):
def
default_node
(
cls
):
...
@@ -365,7 +369,7 @@ class Node(OrgModelMixin, TreeMixin, FamilyMixin, FullValueMixin, NodeAssetsMixi
...
@@ -365,7 +369,7 @@ class Node(OrgModelMixin, TreeMixin, FamilyMixin, FullValueMixin, NodeAssetsMixi
'title'
:
name
,
'title'
:
name
,
'pId'
:
self
.
parent_key
,
'pId'
:
self
.
parent_key
,
'isParent'
:
True
,
'isParent'
:
True
,
'open'
:
self
.
is_root
(),
'open'
:
self
.
is_
org_
root
(),
'meta'
:
{
'meta'
:
{
'node'
:
{
'node'
:
{
"id"
:
self
.
id
,
"id"
:
self
.
id
,
...
...
apps/assets/models/user.py
View file @
1fe18e80
...
@@ -148,9 +148,11 @@ class SystemUser(AssetUser):
...
@@ -148,9 +148,11 @@ class SystemUser(AssetUser):
return
True
,
None
return
True
,
None
def
get_all_assets
(
self
):
def
get_all_assets
(
self
):
from
.node
import
Node
args
=
[
Q
(
systemuser
=
self
)]
args
=
[
Q
(
systemuser
=
self
)]
pattern
=
set
()
pattern
=
set
()
nodes_keys
=
self
.
nodes
.
all
()
.
values_list
(
'key'
,
flat
=
True
)
nodes_keys
=
self
.
nodes
.
all
()
.
values_list
(
'key'
,
flat
=
True
)
nodes_keys
=
Node
.
clean_children_keys
(
nodes_keys
)
for
key
in
nodes_keys
:
for
key
in
nodes_keys
:
pattern
.
add
(
r'^{0}$|^{0}:'
.
format
(
key
))
pattern
.
add
(
r'^{0}$|^{0}:'
.
format
(
key
))
pattern
=
'|'
.
join
(
list
(
pattern
))
pattern
=
'|'
.
join
(
list
(
pattern
))
...
...
apps/assets/signals_handler.py
View file @
1fe18e80
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
#
#
from
collections
import
defaultdict
from
collections
import
defaultdict
from
django.db.models.signals
import
post_save
,
m2m_changed
,
pre_delete
from
django.db.models.signals
import
(
post_save
,
m2m_changed
,
pre_delete
,
pre_save
,
pre_init
,
post_init
)
from
django.dispatch
import
receiver
from
django.dispatch
import
receiver
from
common.utils
import
get_logger
from
common.utils
import
get_logger
...
@@ -30,6 +32,9 @@ def test_asset_conn_on_created(asset):
...
@@ -30,6 +32,9 @@ def test_asset_conn_on_created(asset):
@receiver
(
post_save
,
sender
=
Asset
,
dispatch_uid
=
"my_unique_identifier"
)
@receiver
(
post_save
,
sender
=
Asset
,
dispatch_uid
=
"my_unique_identifier"
)
@on_transaction_commit
@on_transaction_commit
def
on_asset_created_or_update
(
sender
,
instance
=
None
,
created
=
False
,
**
kwargs
):
def
on_asset_created_or_update
(
sender
,
instance
=
None
,
created
=
False
,
**
kwargs
):
"""
当资产创建时,更新硬件信息,更新可连接性
"""
if
created
:
if
created
:
logger
.
info
(
"Asset `{}` create signal received"
.
format
(
instance
))
logger
.
info
(
"Asset `{}` create signal received"
.
format
(
instance
))
...
@@ -37,78 +42,81 @@ def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
...
@@ -37,78 +42,81 @@ def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
update_asset_hardware_info_on_created
(
instance
)
update_asset_hardware_info_on_created
(
instance
)
test_asset_conn_on_created
(
instance
)
test_asset_conn_on_created
(
instance
)
# 过期节点资产数量
Node
.
refresh_nodes
()
@receiver
(
pre_delete
,
sender
=
Asset
,
dispatch_uid
=
"my_unique_identifier"
)
@receiver
(
pre_delete
,
sender
=
Asset
,
dispatch_uid
=
"my_unique_identifier"
)
def
on_asset_delete
(
sender
,
instance
=
None
,
**
kwargs
):
def
on_asset_delete
(
sender
,
instance
=
None
,
**
kwargs
):
# 过期节点资产数量
"""
当资产删除时,刷新节点,节点中存在节点和资产的关系
"""
Node
.
refresh_nodes
()
Node
.
refresh_nodes
()
@receiver
(
post_save
,
sender
=
SystemUser
,
dispatch_uid
=
"my_unique_identifier"
)
@receiver
(
post_save
,
sender
=
SystemUser
,
dispatch_uid
=
"my_unique_identifier"
)
def
on_system_user_update
(
sender
,
instance
=
None
,
created
=
True
,
**
kwargs
):
def
on_system_user_update
(
sender
,
instance
=
None
,
created
=
True
,
**
kwargs
):
"""
当系统用户更新时,可能更新了秘钥,用户名等,这时要自动推送系统用户到资产上,
其实应该当 用户名,密码,秘钥 sudo等更新时再推送,这里偷个懒,
这里直接取了 instance.assets 因为nodes和系统用户发生变化时,会自动将nodes下的资产
关联到上面
"""
if
instance
and
not
created
:
if
instance
and
not
created
:
logger
.
info
(
"System user `{}` update signal received"
.
format
(
instance
))
logger
.
info
(
"System user `{}` update signal received"
.
format
(
instance
))
assets
=
instance
.
assets
.
all
()
assets
=
instance
.
assets
.
all
()
.
valid
()
push_system_user_to_assets
.
delay
(
instance
,
assets
)
push_system_user_to_assets
.
delay
(
instance
,
assets
)
# @receiver(m2m_changed, sender=SystemUser.nodes.through)
@receiver
(
m2m_changed
,
sender
=
SystemUser
.
assets
.
through
,
dispatch_uid
=
"my_unique_identifier"
)
# def on_system_user_nodes_change(sender, instance=None, **kwargs):
# if instance and kwargs["action"] == "post_add":
# logger.info("System user `{}` nodes update signal received".format(instance))
# assets = set()
# nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
# for node in nodes:
# assets.update(set(node.get_all_assets()))
# instance.assets.add(*tuple(assets))
#
@receiver
(
m2m_changed
,
sender
=
SystemUser
.
assets
.
through
)
def
on_system_user_assets_change
(
sender
,
instance
=
None
,
**
kwargs
):
def
on_system_user_assets_change
(
sender
,
instance
=
None
,
**
kwargs
):
"""
当系统用户和资产关系发生变化时,应该重新推送系统用户到新添加的资产中
"""
if
instance
and
kwargs
[
"action"
]
==
"post_add"
:
if
instance
and
kwargs
[
"action"
]
==
"post_add"
:
assets
=
kwargs
[
'model'
]
.
objects
.
filter
(
pk__in
=
kwargs
[
'pk_set'
])
assets
=
kwargs
[
'model'
]
.
objects
.
filter
(
pk__in
=
kwargs
[
'pk_set'
])
push_system_user_to_assets
.
delay
(
instance
,
assets
)
push_system_user_to_assets
.
delay
(
instance
,
assets
)
@receiver
(
m2m_changed
,
sender
=
Asset
.
nodes
.
through
)
@receiver
(
m2m_changed
,
sender
=
SystemUser
.
nodes
.
through
,
dispatch_uid
=
"my_unique_identifier"
)
def
on_asset_node_changed
(
sender
,
instance
=
None
,
**
kwargs
):
def
on_system_user_nodes_change
(
sender
,
instance
=
None
,
**
kwargs
):
logger
.
debug
(
"Asset nodes change signal received"
)
"""
Asset
.
expire_all_nodes_keys_cache
()
当系统用户和节点关系发生变化时,应该将节点关联到新的系统用户上
"""
if
instance
and
kwargs
[
"action"
]
==
"post_add"
:
logger
.
info
(
"System user `{}` nodes update signal received"
.
format
(
instance
))
nodes_keys
=
kwargs
[
'model'
]
.
objects
.
filter
(
pk__in
=
kwargs
[
'pk_set'
]
)
.
values_list
(
'key'
,
flat
=
True
)
assets
=
Node
.
get_nodes_all_assets
(
nodes_keys
)
instance
.
assets
.
add
(
*
tuple
(
assets
))
@receiver
(
m2m_changed
,
sender
=
Asset
.
nodes
.
through
,
dispatch_uid
=
"my_unique_identifier"
)
def
on_asset_nodes_changed
(
sender
,
instance
=
None
,
**
kwargs
):
"""
当资产的节点发生变化时,或者 当节点的资产关系发生变化时,
节点下新增的资产,添加到节点关联的系统用户中
并刷新节点
"""
if
isinstance
(
instance
,
Asset
):
if
isinstance
(
instance
,
Asset
):
# nodes = []
logger
.
debug
(
"Asset nodes change signal received: {}"
.
format
(
instance
))
# if kwargs['action'] == 'pre_remove':
# 节点资产发生变化时,将资产关联到节点关联的系统用户
# nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
if
kwargs
[
'action'
]
==
'post_add'
:
if
kwargs
[
'action'
]
==
'post_add'
:
nodes
=
kwargs
[
'model'
]
.
objects
.
filter
(
pk__in
=
kwargs
[
'pk_set'
])
nodes
=
kwargs
[
'model'
]
.
objects
.
filter
(
pk__in
=
kwargs
[
'pk_set'
])
system_users_assets
=
defaultdict
(
set
)
system_users_assets
=
defaultdict
(
set
)
system_users
=
SystemUser
.
objects
.
filter
(
nodes__in
=
nodes
)
system_users
=
SystemUser
.
objects
.
filter
(
nodes__in
=
nodes
)
for
system_user
in
system_users
:
for
system_user
in
system_users
:
system_users_assets
[
system_user
]
.
update
({
instance
}
)
system_users_assets
[
system_user
]
.
add
(
instance
)
for
system_user
,
assets
in
system_users_assets
.
items
():
for
system_user
,
assets
in
system_users_assets
.
items
():
system_user
.
assets
.
add
(
*
tuple
(
assets
))
system_user
.
assets
.
add
(
*
tuple
(
assets
))
Node
.
refresh_nodes
()
@receiver
(
m2m_changed
,
sender
=
Asset
.
nodes
.
through
)
def
on_node_assets_changed
(
sender
,
instance
=
None
,
**
kwargs
):
if
isinstance
(
instance
,
Node
):
if
isinstance
(
instance
,
Node
):
logger
.
debug
(
"Node assets change signal {} received"
.
format
(
instance
))
logger
.
debug
(
"Node assets change signal received: {}"
.
format
(
instance
))
# 当节点和资产关系发生改变时,过期资产数量缓存
assets
=
kwargs
[
'model'
]
.
objects
.
filter
(
pk__in
=
kwargs
[
'pk_set'
])
Node
.
refresh_nodes
()
if
kwargs
[
'action'
]
==
'post_add'
:
# 重新关联系统用户和资产的关系
system_users
=
SystemUser
.
objects
.
filter
(
nodes
=
instance
)
for
system_user
in
system_users
:
system_user
.
assets
.
add
(
*
tuple
(
assets
))
@receiver
(
post_save
,
sender
=
Node
)
@receiver
(
post_save
,
sender
=
Node
)
def
on_node_update_or_created
(
sender
,
instance
=
None
,
created
=
False
,
**
kwargs
):
def
on_node_update_or_created
(
sender
,
instance
=
None
,
created
=
False
,
**
kwargs
):
if
instance
and
not
created
:
# 刷新节点
Node
.
refresh_nodes
()
Node
.
refresh_nodes
()
@receiver
(
post_save
,
sender
=
AuthBook
)
@receiver
(
post_save
,
sender
=
AuthBook
)
...
...
apps/assets/views/asset.py
View file @
1fe18e80
...
@@ -32,7 +32,7 @@ class AssetListView(PermissionsMixin, TemplateView):
...
@@ -32,7 +32,7 @@ class AssetListView(PermissionsMixin, TemplateView):
permission_classes
=
[
IsOrgAdmin
]
permission_classes
=
[
IsOrgAdmin
]
def
get_context_data
(
self
,
**
kwargs
):
def
get_context_data
(
self
,
**
kwargs
):
Node
.
root
()
Node
.
org_
root
()
context
=
{
context
=
{
'app'
:
_
(
'Assets'
),
'app'
:
_
(
'Assets'
),
'action'
:
_
(
'Asset list'
),
'action'
:
_
(
'Asset list'
),
...
@@ -85,7 +85,7 @@ class AssetCreateView(PermissionsMixin, FormMixin, TemplateView):
...
@@ -85,7 +85,7 @@ class AssetCreateView(PermissionsMixin, FormMixin, TemplateView):
if
node_id
:
if
node_id
:
node
=
get_object_or_none
(
Node
,
id
=
node_id
)
node
=
get_object_or_none
(
Node
,
id
=
node_id
)
else
:
else
:
node
=
Node
.
root
()
node
=
Node
.
org_
root
()
form
[
"nodes"
]
.
initial
=
node
form
[
"nodes"
]
.
initial
=
node
return
form
return
form
...
...
apps/authentication/templates/authentication/_access_key_modal.html
View file @
1fe18e80
...
@@ -40,7 +40,7 @@
...
@@ -40,7 +40,7 @@
<script>
<script>
var
table
=
null
;
var
table
=
null
;
function
initTable
()
{
function
init
AccessKey
Table
()
{
var
options
=
{
var
options
=
{
ele
:
$
(
'#access_key_list_table'
),
ele
:
$
(
'#access_key_list_table'
),
columnDefs
:
[
columnDefs
:
[
...
@@ -93,7 +93,7 @@ function initTable() {
...
@@ -93,7 +93,7 @@ function initTable() {
$
(
document
).
ready
(
function
()
{
$
(
document
).
ready
(
function
()
{
}).
on
(
"show.bs.modal"
,
"#access_key_modal"
,
function
()
{
}).
on
(
"show.bs.modal"
,
"#access_key_modal"
,
function
()
{
if
(
!
table
)
{
if
(
!
table
)
{
initTable
()
init
AccessKey
Table
()
}
}
}).
on
(
"click"
,
"#create-btn"
,
function
()
{
}).
on
(
"click"
,
"#create-btn"
,
function
()
{
var
url
=
"{% url "
api
-
auth
:
access
-
key
-
list
" %}"
;
var
url
=
"{% url "
api
-
auth
:
access
-
key
-
list
" %}"
;
...
...
apps/orgs/signals_handler.py
View file @
1fe18e80
...
@@ -16,7 +16,7 @@ def on_org_create_or_update(sender, instance=None, created=False, **kwargs):
...
@@ -16,7 +16,7 @@ def on_org_create_or_update(sender, instance=None, created=False, **kwargs):
if
instance
:
if
instance
:
old_org
=
get_current_org
()
old_org
=
get_current_org
()
set_current_org
(
instance
)
set_current_org
(
instance
)
node_root
=
Node
.
root
()
node_root
=
Node
.
org_
root
()
if
node_root
.
value
!=
instance
.
name
:
if
node_root
.
value
!=
instance
.
name
:
node_root
.
value
=
instance
.
name
node_root
.
value
=
instance
.
name
node_root
.
save
()
node_root
.
save
()
...
...
apps/perms/models/asset_permission.py
View file @
1fe18e80
...
@@ -97,6 +97,7 @@ class AssetPermission(BasePermission):
...
@@ -97,6 +97,7 @@ class AssetPermission(BasePermission):
args
=
[
Q
(
granted_by_permissions
=
self
)]
args
=
[
Q
(
granted_by_permissions
=
self
)]
pattern
=
set
()
pattern
=
set
()
nodes_keys
=
self
.
nodes
.
all
()
.
values_list
(
'key'
,
flat
=
True
)
nodes_keys
=
self
.
nodes
.
all
()
.
values_list
(
'key'
,
flat
=
True
)
nodes_keys
=
Node
.
clean_children_keys
(
nodes_keys
)
for
key
in
nodes_keys
:
for
key
in
nodes_keys
:
pattern
.
add
(
r'^{0}$|^{0}:'
.
format
(
key
))
pattern
.
add
(
r'^{0}$|^{0}:'
.
format
(
key
))
pattern
=
'|'
.
join
(
list
(
pattern
))
pattern
=
'|'
.
join
(
list
(
pattern
))
...
...
apps/perms/signals_handler.py
View file @
1fe18e80
...
@@ -2,24 +2,15 @@
...
@@ -2,24 +2,15 @@
#
#
from
django.db.models.signals
import
m2m_changed
,
post_save
,
post_delete
from
django.db.models.signals
import
m2m_changed
,
post_save
,
post_delete
from
django.dispatch
import
receiver
from
django.dispatch
import
receiver
from
django.db
import
transaction
from
common.utils
import
get_logger
from
common.utils
import
get_logger
from
common.decorator
import
on_transaction_commit
from
.models
import
AssetPermission
from
.models
import
AssetPermission
logger
=
get_logger
(
__file__
)
logger
=
get_logger
(
__file__
)
def
on_transaction_commit
(
func
):
"""
如果不调用on_commit, 对象创建时添加多对多字段值失败
"""
def
inner
(
*
args
,
**
kwargs
):
transaction
.
on_commit
(
lambda
:
func
(
*
args
,
**
kwargs
))
return
inner
@receiver
(
post_save
,
sender
=
AssetPermission
,
dispatch_uid
=
"my_unique_identifier"
)
@receiver
(
post_save
,
sender
=
AssetPermission
,
dispatch_uid
=
"my_unique_identifier"
)
@on_transaction_commit
@on_transaction_commit
def
on_permission_created
(
sender
,
instance
=
None
,
created
=
False
,
**
kwargs
):
def
on_permission_created
(
sender
,
instance
=
None
,
created
=
False
,
**
kwargs
):
...
...
apps/perms/utils/asset_permission.py
View file @
1fe18e80
# coding: utf-8
# coding: utf-8
import
re
from
collections
import
defaultdict
from
collections
import
defaultdict
from
functools
import
reduce
from
functools
import
reduce
...
@@ -250,6 +249,7 @@ class AssetPermissionUtilV2:
...
@@ -250,6 +249,7 @@ class AssetPermissionUtilV2:
return
system_users_actions
return
system_users_actions
def
get_permissions_nodes_and_assets
(
self
):
def
get_permissions_nodes_and_assets
(
self
):
from
assets.models
import
Node
permissions
=
self
.
permissions
.
values_list
(
'assets'
,
'nodes__key'
)
.
distinct
()
permissions
=
self
.
permissions
.
values_list
(
'assets'
,
'nodes__key'
)
.
distinct
()
nodes_keys
=
set
()
nodes_keys
=
set
()
assets_ids
=
set
()
assets_ids
=
set
()
...
@@ -258,7 +258,7 @@ class AssetPermissionUtilV2:
...
@@ -258,7 +258,7 @@ class AssetPermissionUtilV2:
assets_ids
.
add
(
asset_id
)
assets_ids
.
add
(
asset_id
)
if
node_key
:
if
node_key
:
nodes_keys
.
add
(
node_key
)
nodes_keys
.
add
(
node_key
)
nodes_keys
=
self
.
clean_nodes
_keys
(
nodes_keys
)
nodes_keys
=
Node
.
clean_children
_keys
(
nodes_keys
)
return
nodes_keys
,
assets_ids
return
nodes_keys
,
assets_ids
@staticmethod
@staticmethod
...
@@ -297,20 +297,6 @@ class AssetPermissionUtilV2:
...
@@ -297,20 +297,6 @@ class AssetPermissionUtilV2:
queryset
=
Asset
.
objects
.
filter
(
id__in
=
assets_ids
)
queryset
=
Asset
.
objects
.
filter
(
id__in
=
assets_ids
)
return
queryset
.
valid
()
.
distinct
()
return
queryset
.
valid
()
.
distinct
()
@staticmethod
def
clean_nodes_keys
(
nodes_keys
):
nodes_keys
=
sorted
(
list
(
nodes_keys
),
key
=
lambda
x
:
(
len
(
x
),
x
))
nodes_keys_clean
=
[]
for
key
in
nodes_keys
[::
-
1
]:
found
=
False
for
k
in
nodes_keys
:
if
key
.
startswith
(
k
+
':'
):
found
=
True
break
if
not
found
:
nodes_keys_clean
.
append
(
key
)
return
nodes_keys_clean
def
get_nodes
(
self
):
def
get_nodes
(
self
):
return
[
n
.
identifier
for
n
in
self
.
user_tree
.
all_nodes_itr
()]
return
[
n
.
identifier
for
n
in
self
.
user_tree
.
all_nodes_itr
()]
...
@@ -357,7 +343,7 @@ class ParserNode:
...
@@ -357,7 +343,7 @@ class ParserNode:
'title'
:
name
,
'title'
:
name
,
'pId'
:
node
.
parent_key
,
'pId'
:
node
.
parent_key
,
'isParent'
:
True
,
'isParent'
:
True
,
'open'
:
node
.
is_root
(),
'open'
:
node
.
is_
org_
root
(),
'meta'
:
{
'meta'
:
{
'node'
:
{
'node'
:
{
"id"
:
node
.
id
,
"id"
:
node
.
id
,
...
...
apps/perms/views/asset_permission.py
View file @
1fe18e80
...
@@ -51,7 +51,8 @@ class AssetPermissionCreateView(PermissionsMixin, CreateView):
...
@@ -51,7 +51,8 @@ class AssetPermissionCreateView(PermissionsMixin, CreateView):
if
nodes_id
:
if
nodes_id
:
nodes_id
=
nodes_id
.
split
(
","
)
nodes_id
=
nodes_id
.
split
(
","
)
nodes
=
Node
.
objects
.
filter
(
id__in
=
nodes_id
)
.
exclude
(
id
=
Node
.
root
()
.
id
)
nodes
=
Node
.
objects
.
filter
(
id__in
=
nodes_id
)
\
.
exclude
(
id
=
Node
.
org_root
()
.
id
)
form
[
'nodes'
]
.
initial
=
nodes
form
[
'nodes'
]
.
initial
=
nodes
if
assets_id
:
if
assets_id
:
assets_id
=
assets_id
.
split
(
","
)
assets_id
=
assets_id
.
split
(
","
)
...
...
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