mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-12-21 03:26:03 +00:00
Compare commits
325 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69ab3e3542 | ||
|
|
ff1b902b2e | ||
|
|
52647da79b | ||
|
|
28b6144189 | ||
|
|
f7daf26a03 | ||
|
|
e1673334af | ||
|
|
fd6e561d4b | ||
|
|
4cdddaa493 | ||
|
|
9491827e01 | ||
|
|
5459d1114f | ||
|
|
6acda27d67 | ||
|
|
0f9326bd8f | ||
|
|
e09f3ca4fd | ||
|
|
1fcb272ddc | ||
|
|
b577c626f7 | ||
|
|
2e4e5503cc | ||
|
|
4212cb3600 | ||
|
|
b8874e1855 | ||
|
|
9bb498f7b3 | ||
|
|
e38d089056 | ||
|
|
f9e9bf0b2d | ||
|
|
1e5387ef47 | ||
|
|
f87e08efff | ||
|
|
82d866db7d | ||
|
|
ba0d822734 | ||
|
|
c8568eb244 | ||
|
|
6e19b9d5bc | ||
|
|
354b728f75 | ||
|
|
ce553710ba | ||
|
|
4f806f11f2 | ||
|
|
0a94a346a0 | ||
|
|
d8afe72d4c | ||
|
|
e2072a1e02 | ||
|
|
cc387bf511 | ||
|
|
5c002e91ee | ||
|
|
41a8831034 | ||
|
|
ebd92c79c7 | ||
|
|
6278900201 | ||
|
|
4f580e0df8 | ||
|
|
1f502e02c7 | ||
|
|
cdf8398169 | ||
|
|
1bfef829f3 | ||
|
|
cc0cf8ed1c | ||
|
|
2791213844 | ||
|
|
284e8be45c | ||
|
|
76109f1808 | ||
|
|
54b6e06d1f | ||
|
|
5c30c76ea3 | ||
|
|
94b5eb8685 | ||
|
|
c9f4b104c7 | ||
|
|
3bf1c036c5 | ||
|
|
09fbd3a5ab | ||
|
|
ebecd00581 | ||
|
|
143fa060d1 | ||
|
|
2c18a27e3a | ||
|
|
910dd4e593 | ||
|
|
11aefa479b | ||
|
|
abc56016f2 | ||
|
|
f44db2a25b | ||
|
|
3fa6807837 | ||
|
|
2c4195d619 | ||
|
|
265ef0c8ac | ||
|
|
0b0b06a5c2 | ||
|
|
d77ba1d5ea | ||
|
|
a3bd7cee80 | ||
|
|
8d73cd43e1 | ||
|
|
7e3fd73ae5 | ||
|
|
70960d2ae4 | ||
|
|
d2c574fe9d | ||
|
|
a70fcf057b | ||
|
|
f37582ec53 | ||
|
|
29b87c40fe | ||
|
|
1ec77c5bb9 | ||
|
|
21c71aba93 | ||
|
|
f8db9f480e | ||
|
|
0665644fd0 | ||
|
|
7bafa546b5 | ||
|
|
666815b324 | ||
|
|
532abb86b5 | ||
|
|
76d4e4ad55 | ||
|
|
70fa43adaa | ||
|
|
44bf01d4ed | ||
|
|
1341983fd3 | ||
|
|
78936bf9f2 | ||
|
|
9a5d3cb475 | ||
|
|
9bddc29da4 | ||
|
|
d68a4d9cae | ||
|
|
5457118fb6 | ||
|
|
7ee68f7eeb | ||
|
|
2063f2f257 | ||
|
|
2637c608a6 | ||
|
|
32519ea326 | ||
|
|
3ce9d01b6d | ||
|
|
310bc6ad0b | ||
|
|
b54afbe7bb | ||
|
|
ab848afdb9 | ||
|
|
5bb867d10d | ||
|
|
0eda8865e6 | ||
|
|
2a37107abc | ||
|
|
c78107f62f | ||
|
|
b022bf36ba | ||
|
|
6dc2272a26 | ||
|
|
1d462aea1b | ||
|
|
88a29c0a93 | ||
|
|
9ffae722f3 | ||
|
|
9ab2f4bc56 | ||
|
|
41e7f45c20 | ||
|
|
9945ac172b | ||
|
|
67ddd42b3d | ||
|
|
03adddefa3 | ||
|
|
60b7ccddc0 | ||
|
|
1194932bc0 | ||
|
|
5c8fd91cf9 | ||
|
|
bb13003a10 | ||
|
|
9a18817dbb | ||
|
|
2c4966c678 | ||
|
|
d1390a1cd7 | ||
|
|
fe45d839fb | ||
|
|
9f96f1c537 | ||
|
|
dc918c031c | ||
|
|
6b047ca702 | ||
|
|
47d31005b5 | ||
|
|
57e1ca93f0 | ||
|
|
483a7617ce | ||
|
|
5470ab752e | ||
|
|
2dbd6b6f6e | ||
|
|
504d9242c6 | ||
|
|
14b1e3fa13 | ||
|
|
7eeca511f1 | ||
|
|
670c8a6d0b | ||
|
|
a2aa923abe | ||
|
|
2ac5786ba1 | ||
|
|
5b93a1a0a5 | ||
|
|
00928dd46d | ||
|
|
7ddf7f2a79 | ||
|
|
3533bf588b | ||
|
|
dea007f27b | ||
|
|
cd2b88caee | ||
|
|
1877511acf | ||
|
|
1c5ce61ed0 | ||
|
|
b1132bfc37 | ||
|
|
75e67410cf | ||
|
|
c9d137bc20 | ||
|
|
d97e606503 | ||
|
|
e59b95e97a | ||
|
|
bb6394150d | ||
|
|
2354f0c970 | ||
|
|
ae564ed0d4 | ||
|
|
05ecd7497a | ||
|
|
6b86b8b485 | ||
|
|
fa0bd85fd4 | ||
|
|
7da46354ca | ||
|
|
e41aad1576 | ||
|
|
3f049440b7 | ||
|
|
4f532f588b | ||
|
|
a792781b98 | ||
|
|
3a4c7846bf | ||
|
|
ccc292d9a9 | ||
|
|
337338ebf3 | ||
|
|
aa3bc7b53a | ||
|
|
d5451a482a | ||
|
|
534734881c | ||
|
|
8236c7baa0 | ||
|
|
96ec5fac99 | ||
|
|
b7fcf80fc5 | ||
|
|
de3695bf97 | ||
|
|
7c814080b2 | ||
|
|
f5531b6065 | ||
|
|
b0aa9f197a | ||
|
|
fc156e23f3 | ||
|
|
1a05dab572 | ||
|
|
b8ecb6f81d | ||
|
|
c01936facc | ||
|
|
90c629c837 | ||
|
|
c9d192eefc | ||
|
|
9c4ebf9c75 | ||
|
|
37d89b4ea2 | ||
|
|
87e0e1f2c4 | ||
|
|
183ff09530 | ||
|
|
485a178c0a | ||
|
|
227cc4e965 | ||
|
|
01bef95e6e | ||
|
|
672dd66023 | ||
|
|
c032294b14 | ||
|
|
6ce813faf8 | ||
|
|
aefc18d73b | ||
|
|
23815f87c5 | ||
|
|
206e037cf2 | ||
|
|
492fd98882 | ||
|
|
d92d462dab | ||
|
|
8afd5ef90a | ||
|
|
d3dca5d077 | ||
|
|
9166a26f80 | ||
|
|
3039284666 | ||
|
|
2f395794ef | ||
|
|
c6d50802db | ||
|
|
a10e47f72c | ||
|
|
3dc214d1fa | ||
|
|
f7fb36a176 | ||
|
|
0d7295b60e | ||
|
|
8f654c37a9 | ||
|
|
b29a541aa6 | ||
|
|
9fd52f6665 | ||
|
|
f4c86718dc | ||
|
|
4ff7a1f066 | ||
|
|
eca245fdd5 | ||
|
|
7e3cf908a1 | ||
|
|
dded4e10fb | ||
|
|
45a354f848 | ||
|
|
8386f107c6 | ||
|
|
5ce3dd4079 | ||
|
|
a48fb9de8d | ||
|
|
04e7f54c69 | ||
|
|
d649aacfd6 | ||
|
|
7e65e44a3c | ||
|
|
74c3f12275 | ||
|
|
8c12c382a5 | ||
|
|
2ecfecb06f | ||
|
|
ac238aa36e | ||
|
|
2abb9efe96 | ||
|
|
f17727deb9 | ||
|
|
36f1165d1b | ||
|
|
e7c530d8e6 | ||
|
|
b156f4ad16 | ||
|
|
16b23a37fe | ||
|
|
e41add6126 | ||
|
|
f4c31d8e86 | ||
|
|
28e8f204ec | ||
|
|
80f147cf13 | ||
|
|
c816875f28 | ||
|
|
1c56ba5a11 | ||
|
|
2208d6d51e | ||
|
|
e3aa18ff2d | ||
|
|
b5f6f80ae6 | ||
|
|
bbe4080008 | ||
|
|
cd797b18fb | ||
|
|
b6523da603 | ||
|
|
c24f1a0517 | ||
|
|
83f220d7de | ||
|
|
8e42a65736 | ||
|
|
e1fff18ce3 | ||
|
|
3052744203 | ||
|
|
7924b094f8 | ||
|
|
3d34b06203 | ||
|
|
c94d018d7e | ||
|
|
53086a8977 | ||
|
|
ce1fc0f3e2 | ||
|
|
c215278978 | ||
|
|
061963a316 | ||
|
|
97b240cfdd | ||
|
|
fd5f562cbf | ||
|
|
722bf786f1 | ||
|
|
2cb5876d1a | ||
|
|
8883e0090f | ||
|
|
790652ff4d | ||
|
|
ff3f74abe6 | ||
|
|
1a49cf4d9c | ||
|
|
4d1da56872 | ||
|
|
ff9e109a2c | ||
|
|
09e636495e | ||
|
|
14076d8fe1 | ||
|
|
812078331e | ||
|
|
696589a3cf | ||
|
|
7eba46b303 | ||
|
|
5d63d2369f | ||
|
|
3e17e94245 | ||
|
|
5648dcd7e7 | ||
|
|
28e47f33c1 | ||
|
|
91b3b7ce69 | ||
|
|
8c587a1376 | ||
|
|
1182313c1a | ||
|
|
7412bdcba7 | ||
|
|
d6ec92d82d | ||
|
|
ad3214641d | ||
|
|
9004351ad1 | ||
|
|
d3e22a2a90 | ||
|
|
fd3df81a64 | ||
|
|
72517a2c72 | ||
|
|
01185a2d07 | ||
|
|
8bfd2be21f | ||
|
|
b03ac46df9 | ||
|
|
76be054fcb | ||
|
|
df95c93bb1 | ||
|
|
eaefb5c669 | ||
|
|
0ddb9476ba | ||
|
|
75a9deebdd | ||
|
|
b6cd4a20c5 | ||
|
|
435acafccd | ||
|
|
86e4bc5e9a | ||
|
|
315609bc45 | ||
|
|
814e6a7df8 | ||
|
|
7a7c6d40df | ||
|
|
5d800fa629 | ||
|
|
bd14266abd | ||
|
|
88f36c6f02 | ||
|
|
512fc8f8f0 | ||
|
|
37bb344166 | ||
|
|
f8c2a445f7 | ||
|
|
ff9b1a887f | ||
|
|
43370c547a | ||
|
|
2eda58eadd | ||
|
|
e1be867913 | ||
|
|
01afcf701c | ||
|
|
4002289974 | ||
|
|
ef9e03c7ed | ||
|
|
442d4e727a | ||
|
|
3993797527 | ||
|
|
f1f06491d6 | ||
|
|
eb95a0a912 | ||
|
|
401a7f88a8 | ||
|
|
0a2ff83ca1 | ||
|
|
7276bd0b2a | ||
|
|
2950613b69 | ||
|
|
6fa0562d7a | ||
|
|
ef22d33afa | ||
|
|
86404db6c7 | ||
|
|
fdf2807d9b | ||
|
|
2e6d238c76 | ||
|
|
f5a4370b80 | ||
|
|
db2273ef27 | ||
|
|
dd07fa678f | ||
|
|
a2f23e9681 | ||
|
|
7c35e75586 | ||
|
|
4fd83bd5be | ||
|
|
6035d1f130 |
3
.github/ISSUE_TEMPLATE.md
vendored
3
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,7 +1,8 @@
|
||||
[简述你的问题]
|
||||
|
||||
|
||||
##### 使用版本
|
||||
[请提供你使用的Jumpserver版本 0.3.2 或 0.5.0]
|
||||
[请提供你使用的Jumpserver版本 1.x.x 注: 0.3.x不再提供支持]
|
||||
|
||||
##### 问题复现步骤
|
||||
1. [步骤1]
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -32,3 +32,4 @@ django.db
|
||||
celerybeat-schedule.db
|
||||
data/static
|
||||
docs/_build/
|
||||
xpack
|
||||
|
||||
18
README.md
18
README.md
@@ -1,9 +1,9 @@
|
||||
## Jumpserver
|
||||
|
||||
[](https://www.python.org/)
|
||||
[](https://www.djangoproject.com/)
|
||||
[](https://www.ansible.com/)
|
||||
[](http://www.paramiko.org/)
|
||||
[](https://www.djangoproject.com/)
|
||||
[](https://www.ansible.com/)
|
||||
[](http://www.paramiko.org/)
|
||||
|
||||
|
||||
----
|
||||
@@ -19,25 +19,25 @@ Jumpserver采纳分布式架构,支持多机房跨区域部署,中心节点
|
||||
----
|
||||
|
||||
### 功能
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### 开始使用
|
||||
|
||||
快速开始文档 [Docker安装](http://docs.jumpserver.org/zh/latest/quickstart.html)
|
||||
快速开始文档 [Docker安装](http://docs.jumpserver.org/zh/docs/dockerinstall.html)
|
||||
|
||||
一步一步安装文档 [详细部署](http://docs.jumpserver.org/zh/latest/step_by_step.html)
|
||||
一步一步安装文档 [详细部署](http://docs.jumpserver.org/zh/docs/step_by_step.html)
|
||||
|
||||
也可以查看我们完整文档包括了使用和开发 [文档](http://docs.jumpserver.org)
|
||||
|
||||
### Demo 和 截图
|
||||
### Demo 和 截图
|
||||
|
||||
我们提供了DEMO和截图可以让你快速了解Jumpserver
|
||||
|
||||
[DEMO](http://demo.jumpserver.org)
|
||||
[截图](http://docs.jumpserver.org/zh/docs/snapshot.html)
|
||||
|
||||
### SDK
|
||||
### SDK
|
||||
|
||||
我们还编写了一些SDK,供你其它系统快速和Jumpserver APi交互,
|
||||
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
__version__ = "1.3.2"
|
||||
__version__ = "1.4.4"
|
||||
|
||||
@@ -4,3 +4,4 @@ from .label import *
|
||||
from .system_user import *
|
||||
from .node import *
|
||||
from .domain import *
|
||||
from .cmd_filter import *
|
||||
|
||||
@@ -17,10 +17,11 @@ from django.db import transaction
|
||||
from rest_framework import generics
|
||||
from rest_framework.response import Response
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from rest_framework.pagination import LimitOffsetPagination
|
||||
|
||||
from common.mixins import IDInFilterMixin
|
||||
from common.utils import get_logger
|
||||
from ..hands import IsSuperUser
|
||||
from ..hands import IsOrgAdmin
|
||||
from ..models import AdminUser, Asset
|
||||
from .. import serializers
|
||||
from ..tasks import test_admin_user_connectability_manual
|
||||
@@ -37,21 +38,29 @@ class AdminUserViewSet(IDInFilterMixin, BulkModelViewSet):
|
||||
"""
|
||||
Admin user api set, for add,delete,update,list,retrieve resource
|
||||
"""
|
||||
|
||||
filter_fields = ("name", "username")
|
||||
search_fields = filter_fields
|
||||
queryset = AdminUser.objects.all()
|
||||
serializer_class = serializers.AdminUserSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
pagination_class = LimitOffsetPagination
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset().all()
|
||||
return queryset
|
||||
|
||||
|
||||
class AdminUserAuthApi(generics.UpdateAPIView):
|
||||
queryset = AdminUser.objects.all()
|
||||
serializer_class = serializers.AdminUserAuthSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
|
||||
class ReplaceNodesAdminUserApi(generics.UpdateAPIView):
|
||||
queryset = AdminUser.objects.all()
|
||||
serializer_class = serializers.ReplaceNodeAdminUserSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
admin_user = self.get_object()
|
||||
@@ -75,7 +84,7 @@ class AdminUserTestConnectiveApi(generics.RetrieveAPIView):
|
||||
Test asset admin user connectivity
|
||||
"""
|
||||
queryset = AdminUser.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
admin_user = self.get_object()
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import random
|
||||
|
||||
from rest_framework import generics
|
||||
from rest_framework.response import Response
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
@@ -11,8 +13,8 @@ from django.db.models import Q
|
||||
|
||||
from common.mixins import IDInFilterMixin
|
||||
from common.utils import get_logger
|
||||
from ..hands import IsSuperUser, IsValidUser, IsSuperUserOrAppUser
|
||||
from ..models import Asset, SystemUser, AdminUser, Node
|
||||
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
|
||||
from ..models import Asset, AdminUser, Node
|
||||
from .. import serializers
|
||||
from ..tasks import update_asset_hardware_info_manual, \
|
||||
test_asset_connectability_manual
|
||||
@@ -22,7 +24,8 @@ from ..utils import LabelFilter
|
||||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'AssetViewSet', 'AssetListUpdateApi',
|
||||
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi'
|
||||
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi',
|
||||
'AssetGatewayApi'
|
||||
]
|
||||
|
||||
|
||||
@@ -36,38 +39,42 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetSerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()\
|
||||
.prefetch_related('labels', 'nodes')\
|
||||
.select_related('admin_user')
|
||||
admin_user_id = self.request.query_params.get('admin_user_id')
|
||||
def filter_node(self):
|
||||
node_id = self.request.query_params.get("node_id")
|
||||
show_current_asset = self.request.query_params.get("show_current_asset")
|
||||
if not node_id:
|
||||
return
|
||||
|
||||
node = get_object_or_404(Node, id=node_id)
|
||||
show_current_asset = self.request.query_params.get("show_current_asset") in ('1', 'true')
|
||||
|
||||
if node.is_root():
|
||||
if show_current_asset:
|
||||
self.queryset = self.queryset.filter(
|
||||
Q(nodes=node_id) | Q(nodes__isnull=True)
|
||||
)
|
||||
return
|
||||
if show_current_asset:
|
||||
self.queryset = self.queryset.filter(nodes=node)
|
||||
else:
|
||||
self.queryset = self.queryset.filter(
|
||||
nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key),
|
||||
)
|
||||
|
||||
def filter_admin_user_id(self):
|
||||
admin_user_id = self.request.query_params.get('admin_user_id')
|
||||
if admin_user_id:
|
||||
admin_user = get_object_or_404(AdminUser, id=admin_user_id)
|
||||
queryset = queryset.filter(admin_user=admin_user)
|
||||
self.queryset = self.queryset.filter(admin_user=admin_user)
|
||||
|
||||
if node_id and show_current_asset:
|
||||
node = get_object_or_404(Node, id=node_id)
|
||||
if node.is_root():
|
||||
queryset = queryset.filter(
|
||||
Q(nodes=node_id) | Q(nodes__isnull=True)
|
||||
).distinct()
|
||||
else:
|
||||
queryset = queryset.filter(nodes=node).distinct()
|
||||
|
||||
if node_id and not show_current_asset:
|
||||
node = get_object_or_404(Node, id=node_id)
|
||||
if node.is_root():
|
||||
queryset = Asset.objects.all()
|
||||
else:
|
||||
queryset = queryset.filter(
|
||||
nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key),
|
||||
).distinct()
|
||||
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()
|
||||
|
||||
|
||||
class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||
@@ -76,7 +83,7 @@ class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||
"""
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
|
||||
class AssetRefreshHardwareApi(generics.RetrieveAPIView):
|
||||
@@ -85,7 +92,7 @@ class AssetRefreshHardwareApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetSerializer
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
asset_id = kwargs.get('pk')
|
||||
@@ -99,10 +106,27 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView):
|
||||
Test asset admin user connectivity
|
||||
"""
|
||||
queryset = Asset.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
asset_id = kwargs.get('pk')
|
||||
asset = get_object_or_404(Asset, pk=asset_id)
|
||||
task = test_asset_connectability_manual.delay(asset)
|
||||
return Response({"task": task.id})
|
||||
|
||||
|
||||
class AssetGatewayApi(generics.RetrieveAPIView):
|
||||
queryset = Asset.objects.all()
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
asset_id = kwargs.get('pk')
|
||||
asset = get_object_or_404(Asset, pk=asset_id)
|
||||
|
||||
if asset.domain and \
|
||||
asset.domain.gateways.filter(protocol=asset.protocol).exists():
|
||||
gateway = random.choice(asset.domain.gateways.filter(protocol=asset.protocol))
|
||||
serializer = serializers.GatewayWithAuthSerializer(instance=gateway)
|
||||
return Response(serializer.data)
|
||||
else:
|
||||
return Response({"msg": "Not have gateway"}, status=404)
|
||||
39
apps/assets/api/cmd_filter.py
Normal file
39
apps/assets/api/cmd_filter.py
Normal file
@@ -0,0 +1,39 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from rest_framework.pagination import LimitOffsetPagination
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from ..hands import IsOrgAdmin
|
||||
from ..models import CommandFilter, CommandFilterRule
|
||||
from .. import serializers
|
||||
|
||||
|
||||
__all__ = ['CommandFilterViewSet', 'CommandFilterRuleViewSet']
|
||||
|
||||
|
||||
class CommandFilterViewSet(BulkModelViewSet):
|
||||
filter_fields = ("name",)
|
||||
search_fields = filter_fields
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
queryset = CommandFilter.objects.all()
|
||||
serializer_class = serializers.CommandFilterSerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
|
||||
|
||||
class CommandFilterRuleViewSet(BulkModelViewSet):
|
||||
filter_fields = ("content",)
|
||||
search_fields = filter_fields
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.CommandFilterRuleSerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
|
||||
def get_queryset(self):
|
||||
fpk = self.kwargs.get('filter_pk')
|
||||
if not fpk:
|
||||
return CommandFilterRule.objects.none()
|
||||
cmd_filter = get_object_or_404(CommandFilter, pk=fpk)
|
||||
return cmd_filter.rules.all()
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from rest_framework.views import APIView, Response
|
||||
from rest_framework.generics import RetrieveAPIView
|
||||
from rest_framework.pagination import LimitOffsetPagination
|
||||
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
|
||||
from common.utils import get_logger
|
||||
from ..hands import IsSuperUser, IsSuperUserOrAppUser
|
||||
from common.permissions import IsOrgAdmin, IsAppUser, IsOrgAdminOrAppUser
|
||||
from ..models import Domain, Gateway
|
||||
from ..utils import test_gateway_connectability
|
||||
from .. import serializers
|
||||
@@ -19,8 +19,13 @@ __all__ = ['DomainViewSet', 'GatewayViewSet', "GatewayTestConnectionApi"]
|
||||
|
||||
class DomainViewSet(BulkModelViewSet):
|
||||
queryset = Domain.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.DomainSerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset().all()
|
||||
return queryset
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.request.query_params.get('gateway'):
|
||||
@@ -29,20 +34,21 @@ class DomainViewSet(BulkModelViewSet):
|
||||
|
||||
def get_permissions(self):
|
||||
if self.request.query_params.get('gateway'):
|
||||
self.permission_classes = (IsSuperUserOrAppUser,)
|
||||
self.permission_classes = (IsOrgAdminOrAppUser,)
|
||||
return super().get_permissions()
|
||||
|
||||
|
||||
class GatewayViewSet(BulkModelViewSet):
|
||||
filter_fields = ("domain",)
|
||||
filter_fields = ("domain__name", "name", "username", "ip", "domain")
|
||||
search_fields = filter_fields
|
||||
queryset = Gateway.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.GatewaySerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
|
||||
|
||||
class GatewayTestConnectionApi(SingleObjectMixin, APIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
model = Gateway
|
||||
object = None
|
||||
|
||||
|
||||
@@ -14,10 +14,11 @@
|
||||
# limitations under the License.
|
||||
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from rest_framework.pagination import LimitOffsetPagination
|
||||
from django.db.models import Count
|
||||
|
||||
from common.utils import get_logger
|
||||
from ..hands import IsSuperUser
|
||||
from ..hands import IsOrgAdmin
|
||||
from ..models import Label
|
||||
from .. import serializers
|
||||
|
||||
@@ -27,12 +28,18 @@ __all__ = ['LabelViewSet']
|
||||
|
||||
|
||||
class LabelViewSet(BulkModelViewSet):
|
||||
queryset = Label.objects.annotate(asset_count=Count("assets"))
|
||||
permission_classes = (IsSuperUser,)
|
||||
filter_fields = ("name", "value")
|
||||
search_fields = filter_fields
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.LabelSerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
if request.query_params.get("distinct"):
|
||||
self.serializer_class = serializers.LabelDistinctSerializer
|
||||
self.queryset = self.queryset.values("name").distinct()
|
||||
return super().list(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
self.queryset = Label.objects.annotate(asset_count=Count("assets"))
|
||||
return self.queryset
|
||||
|
||||
@@ -13,16 +13,17 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from rest_framework import generics, mixins
|
||||
from rest_framework import generics, mixins, viewsets
|
||||
from rest_framework.serializers import ValidationError
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.db.models import Count
|
||||
|
||||
from common.utils import get_logger, get_object_or_none
|
||||
from ..hands import IsSuperUser
|
||||
from ..hands import IsOrgAdmin
|
||||
from ..models import Node
|
||||
from ..tasks import update_assets_hardware_info_util, test_asset_connectability_util
|
||||
from .. import serializers
|
||||
@@ -30,18 +31,16 @@ from .. import serializers
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'NodeViewSet', 'NodeChildrenApi',
|
||||
'NodeAssetsApi',
|
||||
'NodeAddAssetsApi', 'NodeRemoveAssetsApi',
|
||||
'NodeReplaceAssetsApi',
|
||||
'NodeViewSet', 'NodeChildrenApi', 'NodeAssetsApi',
|
||||
'NodeAddAssetsApi', 'NodeRemoveAssetsApi', 'NodeReplaceAssetsApi',
|
||||
'NodeAddChildrenApi', 'RefreshNodeHardwareInfoApi',
|
||||
'TestNodeConnectiveApi'
|
||||
]
|
||||
|
||||
|
||||
class NodeViewSet(BulkModelViewSet):
|
||||
class NodeViewSet(viewsets.ModelViewSet):
|
||||
queryset = Node.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.NodeSerializer
|
||||
|
||||
def perform_create(self, serializer):
|
||||
@@ -49,38 +48,22 @@ class NodeViewSet(BulkModelViewSet):
|
||||
serializer.validated_data["key"] = child_key
|
||||
serializer.save()
|
||||
|
||||
|
||||
# class NodeWithAssetsApi(generics.ListAPIView):
|
||||
# permission_classes = (IsSuperUser,)
|
||||
# serializers = serializers.NodeSerializer
|
||||
#
|
||||
# def get_node(self):
|
||||
# pk = self.kwargs.get('pk') or self.request.query_params.get('node')
|
||||
# if not pk:
|
||||
# node = Node.root()
|
||||
# else:
|
||||
# node = get_object_or_404(Node, pk)
|
||||
# return node
|
||||
#
|
||||
# def get_queryset(self):
|
||||
# queryset = []
|
||||
# node = self.get_node()
|
||||
# children = node.get_children()
|
||||
# assets = node.get_assets()
|
||||
# queryset.extend(list(children))
|
||||
#
|
||||
# for asset in assets:
|
||||
# node = Node()
|
||||
# node.id = asset.id
|
||||
# node.parent = node.id
|
||||
# node.value = asset.hostname
|
||||
# queryset.append(node)
|
||||
# return queryset
|
||||
def update(self, request, *args, **kwargs):
|
||||
node = self.get_object()
|
||||
if node.is_root():
|
||||
node_value = node.value
|
||||
post_value = request.data.get('value')
|
||||
if node_value != post_value:
|
||||
return Response(
|
||||
{"msg": _("You can't update the root node name")},
|
||||
status=400
|
||||
)
|
||||
return super().update(request, *args, **kwargs)
|
||||
|
||||
|
||||
class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
|
||||
queryset = Node.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.NodeSerializer
|
||||
instance = None
|
||||
|
||||
@@ -126,22 +109,26 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
|
||||
query_all = self.request.query_params.get("all")
|
||||
query_assets = self.request.query_params.get('assets')
|
||||
node = self.get_object()
|
||||
|
||||
if node is None:
|
||||
node = Node.root()
|
||||
node.assets__count = node.get_all_assets().count()
|
||||
queryset.append(node)
|
||||
|
||||
if query_all:
|
||||
children = node.get_all_children()
|
||||
else:
|
||||
children = node.get_children()
|
||||
|
||||
queryset.extend(list(children))
|
||||
|
||||
if query_assets:
|
||||
assets = node.get_assets()
|
||||
for asset in assets:
|
||||
node_fake = Node()
|
||||
node_fake.assets__count = 0
|
||||
node_fake.id = asset.id
|
||||
node_fake.is_node = False
|
||||
node_fake.parent_id = node.id
|
||||
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)
|
||||
@@ -152,7 +139,7 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
|
||||
|
||||
|
||||
class NodeAssetsApi(generics.ListAPIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.AssetSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
@@ -167,7 +154,7 @@ class NodeAssetsApi(generics.ListAPIView):
|
||||
|
||||
class NodeAddChildrenApi(generics.UpdateAPIView):
|
||||
queryset = Node.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.NodeAddChildrenSerializer
|
||||
instance = None
|
||||
|
||||
@@ -185,7 +172,7 @@ class NodeAddChildrenApi(generics.UpdateAPIView):
|
||||
class NodeAddAssetsApi(generics.UpdateAPIView):
|
||||
serializer_class = serializers.NodeAssetsSerializer
|
||||
queryset = Node.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
instance = None
|
||||
|
||||
def perform_update(self, serializer):
|
||||
@@ -197,7 +184,7 @@ class NodeAddAssetsApi(generics.UpdateAPIView):
|
||||
class NodeRemoveAssetsApi(generics.UpdateAPIView):
|
||||
serializer_class = serializers.NodeAssetsSerializer
|
||||
queryset = Node.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
instance = None
|
||||
|
||||
def perform_update(self, serializer):
|
||||
@@ -213,7 +200,7 @@ class NodeRemoveAssetsApi(generics.UpdateAPIView):
|
||||
class NodeReplaceAssetsApi(generics.UpdateAPIView):
|
||||
serializer_class = serializers.NodeAssetsSerializer
|
||||
queryset = Node.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
instance = None
|
||||
|
||||
def perform_update(self, serializer):
|
||||
@@ -224,26 +211,28 @@ class NodeReplaceAssetsApi(generics.UpdateAPIView):
|
||||
|
||||
|
||||
class RefreshNodeHardwareInfoApi(APIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
model = Node
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
node_id = kwargs.get('pk')
|
||||
node = get_object_or_404(self.model, id=node_id)
|
||||
assets = node.assets.all()
|
||||
task_name = _("更新节点资产硬件信息: {}".format(node.name))
|
||||
# task_name = _("更新节点资产硬件信息: {}".format(node.name))
|
||||
task_name = _("Update node asset hardware information: {}").format(node.name)
|
||||
task = update_assets_hardware_info_util.delay(assets, task_name=task_name)
|
||||
return Response({"task": task.id})
|
||||
|
||||
|
||||
class TestNodeConnectiveApi(APIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
model = Node
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
node_id = kwargs.get('pk')
|
||||
node = get_object_or_404(self.model, id=node_id)
|
||||
assets = node.assets.all()
|
||||
task_name = _("测试节点下资产是否可连接: {}".format(node.name))
|
||||
# task_name = _("测试节点下资产是否可连接: {}".format(node.name))
|
||||
task_name = _("Test if the assets under the node are connectable: {}".format(node.name))
|
||||
task = test_asset_connectability_util.delay(assets, task_name=task_name)
|
||||
return Response({"task": task.id})
|
||||
|
||||
@@ -13,21 +13,28 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework import generics
|
||||
from rest_framework.response import Response
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from rest_framework.pagination import LimitOffsetPagination
|
||||
|
||||
from common.utils import get_logger
|
||||
from ..hands import IsSuperUser, IsSuperUserOrAppUser
|
||||
from ..models import SystemUser
|
||||
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
|
||||
from ..models import SystemUser, Asset
|
||||
from .. import serializers
|
||||
from ..tasks import push_system_user_to_assets_manual, \
|
||||
test_system_user_connectability_manual
|
||||
test_system_user_connectability_manual, push_system_user_a_asset_manual, \
|
||||
test_system_user_connectability_a_asset
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'SystemUserViewSet', 'SystemUserAuthInfoApi',
|
||||
'SystemUserPushApi', 'SystemUserTestConnectiveApi'
|
||||
'SystemUserPushApi', 'SystemUserTestConnectiveApi',
|
||||
'SystemUserAssetsListView', 'SystemUserPushToAssetApi',
|
||||
'SystemUserTestAssetConnectabilityApi', 'SystemUserCommandFilterRuleListApi',
|
||||
|
||||
]
|
||||
|
||||
|
||||
@@ -35,9 +42,16 @@ class SystemUserViewSet(BulkModelViewSet):
|
||||
"""
|
||||
System user api set, for add,delete,update,list,retrieve resource
|
||||
"""
|
||||
filter_fields = ("name", "username")
|
||||
search_fields = filter_fields
|
||||
queryset = SystemUser.objects.all()
|
||||
serializer_class = serializers.SystemUserSerializer
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
pagination_class = LimitOffsetPagination
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset().all()
|
||||
return queryset
|
||||
|
||||
|
||||
class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
|
||||
@@ -45,7 +59,7 @@ class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
|
||||
Get system user auth info
|
||||
"""
|
||||
queryset = SystemUser.objects.all()
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = serializers.SystemUserAuthSerializer
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
@@ -59,7 +73,7 @@ class SystemUserPushApi(generics.RetrieveAPIView):
|
||||
Push system user to cluster assets api
|
||||
"""
|
||||
queryset = SystemUser.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
system_user = self.get_object()
|
||||
@@ -75,9 +89,62 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView):
|
||||
Push system user to cluster assets api
|
||||
"""
|
||||
queryset = SystemUser.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
system_user = self.get_object()
|
||||
task = test_system_user_connectability_manual.delay(system_user)
|
||||
return Response({"task": task.id})
|
||||
|
||||
|
||||
class SystemUserAssetsListView(generics.ListAPIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.AssetSerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
filter_fields = ("hostname", "ip")
|
||||
search_fields = filter_fields
|
||||
|
||||
def get_object(self):
|
||||
pk = self.kwargs.get('pk')
|
||||
return get_object_or_404(SystemUser, pk=pk)
|
||||
|
||||
def get_queryset(self):
|
||||
system_user = self.get_object()
|
||||
return system_user.assets.all()
|
||||
|
||||
|
||||
class SystemUserPushToAssetApi(generics.RetrieveAPIView):
|
||||
queryset = SystemUser.objects.all()
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
system_user = self.get_object()
|
||||
asset_id = self.kwargs.get('aid')
|
||||
asset = get_object_or_404(Asset, id=asset_id)
|
||||
task = push_system_user_a_asset_manual.delay(system_user, asset)
|
||||
return Response({"task": task.id})
|
||||
|
||||
|
||||
class SystemUserTestAssetConnectabilityApi(generics.RetrieveAPIView):
|
||||
queryset = SystemUser.objects.all()
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
system_user = self.get_object()
|
||||
asset_id = self.kwargs.get('aid')
|
||||
asset = get_object_or_404(Asset, id=asset_id)
|
||||
task = test_system_user_connectability_a_asset.delay(system_user, asset)
|
||||
return Response({"task": task.id})
|
||||
|
||||
|
||||
class SystemUserCommandFilterRuleListApi(generics.ListAPIView):
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
def get_serializer_class(self):
|
||||
from ..serializers import CommandFilterRuleSerializer
|
||||
return CommandFilterRuleSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
pk = self.kwargs.get('pk', None)
|
||||
system_user = get_object_or_404(SystemUser, pk=pk)
|
||||
return system_user.cmd_filter_rules
|
||||
|
||||
@@ -4,3 +4,4 @@ from .asset import *
|
||||
from .label import *
|
||||
from .user import *
|
||||
from .domain import *
|
||||
from .cmd_filter import *
|
||||
|
||||
@@ -3,20 +3,23 @@
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from ..models import Asset, AdminUser
|
||||
from common.utils import get_logger
|
||||
from orgs.mixins import OrgModelForm
|
||||
|
||||
from ..models import Asset, AdminUser
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = ['AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm']
|
||||
|
||||
|
||||
class AssetCreateForm(forms.ModelForm):
|
||||
class AssetCreateForm(OrgModelForm):
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = [
|
||||
'hostname', 'ip', 'public_ip', 'port', 'comment',
|
||||
'nodes', 'is_active', 'admin_user', 'labels', 'platform',
|
||||
'domain',
|
||||
'domain', 'protocol',
|
||||
|
||||
]
|
||||
widgets = {
|
||||
@@ -45,18 +48,18 @@ class AssetCreateForm(forms.ModelForm):
|
||||
'root or other NOPASSWD sudo privilege user existed in asset,'
|
||||
'If asset is windows or other set any one, more see admin user left menu'
|
||||
),
|
||||
'platform': _("* required Must set exact system platform, Windows, Linux ..."),
|
||||
'platform': _("Windows 2016 RDP protocol is different, If is window 2016, set it"),
|
||||
'domain': _("If your have some network not connect with each other, you can set domain")
|
||||
}
|
||||
|
||||
|
||||
class AssetUpdateForm(forms.ModelForm):
|
||||
class AssetUpdateForm(OrgModelForm):
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = [
|
||||
'hostname', 'ip', 'port', 'nodes', 'is_active', 'platform',
|
||||
'public_ip', 'number', 'comment', 'admin_user', 'labels',
|
||||
'domain',
|
||||
'domain', 'protocol',
|
||||
]
|
||||
widgets = {
|
||||
'nodes': forms.SelectMultiple(attrs={
|
||||
@@ -85,12 +88,12 @@ class AssetUpdateForm(forms.ModelForm):
|
||||
'root or other NOPASSWD sudo privilege user existed in asset,'
|
||||
'If asset is windows or other set any one, more see admin user left menu'
|
||||
),
|
||||
'platform': _("* required Must set exact system platform, Windows, Linux ..."),
|
||||
'platform': _("Windows 2016 RDP protocol is different, If is window 2016, set it"),
|
||||
'domain': _("If your have some network not connect with each other, you can set domain")
|
||||
}
|
||||
|
||||
|
||||
class AssetBulkUpdateForm(forms.ModelForm):
|
||||
class AssetBulkUpdateForm(OrgModelForm):
|
||||
assets = forms.ModelMultipleChoiceField(
|
||||
required=True, help_text='* required',
|
||||
label=_('Select assets'), queryset=Asset.objects.all(),
|
||||
@@ -105,7 +108,7 @@ class AssetBulkUpdateForm(forms.ModelForm):
|
||||
label=_('Port'), required=False, min_value=1, max_value=65535,
|
||||
)
|
||||
admin_user = forms.ModelChoiceField(
|
||||
required=False, queryset=AdminUser.objects.all(),
|
||||
required=False, queryset=AdminUser.objects,
|
||||
label=_("Admin user"),
|
||||
widget=forms.Select(
|
||||
attrs={
|
||||
|
||||
27
apps/assets/forms/cmd_filter.py
Normal file
27
apps/assets/forms/cmd_filter.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django import forms
|
||||
|
||||
from orgs.mixins import OrgModelForm
|
||||
from ..models import CommandFilter, CommandFilterRule
|
||||
|
||||
__all__ = ['CommandFilterForm', 'CommandFilterRuleForm']
|
||||
|
||||
|
||||
class CommandFilterForm(OrgModelForm):
|
||||
class Meta:
|
||||
model = CommandFilter
|
||||
fields = ['name', 'comment']
|
||||
|
||||
|
||||
class CommandFilterRuleForm(OrgModelForm):
|
||||
class Meta:
|
||||
model = CommandFilterRule
|
||||
fields = [
|
||||
'filter', 'type', 'content', 'priority', 'action', 'comment'
|
||||
]
|
||||
widgets = {
|
||||
'content': forms.Textarea(attrs={
|
||||
'placeholder': 'eg:\r\nreboot\r\nrm -rf'
|
||||
}),
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from orgs.mixins import OrgModelForm
|
||||
from ..models import Domain, Asset, Gateway
|
||||
from .user import PasswordAndKeyAuthForm
|
||||
|
||||
@@ -34,7 +35,11 @@ class DomainForm(forms.ModelForm):
|
||||
return instance
|
||||
|
||||
|
||||
class GatewayForm(PasswordAndKeyAuthForm):
|
||||
class GatewayForm(PasswordAndKeyAuthForm, OrgModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
password_field = self.fields.get('password')
|
||||
password_field.help_text = _('Password should not contain special characters')
|
||||
|
||||
def save(self, commit=True):
|
||||
# Because we define custom field, so we need rewrite :method: `save`
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from ..models import AdminUser, SystemUser
|
||||
from common.utils import validate_ssh_private_key, ssh_pubkey_gen, get_logger
|
||||
from orgs.mixins import OrgModelForm
|
||||
from ..models import AdminUser, SystemUser
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
@@ -85,7 +86,7 @@ class AdminUserForm(PasswordAndKeyAuthForm):
|
||||
}
|
||||
|
||||
|
||||
class SystemUserForm(PasswordAndKeyAuthForm):
|
||||
class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm):
|
||||
# Admin user assets define, let user select, save it in form not in view
|
||||
auto_generate_key = forms.BooleanField(initial=True, required=False)
|
||||
|
||||
@@ -93,14 +94,24 @@ class SystemUserForm(PasswordAndKeyAuthForm):
|
||||
# Because we define custom field, so we need rewrite :method: `save`
|
||||
system_user = super().save()
|
||||
password = self.cleaned_data.get('password', '') or None
|
||||
login_mode = self.cleaned_data.get('login_mode', '') or None
|
||||
protocol = self.cleaned_data.get('protocol') or None
|
||||
auto_generate_key = self.cleaned_data.get('auto_generate_key', False)
|
||||
private_key, public_key = super().gen_keys()
|
||||
|
||||
if login_mode == SystemUser.MANUAL_LOGIN or \
|
||||
protocol in [SystemUser.RDP_PROTOCOL, SystemUser.TELNET_PROTOCOL]:
|
||||
system_user.auto_push = 0
|
||||
auto_generate_key = False
|
||||
system_user.save()
|
||||
|
||||
if auto_generate_key:
|
||||
logger.info('Auto generate key and set system user auth')
|
||||
system_user.auto_gen_auth()
|
||||
else:
|
||||
system_user.set_auth(password=password, private_key=private_key, public_key=public_key)
|
||||
system_user.set_auth(password=password, private_key=private_key,
|
||||
public_key=public_key)
|
||||
|
||||
return system_user
|
||||
|
||||
def clean(self):
|
||||
@@ -109,20 +120,38 @@ class SystemUserForm(PasswordAndKeyAuthForm):
|
||||
if not self.instance and not auto_generate:
|
||||
super().validate_password_key()
|
||||
|
||||
def is_valid(self):
|
||||
validated = super().is_valid()
|
||||
username = self.cleaned_data.get('username')
|
||||
login_mode = self.cleaned_data.get('login_mode')
|
||||
if login_mode == SystemUser.AUTO_LOGIN and not username:
|
||||
self.add_error(
|
||||
"username", _('* Automatic login mode,'
|
||||
' must fill in the username.')
|
||||
)
|
||||
return False
|
||||
return validated
|
||||
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
fields = [
|
||||
'name', 'username', 'protocol', 'auto_generate_key',
|
||||
'password', 'private_key_file', 'auto_push', 'sudo',
|
||||
'comment', 'shell', 'priority',
|
||||
'comment', 'shell', 'priority', 'login_mode', 'cmd_filters',
|
||||
]
|
||||
widgets = {
|
||||
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
|
||||
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
|
||||
'cmd_filters': forms.SelectMultiple(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Command filter')
|
||||
}),
|
||||
}
|
||||
help_texts = {
|
||||
'name': '* required',
|
||||
'username': '* required',
|
||||
'auto_push': _('Auto push system user to asset'),
|
||||
'priority': _('High level will be using login asset as default, if user was granted more than 2 system user'),
|
||||
}
|
||||
'priority': _('1-100, High level will be using login asset as default, '
|
||||
'if user was granted more than 2 system user'),
|
||||
'login_mode': _('If you choose manual login mode, you do not '
|
||||
'need to fill in the username and password.')
|
||||
}
|
||||
|
||||
@@ -11,6 +11,6 @@
|
||||
"""
|
||||
|
||||
|
||||
from common.mixins import AdminUserRequiredMixin
|
||||
from common.permissions import IsAppUser, IsSuperUser, IsValidUser, IsSuperUserOrAppUser
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser
|
||||
from users.models import User, UserGroup
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from .user import AdminUser, SystemUser
|
||||
from .user import *
|
||||
from .label import Label
|
||||
from .cluster import *
|
||||
from .group import *
|
||||
from .domain import *
|
||||
from .node import *
|
||||
from .asset import *
|
||||
from .cmd_filter import *
|
||||
from .utils import *
|
||||
|
||||
@@ -6,13 +6,16 @@ import uuid
|
||||
import logging
|
||||
import random
|
||||
from functools import reduce
|
||||
from collections import defaultdict
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.cache import cache
|
||||
|
||||
from ..const import ASSET_ADMIN_CONN_CACHE_KEY
|
||||
from .user import AdminUser, SystemUser
|
||||
from orgs.mixins import OrgModelMixin, OrgManager
|
||||
|
||||
__all__ = ['Asset']
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -31,7 +34,8 @@ def default_cluster():
|
||||
def default_node():
|
||||
try:
|
||||
from .node import Node
|
||||
return Node.root()
|
||||
root = Node.root()
|
||||
return root
|
||||
except:
|
||||
return None
|
||||
|
||||
@@ -44,12 +48,7 @@ class AssetQuerySet(models.QuerySet):
|
||||
return self.active()
|
||||
|
||||
|
||||
class AssetManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
return AssetQuerySet(self.model, using=self._db)
|
||||
|
||||
|
||||
class Asset(models.Model):
|
||||
class Asset(OrgModelMixin):
|
||||
# Important
|
||||
PLATFORM_CHOICES = (
|
||||
('Linux', 'Linux'),
|
||||
@@ -57,16 +56,25 @@ class Asset(models.Model):
|
||||
('MacOS', 'MacOS'),
|
||||
('BSD', 'BSD'),
|
||||
('Windows', 'Windows'),
|
||||
('Windows2016', 'Windows(2016)'),
|
||||
('Other', 'Other'),
|
||||
)
|
||||
|
||||
SSH_PROTOCOL = 'ssh'
|
||||
RDP_PROTOCOL = 'rdp'
|
||||
TELNET_PROTOCOL = 'telnet'
|
||||
PROTOCOL_CHOICES = (
|
||||
(SSH_PROTOCOL, 'ssh'),
|
||||
(RDP_PROTOCOL, 'rdp'),
|
||||
(TELNET_PROTOCOL, 'telnet (beta)'),
|
||||
)
|
||||
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'),
|
||||
db_index=True)
|
||||
hostname = models.CharField(max_length=128, unique=True,
|
||||
verbose_name=_('Hostname'))
|
||||
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
|
||||
hostname = models.CharField(max_length=128, verbose_name=_('Hostname'))
|
||||
protocol = models.CharField(max_length=128, default=SSH_PROTOCOL, choices=PROTOCOL_CHOICES, verbose_name=_('Protocol'))
|
||||
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
||||
platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES,
|
||||
default='Linux', verbose_name=_('Platform'))
|
||||
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)
|
||||
@@ -80,11 +88,8 @@ class Asset(models.Model):
|
||||
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'))
|
||||
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,
|
||||
@@ -98,6 +103,7 @@ class Asset(models.Model):
|
||||
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,
|
||||
@@ -125,7 +131,7 @@ class Asset(models.Model):
|
||||
comment = models.TextField(max_length=128, default='', blank=True,
|
||||
verbose_name=_('Comment'))
|
||||
|
||||
objects = AssetManager()
|
||||
objects = OrgManager.from_queryset(AssetQuerySet)()
|
||||
|
||||
def __str__(self):
|
||||
return '{0.hostname}({0.ip})'.format(self)
|
||||
@@ -140,7 +146,7 @@ class Asset(models.Model):
|
||||
return False, warning
|
||||
|
||||
def is_unixlike(self):
|
||||
if self.platform not in ("Windows",):
|
||||
if self.platform not in ("Windows", "Windows2016"):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
@@ -159,11 +165,25 @@ class Asset(models.Model):
|
||||
nodes = list(reduce(lambda x, y: set(x) | set(y), nodes))
|
||||
return nodes
|
||||
|
||||
@classmethod
|
||||
def get_queryset_by_fullname_list(cls, fullname_list):
|
||||
org_fullname_map = defaultdict(list)
|
||||
for fullname in fullname_list:
|
||||
hostname, org = cls.split_fullname(fullname)
|
||||
org_fullname_map[org].append(hostname)
|
||||
filter_arg = Q()
|
||||
for org, hosts in org_fullname_map.items():
|
||||
if org.is_real():
|
||||
filter_arg |= Q(hostname__in=hosts, org_id=org.id)
|
||||
else:
|
||||
filter_arg |= Q(Q(org_id__isnull=True) | Q(org_id=''), hostname__in=hosts)
|
||||
return Asset.objects.filter(filter_arg)
|
||||
|
||||
@property
|
||||
def hardware_info(self):
|
||||
if self.cpu_count:
|
||||
return '{} Core {} {}'.format(
|
||||
self.cpu_count * self.cpu_cores,
|
||||
self.cpu_vcpus or self.cpu_count * self.cpu_cores,
|
||||
self.memory, self.disk_total
|
||||
)
|
||||
else:
|
||||
@@ -199,6 +219,16 @@ class Asset(models.Model):
|
||||
'become': self.admin_user.become_info,
|
||||
}
|
||||
|
||||
def as_node(self):
|
||||
from .node import Node
|
||||
fake_node = Node()
|
||||
fake_node.id = self.id
|
||||
fake_node.key = self.id
|
||||
fake_node.value = self.hostname
|
||||
fake_node.asset = self
|
||||
fake_node.is_node = False
|
||||
return fake_node
|
||||
|
||||
def _to_secret_json(self):
|
||||
"""
|
||||
Ansible use it create inventory, First using asset user,
|
||||
@@ -219,7 +249,7 @@ class Asset(models.Model):
|
||||
return data
|
||||
|
||||
class Meta:
|
||||
unique_together = ('ip', 'port')
|
||||
unique_together = [('org_id', 'hostname')]
|
||||
verbose_name = _("Asset")
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -11,15 +11,16 @@ from django.conf import settings
|
||||
|
||||
from common.utils import get_signer, ssh_key_string_to_obj, ssh_key_gen
|
||||
from common.validators import alphanumeric
|
||||
from orgs.mixins import OrgModelMixin
|
||||
from .utils import private_key_validator
|
||||
|
||||
signer = get_signer()
|
||||
|
||||
|
||||
class AssetUser(models.Model):
|
||||
class AssetUser(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
|
||||
username = models.CharField(max_length=32, verbose_name=_('Username'), validators=[alphanumeric])
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||
username = models.CharField(max_length=32, blank=True, verbose_name=_('Username'), validators=[alphanumeric])
|
||||
_password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
|
||||
_private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ])
|
||||
_public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
|
||||
|
||||
@@ -52,7 +52,8 @@ class Cluster(models.Model):
|
||||
contact=forgery_py.name.full_name(),
|
||||
phone=forgery_py.address.phone(),
|
||||
address=forgery_py.address.city() + forgery_py.address.street_address(),
|
||||
operator=choice(['北京联通', '北京电信', 'BGP全网通']),
|
||||
# operator=choice(['北京联通', '北京电信', 'BGP全网通']),
|
||||
operator=choice([_('Beijing unicom'), _('Beijing telecom'), _('BGP full netcom')]),
|
||||
comment=forgery_py.lorem_ipsum.sentence(),
|
||||
created_by='Fake')
|
||||
try:
|
||||
|
||||
60
apps/assets/models/cmd_filter.py
Normal file
60
apps/assets/models/cmd_filter.py
Normal file
@@ -0,0 +1,60 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import uuid
|
||||
|
||||
from django.db import models
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins import OrgModelMixin
|
||||
|
||||
|
||||
__all__ = [
|
||||
'CommandFilter', 'CommandFilterRule'
|
||||
]
|
||||
|
||||
|
||||
class CommandFilter(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=64, verbose_name=_("Name"))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
|
||||
comment = models.TextField(blank=True, default='', verbose_name=_("Comment"))
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
date_updated = models.DateTimeField(auto_now=True)
|
||||
created_by = models.CharField(max_length=128, blank=True, default='', verbose_name=_('Created by'))
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class CommandFilterRule(OrgModelMixin):
|
||||
TYPE_REGEX = 'regex'
|
||||
TYPE_COMMAND = 'command'
|
||||
TYPE_CHOICES = (
|
||||
(TYPE_REGEX, _('Regex')),
|
||||
(TYPE_COMMAND, _('Command')),
|
||||
)
|
||||
|
||||
ACTION_DENY, ACTION_ALLOW = range(2)
|
||||
ACTION_CHOICES = (
|
||||
(ACTION_DENY, _('Deny')),
|
||||
(ACTION_ALLOW, _('Allow')),
|
||||
)
|
||||
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
filter = models.ForeignKey('CommandFilter', on_delete=models.CASCADE, verbose_name=_("Filter"), related_name='rules')
|
||||
type = models.CharField(max_length=16, default=TYPE_COMMAND, choices=TYPE_CHOICES, verbose_name=_("Type"))
|
||||
priority = models.IntegerField(default=50, verbose_name=_("Priority"), help_text=_("1-100, the higher will be match first"),
|
||||
validators=[MinValueValidator(1), MaxValueValidator(100)])
|
||||
content = models.TextField(max_length=1024, verbose_name=_("Content"), help_text=_("One line one command"))
|
||||
action = models.IntegerField(default=ACTION_DENY, choices=ACTION_CHOICES, verbose_name=_("Action"))
|
||||
comment = models.CharField(max_length=64, blank=True, default='', verbose_name=_("Comment"))
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
date_updated = models.DateTimeField(auto_now=True)
|
||||
created_by = models.CharField(max_length=128, blank=True, default='', verbose_name=_('Created by'))
|
||||
|
||||
class Meta:
|
||||
ordering = ('-priority', 'action')
|
||||
|
||||
def __str__(self):
|
||||
return '{} % {}'.format(self.type, self.content)
|
||||
@@ -7,18 +7,22 @@ import random
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins import OrgModelMixin
|
||||
from .base import AssetUser
|
||||
|
||||
__all__ = ['Domain', 'Gateway']
|
||||
|
||||
|
||||
class Domain(models.Model):
|
||||
class Domain(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
date_created = models.DateTimeField(auto_now_add=True, null=True,
|
||||
verbose_name=_('Date created'))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Domain")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@@ -43,10 +47,13 @@ class Gateway(AssetUser):
|
||||
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
|
||||
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
||||
protocol = models.CharField(choices=PROTOCOL_CHOICES, max_length=16, default=SSH_PROTOCOL, verbose_name=_("Protocol"))
|
||||
domain = models.ForeignKey(Domain, verbose_name=_("Domain"))
|
||||
domain = models.ForeignKey(Domain, on_delete=models.CASCADE, verbose_name=_("Domain"))
|
||||
comment = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Comment"))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
unique_together = [('name', 'org_id')]
|
||||
verbose_name = _("Gateway")
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
import uuid
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from orgs.mixins import OrgModelMixin
|
||||
|
||||
|
||||
class Label(models.Model):
|
||||
class Label(OrgModelMixin):
|
||||
SYSTEM_CATEGORY = "S"
|
||||
USER_CATEGORY = "U"
|
||||
CATEGORY_CHOICES = (
|
||||
@@ -34,4 +35,4 @@ class Label(models.Model):
|
||||
|
||||
class Meta:
|
||||
db_table = "assets_label"
|
||||
unique_together = ('name', 'value')
|
||||
unique_together = [('name', 'value', 'org_id')]
|
||||
|
||||
@@ -5,12 +5,16 @@ import uuid
|
||||
from django.db import models, transaction
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from common.utils import with_cache
|
||||
from django.core.cache import cache
|
||||
|
||||
from orgs.mixins import OrgModelMixin
|
||||
from orgs.utils import set_current_org, get_current_org
|
||||
from orgs.models import Organization
|
||||
|
||||
__all__ = ['Node']
|
||||
|
||||
|
||||
class Node(models.Model):
|
||||
class Node(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
key = models.CharField(unique=True, max_length=64, verbose_name=_("Key")) # '1:1:1:1'
|
||||
value = models.CharField(max_length=128, verbose_name=_("Value"))
|
||||
@@ -18,11 +22,17 @@ class Node(models.Model):
|
||||
date_create = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
is_node = True
|
||||
_full_value_cache_key_prefix = '_NODE_VALUE_{}'
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Node")
|
||||
|
||||
def __str__(self):
|
||||
return self.full_value
|
||||
|
||||
def __eq__(self, other):
|
||||
if not other:
|
||||
return False
|
||||
return self.key == other.key
|
||||
|
||||
def __gt__(self, other):
|
||||
@@ -30,12 +40,10 @@ class Node(models.Model):
|
||||
return True
|
||||
self_key = [int(k) for k in self.key.split(':')]
|
||||
other_key = [int(k) for k in other.key.split(':')]
|
||||
if len(self_key) < len(other_key):
|
||||
return True
|
||||
elif len(self_key) > len(other_key):
|
||||
return False
|
||||
else:
|
||||
return self_key[-1] < other_key[-1]
|
||||
return self_key.__lt__(other_key)
|
||||
|
||||
def __lt__(self, other):
|
||||
return not self.__gt__(other)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@@ -43,10 +51,29 @@ class Node(models.Model):
|
||||
|
||||
@property
|
||||
def full_value(self):
|
||||
ancestor = [a.value for a in self.get_ancestor(with_self=True)]
|
||||
key = self._full_value_cache_key_prefix.format(self.key)
|
||||
cached = cache.get(key)
|
||||
if cached:
|
||||
return cached
|
||||
value = self.get_full_value()
|
||||
self.cache_full_value(value)
|
||||
return value
|
||||
|
||||
def get_full_value(self):
|
||||
# ancestor = [a.value for a in self.get_ancestor(with_self=True)]
|
||||
if self.is_root():
|
||||
return self.value
|
||||
return ' / '.join(ancestor)
|
||||
parent_full_value = self.parent.full_value
|
||||
value = parent_full_value + ' / ' + self.value
|
||||
return value
|
||||
|
||||
def cache_full_value(self, value):
|
||||
key = self._full_value_cache_key_prefix.format(self.key)
|
||||
cache.set(key, value, 3600)
|
||||
|
||||
def expire_full_value(self):
|
||||
key = self._full_value_cache_key_prefix.format(self.key)
|
||||
cache.delete_pattern(key+'*')
|
||||
|
||||
@property
|
||||
def level(self):
|
||||
@@ -65,7 +92,7 @@ class Node(models.Model):
|
||||
return child
|
||||
|
||||
def get_children(self, with_self=False):
|
||||
pattern = r'^{0}$|^{}:[0-9]+$' if with_self else r'^{}:[0-9]+$'
|
||||
pattern = r'^{0}$|^{0}:[0-9]+$' if with_self else r'^{0}:[0-9]+$'
|
||||
return self.__class__.objects.filter(
|
||||
key__regex=pattern.format(self.key)
|
||||
)
|
||||
@@ -93,62 +120,72 @@ class Node(models.Model):
|
||||
|
||||
def get_assets(self):
|
||||
from .asset import Asset
|
||||
if self.is_root():
|
||||
assets = Asset.objects.filter(
|
||||
Q(nodes__id=self.id) | Q(nodes__isnull=True)
|
||||
)
|
||||
if self.is_default_node():
|
||||
assets = Asset.objects.filter(Q(nodes__id=self.id) | Q(nodes__isnull=True))
|
||||
else:
|
||||
assets = self.assets.all()
|
||||
return assets
|
||||
assets = Asset.objects.filter(nodes__id=self.id)
|
||||
return assets.distinct()
|
||||
|
||||
def get_valid_assets(self):
|
||||
return self.get_assets().valid()
|
||||
|
||||
def get_all_assets(self):
|
||||
from .asset import Asset
|
||||
if self.is_root():
|
||||
assets = Asset.objects.all()
|
||||
pattern = r'^{0}$|^{0}:'.format(self.key)
|
||||
args = []
|
||||
kwargs = {}
|
||||
if self.is_default_node():
|
||||
args.append(Q(nodes__key__regex=pattern) | Q(nodes=None))
|
||||
else:
|
||||
pattern = r'^{0}$|^{0}:'.format(self.key)
|
||||
assets = Asset.objects.filter(nodes__key__regex=pattern)
|
||||
kwargs['nodes__key__regex'] = pattern
|
||||
assets = Asset.objects.filter(*args, **kwargs).distinct()
|
||||
return assets
|
||||
|
||||
def get_all_valid_assets(self):
|
||||
return self.get_all_assets().valid()
|
||||
|
||||
def is_default_node(self):
|
||||
return self.is_root() and self.key == '0'
|
||||
|
||||
def is_root(self):
|
||||
return self.key == '0'
|
||||
if self.key.isdigit():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@property
|
||||
def parent_key(self):
|
||||
parent_key = ":".join(self.key.split(":")[:-1])
|
||||
return parent_key
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
if self.key == "0" or not self.key.startswith("0"):
|
||||
return self.__class__.root()
|
||||
parent_key = ":".join(self.key.split(":")[:-1])
|
||||
if self.is_root():
|
||||
return self
|
||||
try:
|
||||
parent = self.__class__.objects.get(key=parent_key)
|
||||
parent = self.__class__.objects.get(key=self.parent_key)
|
||||
return parent
|
||||
except Node.DoesNotExist:
|
||||
return self.__class__.root()
|
||||
|
||||
@parent.setter
|
||||
def parent(self, parent):
|
||||
if self.is_node:
|
||||
children = self.get_all_children()
|
||||
old_key = self.key
|
||||
with transaction.atomic():
|
||||
self.key = parent.get_next_child_key()
|
||||
for child in children:
|
||||
child.key = child.key.replace(old_key, self.key, 1)
|
||||
child.save()
|
||||
self.save()
|
||||
else:
|
||||
self.key = parent.key+':fake'
|
||||
if not self.is_node:
|
||||
self.key = parent.key + ':fake'
|
||||
return
|
||||
children = self.get_all_children()
|
||||
old_key = self.key
|
||||
with transaction.atomic():
|
||||
self.key = parent.get_next_child_key()
|
||||
for child in children:
|
||||
child.key = child.key.replace(old_key, self.key, 1)
|
||||
child.save()
|
||||
self.save()
|
||||
|
||||
def get_ancestor(self, with_self=False):
|
||||
if self.is_root():
|
||||
ancestor = self.__class__.objects.filter(key='0')
|
||||
return ancestor
|
||||
|
||||
root = self.__class__.root()
|
||||
return [root]
|
||||
_key = self.key.split(':')
|
||||
if not with_self:
|
||||
_key.pop()
|
||||
@@ -162,10 +199,46 @@ class Node(models.Model):
|
||||
return ancestor
|
||||
|
||||
@classmethod
|
||||
def root(cls):
|
||||
obj, created = cls.objects.get_or_create(
|
||||
key='0', defaults={"key": '0', 'value': "ROOT"}
|
||||
)
|
||||
print(obj)
|
||||
return obj
|
||||
def create_root_node(cls):
|
||||
# 如果使用current_org 在set_current_org时会死循环
|
||||
_current_org = get_current_org()
|
||||
with transaction.atomic():
|
||||
if _current_org.is_root():
|
||||
key = '0'
|
||||
elif _current_org.is_default():
|
||||
key = '1'
|
||||
else:
|
||||
set_current_org(Organization.root())
|
||||
org_nodes_roots = cls.objects.filter(key__regex=r'^[0-9]+$')
|
||||
org_nodes_roots_keys = org_nodes_roots.values_list('key', flat=True) or ['1']
|
||||
key = max([int(k) for k in org_nodes_roots_keys])
|
||||
key = str(key + 1) if key != 0 else '2'
|
||||
set_current_org(_current_org)
|
||||
root = cls.objects.create(key=key, value=_current_org.name)
|
||||
return root
|
||||
|
||||
@classmethod
|
||||
def root(cls):
|
||||
root = cls.objects.filter(key__regex=r'^[0-9]+$')
|
||||
if root:
|
||||
return root[0]
|
||||
else:
|
||||
return cls.create_root_node()
|
||||
|
||||
@classmethod
|
||||
def default_node(cls):
|
||||
defaults = {'value': 'Default'}
|
||||
return cls.objects.get_or_create(defaults=defaults, key='1')
|
||||
|
||||
@classmethod
|
||||
def get_tree_name_ref(cls):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def generate_fake(cls, count=100):
|
||||
import random
|
||||
for i in range(count):
|
||||
node = random.choice(cls.objects.all())
|
||||
node.create_child('Node {}'.format(i))
|
||||
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
#
|
||||
|
||||
import logging
|
||||
import uuid
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
|
||||
from common.utils import get_signer
|
||||
from ..const import SYSTEM_USER_CONN_CACHE_KEY
|
||||
@@ -69,6 +69,7 @@ class AdminUser(AssetUser):
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
unique_together = [('name', 'org_id')]
|
||||
verbose_name = _("Admin user")
|
||||
|
||||
@classmethod
|
||||
@@ -95,18 +96,32 @@ class AdminUser(AssetUser):
|
||||
class SystemUser(AssetUser):
|
||||
SSH_PROTOCOL = 'ssh'
|
||||
RDP_PROTOCOL = 'rdp'
|
||||
TELNET_PROTOCOL = 'telnet'
|
||||
PROTOCOL_CHOICES = (
|
||||
(SSH_PROTOCOL, 'ssh'),
|
||||
(RDP_PROTOCOL, 'rdp'),
|
||||
(TELNET_PROTOCOL, 'telnet (beta)'),
|
||||
)
|
||||
|
||||
AUTO_LOGIN = 'auto'
|
||||
MANUAL_LOGIN = 'manual'
|
||||
LOGIN_MODE_CHOICES = (
|
||||
(AUTO_LOGIN, _('Automatic login')),
|
||||
(MANUAL_LOGIN, _('Manually login'))
|
||||
)
|
||||
|
||||
nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes"))
|
||||
assets = models.ManyToManyField('assets.Asset', blank=True, verbose_name=_("Assets"))
|
||||
priority = models.IntegerField(default=10, verbose_name=_("Priority"))
|
||||
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'))
|
||||
cmd_filters = models.ManyToManyField('CommandFilter', related_name='system_users', verbose_name=_("Command filter"), blank=True)
|
||||
|
||||
cache_key = "__SYSTEM_USER_CACHED_{}"
|
||||
|
||||
def __str__(self):
|
||||
return '{0.name}({0.username})'.format(self)
|
||||
@@ -144,8 +159,35 @@ class SystemUser(AssetUser):
|
||||
else:
|
||||
return False
|
||||
|
||||
def set_cache(self):
|
||||
cache.set(self.cache_key.format(self.id), self, 3600)
|
||||
|
||||
def expire_cache(self):
|
||||
cache.delete(self.cache_key.format(self.id))
|
||||
|
||||
@property
|
||||
def cmd_filter_rules(self):
|
||||
from .cmd_filter import CommandFilterRule
|
||||
rules = CommandFilterRule.objects.filter(
|
||||
filter__in=self.cmd_filters.all()
|
||||
).distinct()
|
||||
return rules
|
||||
|
||||
@classmethod
|
||||
def get_system_user_by_id_or_cached(cls, sid):
|
||||
cached = cache.get(cls.cache_key.format(sid))
|
||||
if cached:
|
||||
return cached
|
||||
try:
|
||||
system_user = cls.objects.get(id=sid)
|
||||
system_user.set_cache()
|
||||
return system_user
|
||||
except cls.DoesNotExist:
|
||||
return None
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
unique_together = [('name', 'org_id')]
|
||||
verbose_name = _("System user")
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -7,3 +7,4 @@ from .label import *
|
||||
from .system_user import *
|
||||
from .node import *
|
||||
from .domain import *
|
||||
from .cmd_filter import *
|
||||
|
||||
@@ -58,7 +58,7 @@ class ReplaceNodeAdminUserSerializer(serializers.ModelSerializer):
|
||||
管理用户更新关联到的集群
|
||||
"""
|
||||
nodes = serializers.PrimaryKeyRelatedField(
|
||||
many=True, queryset=Node.objects.all()
|
||||
many=True, queryset = Node.objects.all()
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
||||
@@ -4,7 +4,7 @@ from rest_framework import serializers
|
||||
from rest_framework_bulk.serializers import BulkListSerializer
|
||||
|
||||
from common.mixins import BulkSerializerMixin
|
||||
from ..models import Asset, Node
|
||||
from ..models import Asset
|
||||
from .system_user import AssetSystemUserSerializer
|
||||
|
||||
__all__ = [
|
||||
@@ -20,12 +20,12 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
model = Asset
|
||||
list_serializer_class = BulkListSerializer
|
||||
fields = '__all__'
|
||||
validators = [] # If not set to [], partial bulk update will be error
|
||||
validators = []
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super().get_field_names(declared_fields, info)
|
||||
fields.extend([
|
||||
'hardware_info', 'is_connective',
|
||||
'hardware_info', 'is_connective', 'org_name'
|
||||
])
|
||||
return fields
|
||||
|
||||
@@ -43,7 +43,7 @@ class AssetGrantedSerializer(serializers.ModelSerializer):
|
||||
fields = (
|
||||
"id", "hostname", "ip", "port", "system_users_granted",
|
||||
"is_active", "system_users_join", "os", 'domain',
|
||||
"platform", "comment"
|
||||
"platform", "comment", "protocol", "org_id", "org_name",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@@ -61,6 +61,6 @@ class MyAssetGrantedSerializer(AssetGrantedSerializer):
|
||||
model = Asset
|
||||
fields = (
|
||||
"id", "hostname", "system_users_granted",
|
||||
"is_active", "system_users_join",
|
||||
"os", "platform", "comment",
|
||||
"is_active", "system_users_join", "org_name",
|
||||
"os", "platform", "comment", "org_id", "protocol"
|
||||
)
|
||||
|
||||
23
apps/assets/serializers/cmd_filter.py
Normal file
23
apps/assets/serializers/cmd_filter.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.fields import ChoiceDisplayField
|
||||
from ..models import CommandFilter, CommandFilterRule, SystemUser
|
||||
|
||||
|
||||
class CommandFilterSerializer(serializers.ModelSerializer):
|
||||
rules = serializers.PrimaryKeyRelatedField(queryset=CommandFilterRule.objects.all(), many=True)
|
||||
system_users = serializers.PrimaryKeyRelatedField(queryset=SystemUser.objects.all(), many=True)
|
||||
|
||||
class Meta:
|
||||
model = CommandFilter
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class CommandFilterRuleSerializer(serializers.ModelSerializer):
|
||||
serializer_choice_field = ChoiceDisplayField
|
||||
|
||||
class Meta:
|
||||
model = CommandFilterRule
|
||||
fields = '__all__'
|
||||
@@ -26,7 +26,7 @@ class NodeGrantedSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
model = Node
|
||||
fields = [
|
||||
'id', 'key', 'name', 'value', 'parent',
|
||||
'assets_granted', 'assets_amount',
|
||||
'assets_granted', 'assets_amount', 'org_id',
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
@@ -43,12 +43,16 @@ class NodeGrantedSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
|
||||
|
||||
class NodeSerializer(serializers.ModelSerializer):
|
||||
parent = serializers.SerializerMethodField()
|
||||
assets_amount = serializers.SerializerMethodField()
|
||||
tree_id = serializers.SerializerMethodField()
|
||||
tree_parent = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Node
|
||||
fields = ['id', 'key', 'value', 'parent', 'assets_amount', 'is_node']
|
||||
fields = [
|
||||
'id', 'key', 'value', 'assets_amount',
|
||||
'is_node', 'org_id', 'tree_id', 'tree_parent',
|
||||
]
|
||||
list_serializer_class = BulkListSerializer
|
||||
|
||||
def validate(self, data):
|
||||
@@ -63,12 +67,16 @@ class NodeSerializer(serializers.ModelSerializer):
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def get_parent(obj):
|
||||
return obj.parent.id if obj.is_node else obj.parent_id
|
||||
def get_assets_amount(obj):
|
||||
return obj.get_all_assets().count()
|
||||
|
||||
@staticmethod
|
||||
def get_assets_amount(obj):
|
||||
return obj.get_all_assets().count() if obj.is_node else 0
|
||||
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()
|
||||
@@ -78,7 +86,7 @@ class NodeSerializer(serializers.ModelSerializer):
|
||||
|
||||
|
||||
class NodeAssetsSerializer(serializers.ModelSerializer):
|
||||
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
|
||||
assets = serializers.PrimaryKeyRelatedField(many=True, queryset = Asset.objects.all())
|
||||
|
||||
class Meta:
|
||||
model = Node
|
||||
|
||||
@@ -18,6 +18,13 @@ class SystemUserSerializer(serializers.ModelSerializer):
|
||||
model = SystemUser
|
||||
exclude = ('_password', '_private_key', '_public_key')
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info)
|
||||
fields.extend([
|
||||
'get_login_mode_display',
|
||||
])
|
||||
return fields
|
||||
|
||||
@staticmethod
|
||||
def get_unreachable_assets(obj):
|
||||
return obj.unreachable_assets
|
||||
@@ -46,7 +53,7 @@ class SystemUserAuthSerializer(AuthSerializer):
|
||||
model = SystemUser
|
||||
fields = [
|
||||
"id", "name", "username", "protocol",
|
||||
"password", "private_key",
|
||||
"login_mode", "password", "private_key",
|
||||
]
|
||||
|
||||
|
||||
@@ -56,7 +63,10 @@ class AssetSystemUserSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
fields = ('id', 'name', 'username', 'priority', 'protocol', 'comment',)
|
||||
fields = (
|
||||
'id', 'name', 'username', 'priority',
|
||||
'protocol', 'comment', 'login_mode'
|
||||
)
|
||||
|
||||
|
||||
class SystemUserSimpleSerializer(serializers.ModelSerializer):
|
||||
|
||||
@@ -86,3 +86,9 @@ def on_node_assets_changed(sender, instance=None, **kwargs):
|
||||
system_users = SystemUser.objects.filter(nodes=instance)
|
||||
for system_user in system_users:
|
||||
system_user.assets.add(*tuple(assets))
|
||||
|
||||
|
||||
@receiver(post_save, sender=Node)
|
||||
def on_node_update_or_created(sender, instance=None, created=False, **kwargs):
|
||||
if instance and not created:
|
||||
instance.expire_full_value()
|
||||
|
||||
@@ -44,7 +44,7 @@ def set_assets_hardware_info(result, **kwargs):
|
||||
logger.error("Get asset info failed: {}".format(hostname))
|
||||
continue
|
||||
|
||||
asset = get_object_or_none(Asset, hostname=hostname)
|
||||
asset = Asset.objects.get_object_by_fullname(hostname)
|
||||
if not asset:
|
||||
continue
|
||||
|
||||
@@ -60,6 +60,7 @@ def set_assets_hardware_info(result, **kwargs):
|
||||
___cpu_model = ___cpu_model[:64]
|
||||
___cpu_count = info.get('ansible_processor_count', 0)
|
||||
___cpu_cores = info.get('ansible_processor_cores', None) or len(info.get('ansible_processor', []))
|
||||
___cpu_vcpus = info.get('ansible_processor_vcpus', 0)
|
||||
___memory = '%s %s' % capacity_convert('{} MB'.format(info.get('ansible_memtotal_mb')))
|
||||
disk_info = {}
|
||||
for dev, dev_info in info.get('ansible_devices', {}).items():
|
||||
@@ -92,10 +93,10 @@ def update_assets_hardware_info_util(assets, task_name=None):
|
||||
"""
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
if task_name is None:
|
||||
# task_name = _("Update some assets hardware info")
|
||||
task_name = _("更新资产硬件信息")
|
||||
task_name = _("Update some assets hardware info")
|
||||
# task_name = _("更新资产硬件信息")
|
||||
tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
|
||||
hostname_list = [asset.hostname for asset in assets if asset.is_active and asset.is_unixlike()]
|
||||
hostname_list = [asset.fullname for asset in assets if asset.is_active and asset.is_unixlike()]
|
||||
if not hostname_list:
|
||||
logger.info("Not hosts get, may be asset is not active or not unixlike platform")
|
||||
return {}
|
||||
@@ -112,8 +113,8 @@ def update_assets_hardware_info_util(assets, task_name=None):
|
||||
|
||||
@shared_task
|
||||
def update_asset_hardware_info_manual(asset):
|
||||
# task_name = _("Update asset hardware info")
|
||||
task_name = _("更新资产硬件信息")
|
||||
task_name = _("Update asset hardware info")
|
||||
# task_name = _("更新资产硬件信息")
|
||||
return update_assets_hardware_info_util([asset], task_name=task_name)
|
||||
|
||||
|
||||
@@ -131,10 +132,10 @@ def update_assets_hardware_info_period():
|
||||
return
|
||||
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
# task_name = _("Update assets hardware info period")
|
||||
task_name = _("定期更新资产硬件信息")
|
||||
task_name = _("Update assets hardware info period")
|
||||
# task_name = _("定期更新资产硬件信息")
|
||||
hostname_list = [
|
||||
asset.hostname for asset in Asset.objects.all()
|
||||
asset.fullname for asset in Asset.objects.all()
|
||||
if asset.is_active and asset.is_unixlike()
|
||||
]
|
||||
tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
|
||||
@@ -181,7 +182,7 @@ def test_admin_user_connectability_util(admin_user, task_name):
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
|
||||
assets = admin_user.get_related_assets()
|
||||
hosts = [asset.hostname for asset in assets
|
||||
hosts = [asset.fullname for asset in assets
|
||||
if asset.is_active and asset.is_unixlike()]
|
||||
if not hosts:
|
||||
return
|
||||
@@ -209,15 +210,15 @@ def test_admin_user_connectability_period():
|
||||
|
||||
admin_users = AdminUser.objects.all()
|
||||
for admin_user in admin_users:
|
||||
# task_name = _("Test admin user connectability period: {}".format(admin_user.name))
|
||||
task_name = _("定期测试管理账号可连接性: {}".format(admin_user.name))
|
||||
task_name = _("Test admin user connectability period: {}".format(admin_user.name))
|
||||
# task_name = _("定期测试管理账号可连接性: {}".format(admin_user.name))
|
||||
test_admin_user_connectability_util(admin_user, task_name)
|
||||
|
||||
|
||||
@shared_task
|
||||
def test_admin_user_connectability_manual(admin_user):
|
||||
# task_name = _("Test admin user connectability: {}").format(admin_user.name)
|
||||
task_name = _("测试管理行号可连接性: {}").format(admin_user.name)
|
||||
task_name = _("Test admin user connectability: {}").format(admin_user.name)
|
||||
# task_name = _("测试管理行号可连接性: {}").format(admin_user.name)
|
||||
return test_admin_user_connectability_util(admin_user, task_name)
|
||||
|
||||
|
||||
@@ -226,9 +227,9 @@ def test_asset_connectability_util(assets, task_name=None):
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
|
||||
if task_name is None:
|
||||
# task_name = _("Test assets connectability")
|
||||
task_name = _("测试资产可连接性")
|
||||
hosts = [asset.hostname for asset in assets if asset.is_active and asset.is_unixlike()]
|
||||
task_name = _("Test assets connectability")
|
||||
# task_name = _("测试资产可连接性")
|
||||
hosts = [asset.fullname for asset in assets if asset.is_active and asset.is_unixlike()]
|
||||
if not hosts:
|
||||
logger.info("No hosts, passed")
|
||||
return {}
|
||||
@@ -271,16 +272,17 @@ def set_system_user_connectablity_info(result, **kwargs):
|
||||
|
||||
|
||||
@shared_task
|
||||
def test_system_user_connectability_util(system_user, task_name):
|
||||
def test_system_user_connectability_util(system_user, assets, task_name):
|
||||
"""
|
||||
Test system cant connect his assets or not.
|
||||
:param system_user:
|
||||
:param assets:
|
||||
:param task_name:
|
||||
:return:
|
||||
"""
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
assets = system_user.get_assets()
|
||||
hosts = [asset.hostname for asset in assets if asset.is_active and asset.is_unixlike()]
|
||||
# assets = system_user.get_assets()
|
||||
hosts = [asset.fullname for asset in assets if asset.is_active and asset.is_unixlike()]
|
||||
tasks = const.TEST_SYSTEM_USER_CONN_TASKS
|
||||
if not hosts:
|
||||
logger.info("No hosts, passed")
|
||||
@@ -298,7 +300,16 @@ def test_system_user_connectability_util(system_user, task_name):
|
||||
@shared_task
|
||||
def test_system_user_connectability_manual(system_user):
|
||||
task_name = _("Test system user connectability: {}").format(system_user)
|
||||
return test_system_user_connectability_util(system_user, task_name)
|
||||
assets = system_user.get_assets()
|
||||
return test_system_user_connectability_util(system_user, assets, task_name)
|
||||
|
||||
|
||||
@shared_task
|
||||
def test_system_user_connectability_a_asset(system_user, asset):
|
||||
task_name = _("Test system user connectability: {} => {}").format(
|
||||
system_user, asset
|
||||
)
|
||||
return test_system_user_connectability_util(system_user, [asset], task_name)
|
||||
|
||||
|
||||
@shared_task
|
||||
@@ -312,8 +323,8 @@ def test_system_user_connectability_period():
|
||||
|
||||
system_users = SystemUser.objects.all()
|
||||
for system_user in system_users:
|
||||
# task_name = _("Test system user connectability period: {}".format(system_user))
|
||||
task_name = _("定期测试系统用户可连接性: {}".format(system_user))
|
||||
task_name = _("Test system user connectability period: {}".format(system_user))
|
||||
# task_name = _("定期测试系统用户可连接性: {}".format(system_user))
|
||||
test_system_user_connectability_util(system_user, task_name)
|
||||
|
||||
|
||||
@@ -378,7 +389,7 @@ def push_system_user_util(system_users, assets, task_name):
|
||||
logger.info("Not tasks, passed")
|
||||
return {}
|
||||
|
||||
hosts = [asset.hostname for asset in assets if asset.is_active and asset.is_unixlike()]
|
||||
hosts = [asset.fullname for asset in assets if asset.is_active and asset.is_unixlike()]
|
||||
if not hosts:
|
||||
logger.info("Not hosts, passed")
|
||||
return {}
|
||||
@@ -392,13 +403,23 @@ def push_system_user_util(system_users, assets, task_name):
|
||||
@shared_task
|
||||
def push_system_user_to_assets_manual(system_user):
|
||||
assets = system_user.get_assets()
|
||||
task_name = "推送系统用户到入资产: {}".format(system_user.name)
|
||||
# task_name = "推送系统用户到入资产: {}".format(system_user.name)
|
||||
task_name = _("Push system users to assets: {}").format(system_user.name)
|
||||
return push_system_user_util([system_user], assets, task_name=task_name)
|
||||
|
||||
|
||||
@shared_task
|
||||
def push_system_user_a_asset_manual(system_user, asset):
|
||||
task_name = _("Push system users to asset: {} => {}").format(
|
||||
system_user.name, asset.fullname
|
||||
)
|
||||
return push_system_user_util([system_user], [asset], task_name=task_name)
|
||||
|
||||
|
||||
@shared_task
|
||||
def push_system_user_to_assets(system_user, assets):
|
||||
task_name = _("推送系统用户到入资产: {}").format(system_user.name)
|
||||
# task_name = _("推送系统用户到入资产: {}").format(system_user.name)
|
||||
task_name = _("Push system users to assets: {}").format(system_user.name)
|
||||
return push_system_user_util.delay([system_user], assets, task_name)
|
||||
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ function initTable2() {
|
||||
|
||||
function onSelected2(event, treeNode) {
|
||||
var url = asset_table2.ajax.url();
|
||||
url = setUrlParam(url, "node_id", treeNode.id);
|
||||
url = setUrlParam(url, "node_id", treeNode.node_id);
|
||||
setCookie('node_selected', treeNode.id);
|
||||
asset_table2.ajax.url(url);
|
||||
asset_table2.ajax.reload();
|
||||
@@ -97,17 +97,20 @@ function initTree2() {
|
||||
var zNodes = [];
|
||||
$.get("{% url 'api-assets:node-list' %}", function(data, status){
|
||||
$.each(data, function (index, value) {
|
||||
value["pId"] = value["parent"];
|
||||
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'] + ')';
|
||||
value['value'] = value['value'];
|
||||
});
|
||||
zNodes = data;
|
||||
$.fn.zTree.init($("#assetTree2"), setting, zNodes);
|
||||
zTree2 = $.fn.zTree.getZTreeObj("assetTree2");
|
||||
var root = zTree2.getNodes()[0];
|
||||
zTree2.expandNode(root);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -36,12 +36,13 @@
|
||||
{% endif %}
|
||||
<h3>{% trans 'Basic' %}</h3>
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
{% bootstrap_field form.login_mode layout="horizontal" %}
|
||||
{% bootstrap_field form.username layout="horizontal" %}
|
||||
{% bootstrap_field form.priority layout="horizontal" %}
|
||||
{% bootstrap_field form.protocol layout="horizontal" %}
|
||||
|
||||
<h3 id="auth_title_id">{% trans 'Auth' %}</h3>
|
||||
{% block auth %}
|
||||
<h3>{% trans 'Auth' %}</h3>
|
||||
<div class="auto-generate">
|
||||
<div class="form-group">
|
||||
<label for="{{ form.auto_generate_key.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto generate key' %}</label>
|
||||
@@ -61,6 +62,10 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
<div id="command-filter-block">
|
||||
<h3>{% trans 'Command filter' %}</h3>
|
||||
{% bootstrap_field form.cmd_filters layout="horizontal" %}
|
||||
</div>
|
||||
<h3>{% trans 'Other' %}</h3>
|
||||
{% bootstrap_field form.sudo layout="horizontal" %}
|
||||
{% bootstrap_field form.shell layout="horizontal" %}
|
||||
@@ -80,23 +85,43 @@
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
var auto_generate_key = '#'+'{{ form.auto_generate_key.id_for_label }}';
|
||||
var protocol_id = '#' + '{{ form.protocol.id_for_label }}';
|
||||
var login_mode_id = '#' + '{{ form.login_mode.id_for_label }}';
|
||||
|
||||
var auto_generate_key = '#'+'{{ form.auto_generate_key.id_for_label }}';
|
||||
var password_id = '#' + '{{ form.password.id_for_label }}';
|
||||
var private_key_id = '#' + '{{ form.private_key_file.id_for_label }}';
|
||||
var auto_push_id = '#' + '{{ form.auto_push.id_for_label }}';
|
||||
var sudo_id = '#' + '{{ form.sudo.id_for_label }}';
|
||||
var shell_id = '#' + '{{ form.shell.id_for_label }}';
|
||||
|
||||
var need_change_field = [
|
||||
auto_generate_key, private_key_id, auto_push_id, sudo_id, shell_id
|
||||
];
|
||||
var need_change_field_login_mode = [
|
||||
auto_generate_key, private_key_id, auto_push_id, password_id
|
||||
];
|
||||
|
||||
function protocolChange() {
|
||||
if ($(protocol_id + " option:selected").text() === 'rdp') {
|
||||
$('.auth-fields').removeClass('hidden');
|
||||
$('#command-filter-block').addClass('hidden');
|
||||
$.each(need_change_field, function (index, value) {
|
||||
$(value).closest('.form-group').addClass('hidden')
|
||||
});
|
||||
} else {
|
||||
}
|
||||
else if ($(protocol_id + " option:selected").text() === 'telnet (beta)') {
|
||||
$('.auth-fields').removeClass('hidden');
|
||||
$.each(need_change_field, function (index, value) {
|
||||
$(value).closest('.form-group').addClass('hidden')
|
||||
});
|
||||
}
|
||||
else {
|
||||
if($(login_mode_id).val() === 'manual'){
|
||||
$(sudo_id).closest('.form-group').removeClass('hidden');
|
||||
$(shell_id).closest('.form-group').removeClass('hidden');
|
||||
return
|
||||
}
|
||||
authFieldsDisplay();
|
||||
$.each(need_change_field, function (index, value) {
|
||||
$(value).closest('.form-group').removeClass('hidden')
|
||||
@@ -111,18 +136,35 @@ function authFieldsDisplay() {
|
||||
$('.auth-fields').removeClass('hidden');
|
||||
}
|
||||
}
|
||||
function loginModeChange(){
|
||||
if ($(login_mode_id).val() === 'manual'){
|
||||
$('#auth_title_id').addClass('hidden');
|
||||
$.each(need_change_field_login_mode, function(index, value){
|
||||
$(value).closest('.form-group').addClass('hidden')
|
||||
})
|
||||
}
|
||||
else if($(login_mode_id).val() === 'auto'){
|
||||
$('#auth_title_id').removeClass('hidden');
|
||||
$(password_id).closest('.form-group').removeClass('hidden')
|
||||
protocolChange();
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
authFieldsDisplay();
|
||||
protocolChange();
|
||||
loginModeChange();
|
||||
})
|
||||
.on('change', protocol_id, function(){
|
||||
protocolChange();
|
||||
})
|
||||
.on('change', auto_generate_key, function(){
|
||||
authFieldsDisplay();
|
||||
});
|
||||
})
|
||||
.on('change', login_mode_id, function(){
|
||||
loginModeChange();
|
||||
})
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
24
apps/assets/templates/assets/_user_asset_detail_modal.html
Normal file
24
apps/assets/templates/assets/_user_asset_detail_modal.html
Normal file
@@ -0,0 +1,24 @@
|
||||
{% extends '_modal.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
<style>
|
||||
.modal-body {
|
||||
background-color: white !important;
|
||||
}
|
||||
</style>
|
||||
{% block modal_id %}user_asset_detail_modal{% endblock %}
|
||||
|
||||
{% block modal_title %}{% trans "Asset detail" %}{% endblock %}
|
||||
|
||||
{% block modal_body %}
|
||||
<div class="ibox-content" style="background-color: inherit">
|
||||
<table class="table">
|
||||
<tbody id="asset_detail_tbody">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal_button %}
|
||||
<button data-dismiss="modal" class="btn btn-white" type="button">{% trans "Close" %}</button>
|
||||
{% endblock %}
|
||||
@@ -90,7 +90,7 @@
|
||||
<td colspan="2" class="no-borders">
|
||||
<select data-placeholder="{% trans 'Select nodes' %}" id="nodes_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for node in nodes %}
|
||||
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node.value }}</option>
|
||||
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
|
||||
@@ -5,8 +5,11 @@
|
||||
|
||||
{% block help_message %}
|
||||
<div class="alert alert-info help-message">
|
||||
管理用户是服务器的root,或拥有 NOPASSWD: ALL sudo权限的用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。
|
||||
Windows或其它硬件可以随意设置一个
|
||||
{# 管理用户是资产(被控服务器)上的root,或拥有 NOPASSWD: ALL sudo权限的用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。#}
|
||||
{# Windows或其它硬件可以随意设置一个#}
|
||||
{% trans 'Admin users are asset (charged server) on the root, or have NOPASSWD: ALL sudo permissions users, '%}
|
||||
{% trans 'Jumpserver users of the system using the user to `push system user`, `get assets hardware information`, etc. '%}
|
||||
{% trans 'You can set any one for Windows or other hardware.' %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -90,7 +93,7 @@ $(document).ready(function(){
|
||||
columns: [{data: function(){return ""}}, {data: "name" }, {data: "username" }, {data: "assets_amount" },
|
||||
{data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment" }, {data: "id" }]
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
jumpserver.initServerSideDataTable(options)
|
||||
})
|
||||
|
||||
.on('click', '.btn_admin_user_delete', function () {
|
||||
@@ -107,6 +110,3 @@ $(document).ready(function(){
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
|
||||
{% block form %}
|
||||
<div class="ydxbd" id="formlists" style="display: block;">
|
||||
<p id="tags_p" class="mgl-5 c02">选择需要修改属性</p>
|
||||
<p id="tags_p" class="mgl-5 c02">{% trans 'Select properties that need to be modified' %}</p>
|
||||
<div class="tagBtnList">
|
||||
<a class="label label-primary" id="change_all" value="1">全选</a>
|
||||
<a class="label label-primary" id="change_all" value="1">{% trans 'Select all' %}</a>
|
||||
{% for field in form %}
|
||||
{% if field.name != 'assets' %}
|
||||
<a data-id="{{ field.id_for_label }}" class="label label-default label-primary field-tag" value="1">{{ field.label }}</a>
|
||||
|
||||
@@ -15,9 +15,10 @@
|
||||
{% csrf_token %}
|
||||
<h3>{% trans 'Basic' %}</h3>
|
||||
{% bootstrap_field form.hostname layout="horizontal" %}
|
||||
{% bootstrap_field form.platform layout="horizontal" %}
|
||||
{% bootstrap_field form.ip layout="horizontal" %}
|
||||
{% bootstrap_field form.protocol layout="horizontal" %}
|
||||
{% bootstrap_field form.port layout="horizontal" %}
|
||||
{% bootstrap_field form.platform layout="horizontal" %}
|
||||
{% bootstrap_field form.public_ip layout="horizontal" %}
|
||||
{% bootstrap_field form.domain layout="horizontal" %}
|
||||
|
||||
@@ -85,14 +86,14 @@ $(document).ready(function () {
|
||||
allowClear: true,
|
||||
templateSelection: format
|
||||
});
|
||||
$("#id_platform").change(function (){
|
||||
var platform = $("#id_platform option:selected").text();
|
||||
$("#id_protocol").change(function (){
|
||||
var protocol = $("#id_protocol option:selected").text();
|
||||
var port = 22;
|
||||
if(platform === 'Windows'){
|
||||
if(protocol === 'rdp'){
|
||||
port = 3389;
|
||||
}
|
||||
if(platform === 'Other'){
|
||||
port = null;
|
||||
if(protocol === 'telnet (beta)'){
|
||||
port = 23;
|
||||
}
|
||||
$("#id_port").val(port);
|
||||
});
|
||||
|
||||
@@ -69,6 +69,10 @@
|
||||
<td>{% trans 'Port' %}:</td>
|
||||
<td><b>{{ asset.port }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Protocol' %}:</td>
|
||||
<td><b>{{ asset.protocol }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Admin user' %}:</td>
|
||||
<td><b>{{ asset.admin_user }}</b></td>
|
||||
@@ -130,7 +134,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if user.is_superuser %}
|
||||
{% if user.is_superuser or user.is_org_admin %}
|
||||
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
|
||||
{% block help_message %}
|
||||
<div class="alert alert-info help-message">
|
||||
左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织的,右侧是属于该节点下的资产
|
||||
{# 左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织的,右侧是属于该节点下的资产#}
|
||||
{% trans 'The left side is the asset tree, right click to create, delete, and change the tree node, authorization asset is also organized as a node, and the right side is the asset under that node' %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet">
|
||||
{# <link href="https://cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css" rel="stylesheet">#}
|
||||
<script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
|
||||
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
||||
<style type="text/css">
|
||||
@@ -27,6 +29,10 @@
|
||||
list-style: none;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
.dataTables_wrapper .dataTables_processing {
|
||||
opacity: .9;
|
||||
border: none;
|
||||
}
|
||||
div#rMenu li{
|
||||
margin: 1px 0;
|
||||
cursor: pointer;
|
||||
@@ -161,16 +167,6 @@ function initTable() {
|
||||
}
|
||||
}},
|
||||
|
||||
{#{targets: 5, createdCell: function (td, cellData) {#}
|
||||
{# if (cellData === 'Unknown'){#}
|
||||
{# $(td).html('<i class="fa fa-circle text-warning"></i>')#}
|
||||
{# } else if (!cellData) {#}
|
||||
{# $(td).html('<i class="fa fa-circle text-danger"></i>')#}
|
||||
{# } else {#}
|
||||
{# $(td).html('<i class="fa fa-circle text-navy"></i>')#}
|
||||
{# }#}
|
||||
{# }},#}
|
||||
|
||||
{targets: 5, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "assets:asset-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
@@ -178,13 +174,6 @@ function initTable() {
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-assets:asset-list" %}',
|
||||
|
||||
{#columns: [#}
|
||||
{# {data: "id"}, {data: "hostname" }, {data: "ip" },#}
|
||||
{# {data: "cpu_cores"}, {data: "is_active", orderable: false },#}
|
||||
{# {data: "is_connective", orderable: false}, {data: "id", orderable: false }#}
|
||||
{#],#}
|
||||
|
||||
columns: [
|
||||
{data: "id"}, {data: "hostname" }, {data: "ip" },
|
||||
{data: "cpu_cores"}, {data: "is_active", orderable: false },
|
||||
@@ -202,17 +191,18 @@ function addTreeNode() {
|
||||
if (!parentNode){
|
||||
return
|
||||
}
|
||||
var url = "{% url 'api-assets:node-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", parentNode.id );
|
||||
var url = "{% url 'api-assets:node-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", parentNode.node_id );
|
||||
$.post(url, {}, function (data, status){
|
||||
if (status === "success") {
|
||||
var newNode = {
|
||||
id: data["key"],
|
||||
name: data["value"],
|
||||
id: data["id"],
|
||||
node_id: data["id"],
|
||||
pId: parentNode.id
|
||||
};
|
||||
newNode.checked = zTree.getSelectedNodes()[0].checked;
|
||||
zTree.addNodes(parentNode, 0, newNode);
|
||||
var node = zTree.getNodeByParam('id', newNode.id, parentNode)
|
||||
var node = zTree.getNodeByParam('id', newNode.node_id, parentNode);
|
||||
zTree.editName(node);
|
||||
} else {
|
||||
alert("{% trans 'Create node failed' %}")
|
||||
@@ -231,7 +221,7 @@ function removeTreeNode() {
|
||||
} else if (current_node.assets_amount !== 0) {
|
||||
toastr.error("{% trans 'Have assets, cancel' %}");
|
||||
} else {
|
||||
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.id );
|
||||
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.node_id );
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: "DELETE",
|
||||
@@ -291,7 +281,7 @@ function onBodyMouseDown(event){
|
||||
|
||||
|
||||
function onRename(event, treeId, treeNode, isCancel){
|
||||
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", treeNode.id);
|
||||
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", treeNode.node_id);
|
||||
var data = {"value": treeNode.name};
|
||||
if (isCancel){
|
||||
return
|
||||
@@ -299,15 +289,17 @@ function onRename(event, treeId, treeNode, isCancel){
|
||||
APIUpdateAttr({
|
||||
url: url,
|
||||
body: JSON.stringify(data),
|
||||
method: "PATCH"
|
||||
method: "PATCH",
|
||||
success_message: "{% trans 'Rename success' %}",
|
||||
fail_message: "{% trans 'Rename failed, do not change the root node name' %}"
|
||||
})
|
||||
}
|
||||
|
||||
function onSelected(event, treeNode) {
|
||||
var url = asset_table.ajax.url();
|
||||
url = setUrlParam(url, "node_id", treeNode.id);
|
||||
url = setUrlParam(url, "node_id", treeNode.node_id);
|
||||
url = setUrlParam(url, "show_current_asset", getCookie('show_current_asset'));
|
||||
setCookie('node_selected', treeNode.id);
|
||||
setCookie('node_selected', treeNode.node_id);
|
||||
asset_table.ajax.url(url);
|
||||
asset_table.ajax.reload();
|
||||
}
|
||||
@@ -324,7 +316,7 @@ function selectQueryNode() {
|
||||
node_id = cookie_node_id;
|
||||
}
|
||||
|
||||
node = zTree.getNodesByParam("id", node_id, null);
|
||||
node = zTree.getNodesByParam("node_id", node_id, null);
|
||||
if (node){
|
||||
zTree.selectNode(node[0]);
|
||||
}
|
||||
@@ -341,11 +333,7 @@ function beforeDrop(treeId, treeNodes, targetNode, moveType) {
|
||||
});
|
||||
|
||||
var msg = "你想移动节点: `" + treeNodesNames.join(",") + "` 到 `" + targetNode.value + "` 下吗?";
|
||||
if (confirm(msg)){
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
return confirm(msg);
|
||||
}
|
||||
|
||||
function onDrag(event, treeId, treeNodes) {
|
||||
@@ -354,10 +342,10 @@ function onDrag(event, treeId, treeNodes) {
|
||||
function onDrop(event, treeId, treeNodes, targetNode, moveType) {
|
||||
var treeNodesIds = [];
|
||||
$.each(treeNodes, function (index, value) {
|
||||
treeNodesIds.push(value.id);
|
||||
treeNodesIds.push(value.node_id);
|
||||
});
|
||||
|
||||
var the_url = "{% url 'api-assets:node-add-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", targetNode.id);
|
||||
var the_url = "{% url 'api-assets:node-add-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", targetNode.node_id);
|
||||
var body = {nodes: treeNodesIds};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
@@ -401,16 +389,21 @@ function initTree() {
|
||||
var zNodes = [];
|
||||
$.get("{% url 'api-assets:node-list' %}", function(data, status){
|
||||
$.each(data, function (index, value) {
|
||||
value["pId"] = value["parent"];
|
||||
if (value["key"] === "0") {
|
||||
value["open"] = true;
|
||||
}
|
||||
value["node_id"] = value["id"];
|
||||
value["id"] = value["tree_id"];
|
||||
if (value["tree_id"] !== value["tree_parent"]){
|
||||
value["pId"] = value["tree_parent"];
|
||||
} else {
|
||||
value["isParent"] = true;
|
||||
}
|
||||
value["name"] = value["value"] + ' (' + value['assets_amount'] + ')';
|
||||
value['value'] = value['value'];
|
||||
});
|
||||
zNodes = data;
|
||||
$.fn.zTree.init($("#assetTree"), setting, zNodes);
|
||||
zTree = $.fn.zTree.getZTreeObj("assetTree");
|
||||
var root = zTree.getNodes()[0];
|
||||
zTree.expandNode(root);
|
||||
rMenu = $("#rMenu");
|
||||
selectQueryNode();
|
||||
});
|
||||
@@ -459,10 +452,11 @@ $(document).ready(function(){
|
||||
$.each(rows, function (index, obj) {
|
||||
assets.push(obj.id)
|
||||
});
|
||||
var _node_id = current_node ? current_node : null;
|
||||
$.ajax({
|
||||
url: "{% url "assets:asset-export" %}",
|
||||
method: 'POST',
|
||||
data: JSON.stringify({assets_id: assets, node_id: current_node.id}),
|
||||
data: JSON.stringify({assets_id: assets, node_id: _node_id}),
|
||||
dataType: "json",
|
||||
success: function (data, textStatus) {
|
||||
window.open(data.redirect)
|
||||
@@ -479,8 +473,8 @@ $(document).ready(function(){
|
||||
var current_node;
|
||||
if (nodes && nodes.length ===1 ){
|
||||
current_node = nodes[0];
|
||||
action = setUrlParam(action, 'node_id', current_node.id);
|
||||
{#action += "?node_id=" + current_node.id;#}
|
||||
action = setUrlParam(action, 'node_id', current_node.node_id);
|
||||
{#action += "?node_id=" + current_node.node_id;#}
|
||||
$form.attr("action", action)
|
||||
}
|
||||
$form.find('.help-block').remove();
|
||||
@@ -506,7 +500,7 @@ $(document).ready(function(){
|
||||
var current_node;
|
||||
if (nodes && nodes.length ===1 ){
|
||||
current_node = nodes[0];
|
||||
url += "?node_id=" + current_node.id;
|
||||
url += "?node_id=" + current_node.node_id;
|
||||
}
|
||||
window.open(url, '_self');
|
||||
})
|
||||
@@ -520,7 +514,7 @@ $(document).ready(function(){
|
||||
return null;
|
||||
}
|
||||
|
||||
var the_url = url.replace("{{ DEFAULT_PK }}", current_node.id);
|
||||
var the_url = url.replace("{{ DEFAULT_PK }}", current_node.node_id);
|
||||
function success(data) {
|
||||
rMenu.css({"visibility" : "hidden"});
|
||||
var task_id = data.task;
|
||||
@@ -545,7 +539,7 @@ $(document).ready(function(){
|
||||
return null;
|
||||
}
|
||||
|
||||
var the_url = url.replace("{{ DEFAULT_PK }}", current_node.id);
|
||||
var the_url = url.replace("{{ DEFAULT_PK }}", current_node.node_id);
|
||||
function success(data) {
|
||||
rMenu.css({"visibility" : "hidden"});
|
||||
var task_id = data.task;
|
||||
@@ -634,6 +628,7 @@ $(document).ready(function(){
|
||||
text: "{% trans 'This will delete the selected assets !!!' %}",
|
||||
type: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonText: "{% trans 'Cancel' %}",
|
||||
confirmButtonColor: "#DD6B55",
|
||||
confirmButtonText: "{% trans 'Confirm' %}",
|
||||
closeOnConfirm: false
|
||||
@@ -682,7 +677,7 @@ $(document).ready(function(){
|
||||
};
|
||||
|
||||
APIUpdateAttr({
|
||||
'url': '/api/assets/v1/nodes/' + current_node.id + '/assets/remove/',
|
||||
'url': '/api/assets/v1/nodes/' + current_node.node_id + '/assets/remove/',
|
||||
'method': 'PUT',
|
||||
'body': JSON.stringify(data),
|
||||
'success': success
|
||||
@@ -719,9 +714,7 @@ $(document).ready(function(){
|
||||
return
|
||||
}
|
||||
|
||||
var data = {
|
||||
'assets': assets_selected
|
||||
};
|
||||
var data = {'assets': assets_selected};
|
||||
var success = function () {
|
||||
asset_table2.selected = [];
|
||||
asset_table2.ajax.reload()
|
||||
@@ -729,9 +722,9 @@ $(document).ready(function(){
|
||||
|
||||
var url = '';
|
||||
if (update_node_action === "move") {
|
||||
url = "{% url 'api-assets:node-replace-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.id);
|
||||
url = "{% url 'api-assets:node-replace-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.node_id);
|
||||
} else {
|
||||
url = "{% url 'api-assets:node-add-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.id);
|
||||
url = "{% url 'api-assets:node-add-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.node_id);
|
||||
}
|
||||
|
||||
APIUpdateAttr({
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
{% block form %}
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% if form.no_field_errors %}
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger">
|
||||
{{ form.non_field_errors }}
|
||||
</div>
|
||||
@@ -21,6 +21,7 @@
|
||||
<h3>{% trans 'Basic' %}</h3>
|
||||
{% bootstrap_field form.hostname layout="horizontal" %}
|
||||
{% bootstrap_field form.ip layout="horizontal" %}
|
||||
{% bootstrap_field form.protocol layout="horizontal" %}
|
||||
{% bootstrap_field form.port layout="horizontal" %}
|
||||
{% bootstrap_field form.platform layout="horizontal" %}
|
||||
{% bootstrap_field form.public_ip layout="horizontal" %}
|
||||
|
||||
20
apps/assets/templates/assets/cmd_filter_create_update.html
Normal file
20
apps/assets/templates/assets/cmd_filter_create_update.html
Normal file
@@ -0,0 +1,20 @@
|
||||
{% extends '_base_create_update.html' %}
|
||||
{% load static %}
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form %}
|
||||
<form id="groupForm" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
{% bootstrap_field form.comment layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-4 col-sm-offset-2">
|
||||
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
|
||||
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
168
apps/assets/templates/assets/cmd_filter_detail.html
Normal file
168
apps/assets/templates/assets/cmd_filter_detail.html
Normal file
@@ -0,0 +1,168 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block custom_head_css_js %}
|
||||
<link href='{% static "css/plugins/select2/select2.min.css" %}' rel="stylesheet">
|
||||
<script src='{% static "js/plugins/select2/select2.full.min.js" %}'></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active">
|
||||
<a href="{% url 'assets:cmd-filter-detail' pk=object.id %}" class="text-center">
|
||||
<i class="fa fa-laptop"></i> {% trans 'Detail' %}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<li>
|
||||
<a href="{% url 'assets:cmd-filter-rule-list' pk=object.id %}" class="text-center">
|
||||
<i class="fa fa-laptop"></i> {% trans 'Rules' %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-default" href="{% url 'assets:cmd-filter-update' pk=object.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-danger btn-del">
|
||||
<i class="fa fa-trash-o"></i>{% trans 'Delete' %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-8" style="padding-left: 0;">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span class="label"><b>{{ object.name }}</b></span>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-user">
|
||||
</ul>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr class="no-borders-tr">
|
||||
<td>{% trans 'Name' %}:</td>
|
||||
<td><b>{{ object.name }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Comment' %}:</td>
|
||||
<td><b>{{ object.comment }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Date created' %}:</td>
|
||||
<td><b>{{ object.date_created }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Date updated' %}:</td>
|
||||
<td><b>{{ object.date_updated }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Created by' %}:</td>
|
||||
<td><b>{{ object.created_by }}</b></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-4" style="padding-left: 0; padding-right: 0">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'System users' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table group_edit" id="table-clusters">
|
||||
<tbody>
|
||||
<form>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<select data-placeholder="{% trans 'Binding to system user' %}" id="system_users_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for system_user in system_users_remain %}
|
||||
<option value="{{ system_user.id }}" id="opt_{{ system_user.id }}">{{ system_user }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<button type="button" class="btn btn-primary btn-sm" id="btn-binding-system-users">{% trans 'Confirm' %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
{% for system_user in object.system_users.all %}
|
||||
<tr>
|
||||
<td><b class="bdg-system-users" data-gid={{ system_user.id }}>{{ system_user }}</b></td>
|
||||
<td>
|
||||
<button class="btn btn-danger pull-right btn-xs btn-unbound-system-user" data-gid={{ system_user.id }} type="button"><i class="fa fa-minus"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
function updateCMDFilterSystemUsers(system_users) {
|
||||
var the_url = "{% url 'api-assets:cmd-filter-detail' pk=object.id %}";
|
||||
var body = {
|
||||
system_users: Object.assign([], system_users)
|
||||
};
|
||||
var success = function(data) {
|
||||
location.reload();
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
body: JSON.stringify(body),
|
||||
method: 'PATCH',
|
||||
success: success
|
||||
});
|
||||
}
|
||||
$(document).ready(function () {
|
||||
$(".select2").select2();
|
||||
}).on('click', '#btn-binding-system-users', function () {
|
||||
var origin_system_users = $.map($(".bdg-system-users"), function (s) {
|
||||
return $(s).data('gid')
|
||||
});
|
||||
var new_selected_system_users_id = $.map($("#system_users_selected").select2('data'), function (s) {
|
||||
return s.id;
|
||||
});
|
||||
var system_users = origin_system_users.concat(new_selected_system_users_id);
|
||||
updateCMDFilterSystemUsers(system_users)
|
||||
}).on('click', '.btn-unbound-system-user', function () {
|
||||
var unbound_system_user = $(this).data('gid');
|
||||
var origin_system_users = $.map($(".bdg-system-users"), function (s) {
|
||||
return $(s).data('gid')
|
||||
});
|
||||
var system_users = $.grep(origin_system_users, function (n, i) {
|
||||
return n !== unbound_system_user
|
||||
});
|
||||
updateCMDFilterSystemUsers(system_users)
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
89
apps/assets/templates/assets/cmd_filter_list.html
Normal file
89
apps/assets/templates/assets/cmd_filter_list.html
Normal file
@@ -0,0 +1,89 @@
|
||||
{% extends '_base_list.html' %}
|
||||
{% load i18n static %}
|
||||
{% block table_search %}{% endblock %}
|
||||
{% block help_message %}
|
||||
<div class="alert alert-info help-message">
|
||||
{% trans 'System user bound some command filter, each command filter has some rules,'%}
|
||||
{% trans 'When user login asset with this system user, then run a command,' %}
|
||||
{% trans 'The command will be filter by rules, higher priority rule run first,' %}
|
||||
{% trans 'When a rule matched, if rule action is allow, then allow command execute,' %}
|
||||
{% trans 'else if action is deny, then command with be deny,' %}
|
||||
{% trans 'else match next rule, if none matched, allowed' %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block table_container %}
|
||||
<div class="uc pull-left m-r-5">
|
||||
<a href="{% url 'assets:cmd-filter-create' %}" class="btn btn-sm btn-primary"> {% trans "Create command filter" %} </a>
|
||||
</div>
|
||||
<table class="table table-striped table-bordered table-hover " id="cmd_filter_list_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<input type="checkbox" id="check_all" class="ipt_check_all" >
|
||||
</th>
|
||||
<th class="text-center">{% trans 'Name' %}</th>
|
||||
<th class="text-center">{% trans 'Rules' %}</th>
|
||||
<th class="text-center">{% trans 'System users' %}</th>
|
||||
<th class="text-center">{% trans 'Comment' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
{% block content_bottom_left %}{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
function initTable() {
|
||||
var options = {
|
||||
ele: $('#cmd_filter_list_table'),
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url 'assets:cmd-filter-detail' pk=DEFAULT_PK %}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||
}},
|
||||
{targets: 2, createdCell: function (td, cellData, rowData) {
|
||||
var filters_list_btn = '<a href="{% url "assets:cmd-filter-rule-list" pk=DEFAULT_PK %}">' + cellData.length + '</a>';
|
||||
filters_list_btn = filters_list_btn.replace("{{ DEFAULT_PK }}", rowData.id);
|
||||
$(td).html(filters_list_btn);
|
||||
}},
|
||||
{targets: 3, createdCell: function (td, cellData, rowData) {
|
||||
var system_users_list_btn = '<a href="{% url "assets:cmd-filter-detail" pk=DEFAULT_PK %}">' + cellData.length + '</a>';
|
||||
system_users_list_btn = system_users_list_btn.replace("{{ DEFAULT_PK }}", rowData.id);
|
||||
$(td).html(system_users_list_btn);
|
||||
}},
|
||||
{targets: 5, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "assets:cmd-filter-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
$(td).html(update_btn + del_btn)
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-assets:cmd-filter-list" %}',
|
||||
columns: [
|
||||
{data: "id"}, {data: "name" }, {data: "rules" },
|
||||
{data: "system_users" }, {data: "comment"}, {data: "id"}
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initServerSideDataTable(options);
|
||||
}
|
||||
$(document).ready(function(){
|
||||
initTable();
|
||||
})
|
||||
.on('click', '.btn-delete', function () {
|
||||
var $this = $(this);
|
||||
var $data_table = $('#cmd_filter_list_table').DataTable();
|
||||
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
|
||||
var uid = $this.data('uid');
|
||||
var the_url = '{% url "api-assets:cmd-filter-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
|
||||
objectDelete($this, name, the_url);
|
||||
setTimeout( function () {
|
||||
$data_table.ajax.reload();
|
||||
}, 3000);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load bootstrap3 %}
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
|
||||
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<h5>{{ action }}</h5>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<form enctype="multipart/form-data" method="post" class="form-horizontal" action="" >
|
||||
{% csrf_token %}
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger">
|
||||
{{ form.non_field_errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% bootstrap_form form layout="horizontal" %}
|
||||
<div class="form-group">
|
||||
<div class="col-sm-4 col-sm-offset-2">
|
||||
<button class="btn btn-white" type="reset">{% trans 'Reset' %}</button>
|
||||
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
var content_origin_placeholder = '';
|
||||
var content_origin_help_text = '';
|
||||
var content_ref = '';
|
||||
var content_help_ref = '';
|
||||
|
||||
$(document).ready(function(){
|
||||
content_ref = $('#id_content');
|
||||
content_help_ref = content_ref.next();
|
||||
content_origin_placeholder = content_ref.attr('placeholder');
|
||||
content_origin_help_text = content_help_ref.html();
|
||||
}).on('change', '#id_type', function () {
|
||||
if ($('#id_type :selected').val() === 'regex') {
|
||||
content_ref.attr('placeholder', 'rm.*|reboot|shutdown');
|
||||
content_help_ref.html("");
|
||||
} else {
|
||||
content_ref.attr('placeholder', content_origin_placeholder);
|
||||
content_help_ref.html(content_origin_help_text);
|
||||
}
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
115
apps/assets/templates/assets/cmd_filter_rule_list.html
Normal file
115
apps/assets/templates/assets/cmd_filter_rule_list.html
Normal file
@@ -0,0 +1,115 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block custom_head_css_js %}
|
||||
<link href='{% static "css/plugins/select2/select2.min.css" %}' rel="stylesheet">
|
||||
<script src='{% static "js/plugins/select2/select2.full.min.js" %}'></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<li>
|
||||
<a href="{% url 'assets:cmd-filter-detail' pk=object.id %}" class="text-center">
|
||||
<i class="fa fa-laptop"></i> {% trans 'Detail' %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="active">
|
||||
<a href="{% url 'assets:cmd-filter-rule-list' pk=object.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Rules' %} </a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-12" style="padding-left: 0;">
|
||||
<div class="" id="content_start">
|
||||
</div>
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span style="float: left"><b>{% trans 'Command filter rule list' %}</b></span>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-user">
|
||||
</ul>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<div class="uc pull-left m-r-5">
|
||||
<a href="{% url 'assets:cmd-filter-rule-create' filter_pk=object.id %}" class="btn btn-sm btn-primary"> {% trans "Create rule" %} </a>
|
||||
</div>
|
||||
<table class="table table-striped table-bordered table-hover " id="cmd_filter_rule_list_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<input type="checkbox" id="check_all" class="ipt_check_all" >
|
||||
</th>
|
||||
<th class="text-center">{% trans 'Type' %}</th>
|
||||
<th class="text-center">{% trans 'Content' %}</th>
|
||||
<th class="text-center">{% trans 'Priority' %}</th>
|
||||
<th class="text-center">{% trans 'Strategy' %}</th>
|
||||
<th class="text-center">{% trans 'Comment' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block content_bottom_left %}{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
function initTable() {
|
||||
var options = {
|
||||
ele: $('#cmd_filter_rule_list_table'),
|
||||
columnDefs: [
|
||||
{targets: 6, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "assets:cmd-filter-rule-update" filter_pk=object.id pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
$(td).html(update_btn + del_btn)
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-assets:cmd-filter-rule-list" filter_pk=object.id %}',
|
||||
columns: [
|
||||
{data: "id"}, {data: "type.display" }, {data: 'content'}, {data: 'priority'},
|
||||
{data: 'action.display'}, {data: "comment" }, {data: "id"}
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initServerSideDataTable(options);
|
||||
}
|
||||
$(document).ready(function(){
|
||||
initTable();
|
||||
})
|
||||
.on('click', '.btn-delete', function () {
|
||||
var $this = $(this);
|
||||
var $data_table = $('#cmd_filter_rule_list_table').DataTable();
|
||||
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
|
||||
var uid = $this.data('uid');
|
||||
var the_url = '{% url "api-assets:cmd-filter-rule-detail" filter_pk=object.id pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
|
||||
objectDelete($this, name, the_url);
|
||||
setTimeout( function () {
|
||||
$data_table.ajax.reload();
|
||||
}, 3000);
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -98,7 +98,7 @@ function initTable() {
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
jumpserver.initServerSideDataTable(options);
|
||||
}
|
||||
$(document).ready(function(){
|
||||
initTable();
|
||||
@@ -120,8 +120,8 @@ $(document).ready(function(){
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
method: "GET",
|
||||
success_message: "可连接",
|
||||
fail_message: "连接失败"
|
||||
success_message: "{% trans 'Can be connected' %}",
|
||||
fail_message: "{% trans 'The connection fails' %}"
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
{% extends '_base_list.html' %}
|
||||
{% load i18n static %}
|
||||
{% block table_search %}{% endblock %}
|
||||
|
||||
{% block help_message %}
|
||||
<div class="alert alert-info help-message">
|
||||
{# 网域功能是为了解决部分环境(如:混合云)无法直接连接而新增的功能,原理是通过网关服务器进行跳转登录。<br>#}
|
||||
{# JMS => 网域网关 => 目标资产#}
|
||||
{% trans 'The domain function is added to address the fact that some environments (such as the hybrid cloud) cannot be connected directly by jumping on the gateway server.' %}
|
||||
<br>
|
||||
{% trans 'JMS => Domain gateway => Target assets' %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_container %}
|
||||
<div class="uc pull-left m-r-5">
|
||||
<a href="{% url 'assets:domain-create' %}" class="btn btn-sm btn-primary"> {% trans "Create domain" %} </a>
|
||||
@@ -51,7 +62,7 @@ function initTable() {
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
jumpserver.initServerSideDataTable(options);
|
||||
}
|
||||
$(document).ready(function(){
|
||||
initTable();
|
||||
@@ -69,6 +80,3 @@ $(document).ready(function(){
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
{% bootstrap_field form.domain layout="horizontal" %}
|
||||
|
||||
{% block auth %}
|
||||
<h3>{% trans 'Auth' %}</h3>
|
||||
<h3 id="auth_title">{% trans 'Auth' %}</h3>
|
||||
<div class="auth-fields">
|
||||
{% bootstrap_field form.username layout="horizontal" %}
|
||||
{% bootstrap_field form.password layout="horizontal" %}
|
||||
@@ -72,14 +72,23 @@
|
||||
var protocol_id = '#' + '{{ form.protocol.id_for_label }}';
|
||||
var private_key_id = '#' + '{{ form.private_key_file.id_for_label }}';
|
||||
var port = '#' + '{{ form.port.id_for_label }}';
|
||||
var username = '#' + '{{ form.username.id_for_label }}';
|
||||
var password = '#' + '{{ form.password.id_for_label }}';
|
||||
var auth_title = '#auth_title';
|
||||
|
||||
function protocolChange() {
|
||||
if ($(protocol_id + " option:selected").text() === 'rdp') {
|
||||
$(port).val(3389);
|
||||
$(private_key_id).closest('.form-group').addClass('hidden')
|
||||
{#$(port).val(3389);#}
|
||||
$(private_key_id).closest('.form-group').addClass('hidden');
|
||||
$(username).closest('.form-group').addClass('hidden');
|
||||
$(password).closest('.form-group').addClass('hidden');
|
||||
$(auth_title).addClass('hidden');
|
||||
} else {
|
||||
$(port).val(22);
|
||||
$(private_key_id).closest('.form-group').removeClass('hidden')
|
||||
{#$(port).val(22);#}
|
||||
$(private_key_id).closest('.form-group').removeClass('hidden');
|
||||
$(username).closest('.form-group').removeClass('hidden');
|
||||
$(password).closest('.form-group').removeClass('hidden');
|
||||
$(auth_title).removeClass('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ function initTable() {
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
jumpserver.initServerSideDataTable(options);
|
||||
}
|
||||
$(document).ready(function(){
|
||||
initTable();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load common_tags %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
@@ -50,6 +51,7 @@
|
||||
<th>{% trans 'IP' %}</th>
|
||||
<th>{% trans 'Port' %}</th>
|
||||
<th>{% trans 'Reachable' %}</th>
|
||||
<th>{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -67,14 +69,6 @@
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr class="no-borders-tr">
|
||||
<td width="50%">{% trans 'Push system user now' %}:</td>
|
||||
<td>
|
||||
<span style="float: right">
|
||||
<button type="button" class="btn btn-primary btn-xs btn-push" style="width: 54px">{% trans 'Push' %}</button>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="50%">{% trans 'Test assets connective' %}:</td>
|
||||
<td>
|
||||
<span style="float: right">
|
||||
@@ -82,6 +76,52 @@
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% if system_user.auto_push %}
|
||||
<tr>
|
||||
<td width="50%">{% trans 'Push system user now' %}:</td>
|
||||
<td>
|
||||
<span style="float: right">
|
||||
<button type="button" class="btn btn-primary btn-xs btn-push" style="width: 54px">{% trans 'Push' %}</button>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Nodes' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table node_edit" id="add-asset2group">
|
||||
<tbody>
|
||||
<form>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<select data-placeholder="{% trans 'Add to node' %}" id="node_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for node in nodes_remain %}
|
||||
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<button type="button" class="btn btn-info btn-sm" id="btn-add-to-node">{% trans 'Confirm' %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
|
||||
{% for node in system_user.nodes.all|sort %}
|
||||
<tr>
|
||||
<td ><b class="bdg_node" data-gid={{ node.id }}>{{ node }}</b></td>
|
||||
<td>
|
||||
<button class="btn btn-danger pull-right btn-xs btn-remove-from-node" type="button"><i class="fa fa-minus"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -96,7 +136,7 @@
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
function initAssetsTable() {
|
||||
var unreachable = {{ system_user.unreachable_assets|safe}};
|
||||
var unreachable = {{ system_user.unreachable_assets|safe }};
|
||||
var options = {
|
||||
ele: $('#system_user_list'),
|
||||
buttons: [],
|
||||
@@ -112,27 +152,64 @@ function initAssetsTable() {
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData) {
|
||||
var push_btn = '';
|
||||
{% if system_user.auto_push %}
|
||||
push_btn = '<a class="btn btn-xs btn-primary btn-push-asset" data-uid="{{ DEFAULT_PK }}" >{% trans "Push" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
||||
{% endif %}
|
||||
var test_btn = ' <a class="btn btn-xs btn-info btn-test-asset" data-uid="{{ DEFAULT_PK }}" >{% trans "Test" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
||||
{#var unbound_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-asset-unbound" data-uid="{{ DEFAULT_PK }}">{% trans "Unbound" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);#}
|
||||
$(td).html(push_btn + test_btn);
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-assets:asset-list" %}?system_user_id={{ system_user.id }}',
|
||||
columns: [{data: "hostname" }, {data: "ip" }, {data: "port" }, {data: "hostname" }],
|
||||
ajax_url: '{% url "api-assets:system-user-assets" pk=system_user.id %}',
|
||||
columns: [{data: "hostname" }, {data: "ip" }, {data: "port" }, {data: "hostname" }, {data: "id"}],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initServerSideDataTable(options);
|
||||
}
|
||||
|
||||
function updateSystemUserNode(nodes) {
|
||||
var the_url = "{% url 'api-assets:system-user-detail' pk=system_user.id %}";
|
||||
var body = {
|
||||
nodes: Object.assign([], nodes)
|
||||
};
|
||||
var success = function(data) {
|
||||
// remove all the selected groups from select > option and rendered ul element;
|
||||
$('.select2-selection__rendered').empty();
|
||||
$('#node_selected').val('');
|
||||
$.map(jumpserver.nodes_selected, function(node_name, index) {
|
||||
$('#opt_' + index).remove();
|
||||
// change tr html of user groups.
|
||||
$('.node_edit tbody').append(
|
||||
'<tr>' +
|
||||
'<td><b class="bdg_node" data-gid="' + index + '">' + node_name + '</b></td>' +
|
||||
'<td><button class="btn btn-danger btn-xs pull-right btn-remove-from-node" type="button"><i class="fa fa-minus"></i></button></td>' +
|
||||
'</tr>'
|
||||
)
|
||||
});
|
||||
// clear jumpserver.nodes_selected
|
||||
jumpserver.nodes_selected = {};
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
body: JSON.stringify(body),
|
||||
success: success
|
||||
});
|
||||
}
|
||||
jumpserver.nodes_selected = {};
|
||||
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2()
|
||||
.on("select2:select", function (evt) {
|
||||
var data = evt.params.data;
|
||||
jumpserver.assets_selected[data.id] = data.text;
|
||||
jumpserver.asset_groups_selected[data.id] = data.text;
|
||||
})
|
||||
.on('select2:unselect', function(evt) {
|
||||
var data = evt.params.data;
|
||||
delete jumpserver.assets_selected[data.id];
|
||||
delete jumpserver.asset_groups_selected[data.id];
|
||||
});
|
||||
.on('select2:select', function(evt) {
|
||||
var data = evt.params.data;
|
||||
jumpserver.nodes_selected[data.id] = data.text;
|
||||
})
|
||||
.on('select2:unselect', function(evt) {
|
||||
var data = evt.params.data;
|
||||
delete jumpserver.nodes_selected[data.id];
|
||||
});
|
||||
initAssetsTable();
|
||||
})
|
||||
.on('click', '.btn-push', function () {
|
||||
@@ -140,11 +217,16 @@ $(document).ready(function () {
|
||||
var error = function (data) {
|
||||
alert(data)
|
||||
};
|
||||
var success = function (data) {
|
||||
var task_id = data.task;
|
||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||
window.open(url, '', 'width=800,height=600,left=400,top=400')
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
error: error,
|
||||
method: 'GET',
|
||||
success_message: "{% trans "Task has been send, Go to ops task list seen result" %}"
|
||||
success: success
|
||||
});
|
||||
})
|
||||
.on('click', '.btn-test-connective', function () {
|
||||
@@ -152,15 +234,85 @@ $(document).ready(function () {
|
||||
var error = function (data) {
|
||||
alert(data)
|
||||
};
|
||||
var success = function (data) {
|
||||
var task_id = data.task;
|
||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||
window.open(url, '', 'width=800,height=600,left=400,top=400')
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
error: error,
|
||||
method: 'GET',
|
||||
success_message: "{% trans "Task has been send, seen left assets status" %}"
|
||||
success: success
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
.on('click', '.btn-remove-from-node', function() {
|
||||
var $this = $(this);
|
||||
var $tr = $this.closest('tr');
|
||||
var $badge = $tr.find('.bdg_node');
|
||||
var gid = $badge.data('gid');
|
||||
var node_name = $badge.html() || $badge.text();
|
||||
$('#groups_selected').append(
|
||||
'<option value="' + gid + '" id="opt_' + gid + '">' + node_name + '</option>'
|
||||
);
|
||||
$tr.remove();
|
||||
var nodes = $('.bdg_node').map(function () {
|
||||
return $(this).data('gid');
|
||||
}).get();
|
||||
updateSystemUserNode(nodes);
|
||||
})
|
||||
.on('click', '#btn-add-to-node', function() {
|
||||
if (Object.keys(jumpserver.nodes_selected).length === 0) {
|
||||
return false;
|
||||
}
|
||||
var nodes = $('.bdg_node').map(function() {
|
||||
return $(this).data('gid');
|
||||
}).get();
|
||||
$.map(jumpserver.nodes_selected, function(value, index) {
|
||||
nodes.push(index);
|
||||
});
|
||||
updateSystemUserNode(nodes);
|
||||
})
|
||||
.on('click', '.btn-push-asset', function () {
|
||||
var $this = $(this);
|
||||
var asset_id = $this.data('uid');
|
||||
var the_url = "{% url 'api-assets:system-user-push-to-asset' pk=object.id aid=DEFAULT_PK %}";
|
||||
the_url = the_url.replace("{{ DEFAULT_PK }}", asset_id);
|
||||
var success = function (data) {
|
||||
var task_id = data.task;
|
||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||
window.open(url, '', 'width=800,height=600,left=400,top=400')
|
||||
};
|
||||
var error = function (data) {
|
||||
alert(data)
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
method: 'GET',
|
||||
success: success,
|
||||
error: error
|
||||
})
|
||||
})
|
||||
.on('click', '.btn-test-asset', function () {
|
||||
var $this = $(this);
|
||||
var asset_id = $this.data('uid');
|
||||
var the_url = "{% url 'api-assets:system-user-test-to-asset' pk=object.id aid=DEFAULT_PK %}";
|
||||
the_url = the_url.replace("{{ DEFAULT_PK }}", asset_id);
|
||||
var success = function (data) {
|
||||
var task_id = data.task;
|
||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||
window.open(url, '', 'width=800,height=600,left=400,top=400')
|
||||
};
|
||||
var error = function (data) {
|
||||
alert(data)
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
method: 'GET',
|
||||
success: success,
|
||||
error: error
|
||||
})
|
||||
})
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
<li class="active">
|
||||
<a href="{% url 'assets:system-user-detail' pk=system_user.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
|
||||
</li>
|
||||
{# <li>#}
|
||||
{# <a href="{% url 'assets:system-user-asset' pk=system_user.id %}" class="text-center">#}
|
||||
{# <i class="fa fa-bar-chart-o"></i> {% trans 'Attached assets' %}#}
|
||||
{# </a>#}
|
||||
{# </li>#}
|
||||
<li>
|
||||
<a href="{% url 'assets:system-user-asset' pk=system_user.id %}" class="text-center">
|
||||
<i class="fa fa-bar-chart-o"></i> {% trans 'Assets' %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-default" href="{% url 'assets:system-user-update' pk=system_user.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
|
||||
</li>
|
||||
@@ -62,6 +62,10 @@
|
||||
<td>{% trans 'Username' %}:</td>
|
||||
<td><b>{{ system_user.username }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Login mode' %}:</td>
|
||||
<td><b>{{ system_user.get_login_mode_display }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Protocol' %}:</td>
|
||||
<td><b id="id_protocol_type">{{ system_user.protocol }}</b></td>
|
||||
@@ -148,66 +152,51 @@
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td width="50%">{% trans 'Clear auth' %}:</td>
|
||||
<td>
|
||||
<span style="float: right">
|
||||
<button type="button" class="btn btn-primary btn-xs btn-clear-auth" style="width: 54px">{% trans 'Clear' %}</button>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{# <tr>#}
|
||||
{# <td width="50%">{% trans 'Change auth period' %}:</td>#}
|
||||
{# <td>#}
|
||||
{# <span style="float: right">#}
|
||||
{# <button type="button" class="btn btn-primary btn-xs" style="width: 54px">{% trans 'Reset' %}</button>#}
|
||||
{# </span>#}
|
||||
{# </td>#}
|
||||
{# </tr>#}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Nodes' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table node_edit" id="add-asset2group">
|
||||
<tbody>
|
||||
<form>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<select data-placeholder="{% trans 'Add to node' %}" id="node_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for node in nodes_remain %}
|
||||
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<button type="button" class="btn btn-info btn-sm" id="btn-add-to-node">{% trans 'Confirm' %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
|
||||
{% for node in system_user.nodes.all %}
|
||||
<tr>
|
||||
<td ><b class="bdg_node" data-gid={{ node.id }}>{{ node }}</b></td>
|
||||
<td>
|
||||
<button class="btn btn-danger pull-right btn-xs btn-remove-from-node" type="button"><i class="fa fa-minus"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if system_user.protocol != 'rdp' %}
|
||||
<div class="col-sm-4" style="padding-left: 0; padding-right: 0">
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Command filter' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<form>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<select data-placeholder="{% trans 'Binding command filters' %}" id="command_filters_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
|
||||
{% for cf in cmd_filters_remain %}
|
||||
<option value="{{ cf.id }}" id="opt_{{ cf.id }}" >{{ cf }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<button type="button" class="btn btn-info btn-sm" id="btn-binding-command-filters">{% trans 'Confirm' %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
{% for cf in object.cmd_filters.all %}
|
||||
<tr>
|
||||
<td><b class="bdg-command-filters" data-gid={{ cf.id }}>{{ cf }}</b></td>
|
||||
<td>
|
||||
<button class="btn btn-danger pull-right btn-xs btn-unbound-command-filter" data-gid={{ cf.id }} type="button"><i class="fa fa-minus"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -215,27 +204,13 @@
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
function updateSystemUserNode(nodes) {
|
||||
function updateCommandFilters(command_filters) {
|
||||
var the_url = "{% url 'api-assets:system-user-detail' pk=system_user.id %}";
|
||||
var body = {
|
||||
nodes: Object.assign([], nodes)
|
||||
cmd_filters: Object.assign([], command_filters)
|
||||
};
|
||||
var success = function(data) {
|
||||
// remove all the selected groups from select > option and rendered ul element;
|
||||
$('.select2-selection__rendered').empty();
|
||||
$('#node_selected').val('');
|
||||
$.map(jumpserver.nodes_selected, function(node_name, index) {
|
||||
$('#opt_' + index).remove();
|
||||
// change tr html of user groups.
|
||||
$('.node_edit tbody').append(
|
||||
'<tr>' +
|
||||
'<td><b class="bdg_node" data-gid="' + index + '">' + node_name + '</b></td>' +
|
||||
'<td><button class="btn btn-danger btn-xs pull-right btn-remove-from-node" type="button"><i class="fa fa-minus"></i></button></td>' +
|
||||
'</tr>'
|
||||
)
|
||||
});
|
||||
// clear jumpserver.groups_selected
|
||||
jumpserver.nodes_selected = {};
|
||||
location.reload();
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
@@ -243,21 +218,12 @@ function updateSystemUserNode(nodes) {
|
||||
success: success
|
||||
});
|
||||
}
|
||||
jumpserver.nodes_selected = {};
|
||||
$(document).ready(function () {
|
||||
if($('#id_protocol_type').text() === 'rdp'){
|
||||
$('.only-ssh').addClass('hidden')
|
||||
}
|
||||
$(".panel-body .table tr:visible:first").addClass('no-borders-tr');
|
||||
$('.select2').select2()
|
||||
.on('select2:select', function(evt) {
|
||||
var data = evt.params.data;
|
||||
jumpserver.nodes_selected[data.id] = data.text;
|
||||
})
|
||||
.on('select2:unselect', function(evt) {
|
||||
var data = evt.params.data;
|
||||
delete jumpserver.nodes_selected[data.id];
|
||||
});
|
||||
})
|
||||
.on('click', '#btn-auto-push', function () {
|
||||
var checked = $(this).prop('checked');
|
||||
@@ -269,33 +235,6 @@ $(document).ready(function () {
|
||||
url: the_url,
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
})
|
||||
.on('click', '#btn-add-to-node', function() {
|
||||
if (Object.keys(jumpserver.nodes_selected).length === 0) {
|
||||
return false;
|
||||
}
|
||||
var nodes = $('.bdg_node').map(function() {
|
||||
return $(this).data('gid');
|
||||
}).get();
|
||||
$.map(jumpserver.nodes_selected, function(value, index) {
|
||||
nodes.push(index);
|
||||
});
|
||||
updateSystemUserNode(nodes);
|
||||
})
|
||||
.on('click', '.btn-remove-from-node', function() {
|
||||
var $this = $(this);
|
||||
var $tr = $this.closest('tr');
|
||||
var $badge = $tr.find('.bdg_node');
|
||||
var gid = $badge.data('gid');
|
||||
var node_name = $badge.html() || $badge.text();
|
||||
$('#groups_selected').append(
|
||||
'<option value="' + gid + '" id="opt_' + gid + '">' + node_name + '</option>'
|
||||
);
|
||||
$tr.remove();
|
||||
var nodes = $('.bdg_node').map(function () {
|
||||
return $(this).data('gid');
|
||||
}).get();
|
||||
updateSystemUserNode(nodes);
|
||||
}).on('click', '.btn-del', function () {
|
||||
var $this = $(this);
|
||||
var name = "{{ system_user.name}}";
|
||||
@@ -331,13 +270,24 @@ $(document).ready(function () {
|
||||
success: success,
|
||||
flash_message: false
|
||||
});
|
||||
}).on('click', '.btn-clear-auth', function () {
|
||||
var the_url = '{% url "api-assets:system-user-auth-info" pk=system_user.id %}';
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
method: 'DELETE',
|
||||
success_message: "{% trans 'Clear auth' %}" + " {% trans 'success' %}"
|
||||
}).on('click', '#btn-binding-command-filters', function () {
|
||||
var new_selected_cmd_filters = $.map($('#command_filters_selected').select2('data'), function (i) {
|
||||
return i.id;
|
||||
});
|
||||
var origin_cmd_filters = $.map($(".bdg-command-filters"), function (s) {
|
||||
return $(s).data('gid')
|
||||
});
|
||||
var command_filters = new_selected_cmd_filters.concat(origin_cmd_filters);
|
||||
updateCommandFilters(command_filters)
|
||||
}).on('click', '.btn-unbound-command-filter', function () {
|
||||
var unbound_command_filter = $(this).data('gid');
|
||||
var origin_command_filters = $.map($(".bdg-command-filters"), function (s) {
|
||||
return $(s).data('gid')
|
||||
});
|
||||
var command_filters = $.grep(origin_command_filters, function (n, i) {
|
||||
return n !== unbound_command_filter
|
||||
});
|
||||
updateCommandFilters(command_filters)
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -3,10 +3,13 @@
|
||||
|
||||
{% block help_message %}
|
||||
<div class="alert alert-info help-message">
|
||||
系统用户是 Jumpserver跳转登录资产时使用的用户,可以理解为登录资产用户,如 web, sa, dba(`ssh web@some-host`), 而不是使用某个用户的用户名跳转登录服务器(`ssh xiaoming@some-host`);
|
||||
简单来说是 用户使用自己的用户名登录Jumpserver, Jumpserver使用系统用户登录资产。
|
||||
系统用户创建时,如果选择了自动推送 Jumpserver会使用ansible自动推送系统用户到资产中,如果资产(交换机、windows)不支持ansible, 请手动填写账号密码。
|
||||
目前还不支持Windows的自动推送
|
||||
{# 系统用户是 Jumpserver跳转登录资产时使用的用户,可以理解为登录资产用户,如 web, sa, dba(`ssh web@some-host`), 而不是使用某个用户的用户名跳转登录服务器(`ssh xiaoming@some-host`);#}
|
||||
{# 简单来说是 用户使用自己的用户名登录Jumpserver, Jumpserver使用系统用户登录资产。#}
|
||||
{# 系统用户创建时,如果选择了自动推送 Jumpserver会使用ansible自动推送系统用户到资产中,如果资产(交换机、windows)不支持ansible, 请手动填写账号密码。#}
|
||||
{# 目前还不支持Windows的自动推送#}
|
||||
{% trans 'System user is Jumpserver jump login assets used by the users, can be understood as the user login assets, such as web, sa, the dba (` ssh web@some-host `), rather than using a user the username login server jump (` ssh xiaoming@some-host `); '%}
|
||||
{% trans 'In simple terms, users log into Jumpserver using their own username, and Jumpserver uses system users to log into assets. '%}
|
||||
{% trans 'When system users are created, if you choose auto push Jumpserver to use ansible push system users into the asset, if the asset (Switch, Windows) does not support ansible, please manually fill in the account password. Automatic push for Windows is not currently supported.' %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -26,6 +29,7 @@
|
||||
<th class="text-center">{% trans 'Name' %}</th>
|
||||
<th class="text-center">{% trans 'Username' %}</th>
|
||||
<th class="text-center">{% trans 'Protocol' %}</th>
|
||||
<th class="text-center">{% trans 'Login mode' %}</th>
|
||||
<th class="text-center">{% trans 'Asset' %}</th>
|
||||
<th class="text-center">{% trans 'Reachable' %}</th>
|
||||
<th class="text-center">{% trans 'Unreachable' %}</th>
|
||||
@@ -48,7 +52,7 @@ function initTable() {
|
||||
var detail_btn = '<a href="{% url "assets:system-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||
}},
|
||||
{targets: 5, createdCell: function (td, cellData) {
|
||||
{targets: 6, createdCell: function (td, cellData) {
|
||||
var innerHtml = "";
|
||||
if (cellData !== 0) {
|
||||
innerHtml = "<span class='text-navy'>" + cellData + "</span>";
|
||||
@@ -57,7 +61,7 @@ function initTable() {
|
||||
}
|
||||
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData +'">' + innerHtml + '</span>');
|
||||
}},
|
||||
{targets: 6, createdCell: function (td, cellData) {
|
||||
{targets: 7, createdCell: function (td, cellData) {
|
||||
var innerHtml = "";
|
||||
if (cellData !== 0) {
|
||||
innerHtml = "<span class='text-danger'>" + cellData + "</span>";
|
||||
@@ -66,7 +70,7 @@ function initTable() {
|
||||
}
|
||||
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
|
||||
}},
|
||||
{targets: 7, createdCell: function (td, cellData, rowData) {
|
||||
{targets: 8, createdCell: function (td, cellData, rowData) {
|
||||
var val = 0;
|
||||
var innerHtml = "";
|
||||
var total = rowData.assets_amount;
|
||||
@@ -84,19 +88,19 @@ function initTable() {
|
||||
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
|
||||
|
||||
}},
|
||||
{targets: 9, createdCell: function (td, cellData, rowData) {
|
||||
{targets: 10, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "assets:system-user-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
$(td).html(update_btn + del_btn)
|
||||
}}],
|
||||
ajax_url: '{% url "api-assets:system-user-list" %}',
|
||||
columns: [
|
||||
{data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"}, {data: "assets_amount" },
|
||||
{data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"}, {data: "get_login_mode_display"}, {data: "assets_amount" },
|
||||
{data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment" }, {data: "id" }
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
jumpserver.initServerSideDataTable(options);
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
@@ -134,6 +138,7 @@ $(document).ready(function(){
|
||||
text: "{% trans 'This will delete the selected System Users !!!' %}",
|
||||
type: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonText: "{% trans 'Cancel' %}",
|
||||
confirmButtonColor: "#DD6B55",
|
||||
confirmButtonText: "{% trans 'Confirm' %}",
|
||||
closeOnConfirm: false
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
{% load bootstrap3 %}
|
||||
|
||||
{% block auth %}
|
||||
<h3>{% trans 'Auth' %}</h3>
|
||||
{% bootstrap_field form.password layout="horizontal" %}
|
||||
{% bootstrap_field form.private_key_file layout="horizontal" %}
|
||||
<div class="form-group">
|
||||
|
||||
@@ -55,11 +55,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include 'assets/_user_asset_detail_modal.html' %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
var zTree, rMenu, asset_table;
|
||||
var zTree, asset_table, show=0;
|
||||
var inited = false;
|
||||
var url;
|
||||
function initTable() {
|
||||
@@ -68,14 +71,15 @@ function initTable() {
|
||||
} else {
|
||||
inited = true;
|
||||
}
|
||||
console.log("init table")
|
||||
url = "{% url 'api-perms:my-assets' %}";
|
||||
var options = {
|
||||
ele: $('#user_assets_table'),
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
{% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %}
|
||||
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||
}},
|
||||
var detail_btn = '<a class="asset_detail" asset-id="rowData_id" data-toggle="modal" data-target="#user_asset_detail_modal" tabindex="0">'+ cellData +'</a>'
|
||||
$(td).html(detail_btn.replace("rowData_id", rowData.id));
|
||||
}},
|
||||
{targets: 3, createdCell: function (td, cellData) {
|
||||
if (!cellData) {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
@@ -98,38 +102,18 @@ function initTable() {
|
||||
{data: "system_users_granted", orderable: false}
|
||||
]
|
||||
};
|
||||
asset_table = jumpserver.initDataTable(options);
|
||||
asset_table = jumpserver.initServerSideDataTable(options);
|
||||
return asset_table
|
||||
}
|
||||
|
||||
function onSelected(event, treeNode) {
|
||||
console.log("select");
|
||||
url = '{% url "api-perms:my-node-assets" node_id=DEFAULT_PK %}';
|
||||
url = url.replace("{{ DEFAULT_PK }}", treeNode.id);
|
||||
initTable();
|
||||
url = url.replace("{{ DEFAULT_PK }}", treeNode.node_id);
|
||||
setCookie('node_selected', treeNode.id);
|
||||
asset_table.ajax.url(url);
|
||||
asset_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]);
|
||||
}
|
||||
}
|
||||
|
||||
function initTree() {
|
||||
var setting = {
|
||||
view: {
|
||||
@@ -149,23 +133,71 @@ function initTree() {
|
||||
var zNodes = [];
|
||||
$.get("{% url 'api-perms:my-nodes' %}", function(data, status){
|
||||
$.each(data, function (index, value) {
|
||||
value["pId"] = value["parent"];
|
||||
if (value["key"] === "0") {
|
||||
value["open"] = true;
|
||||
value["node_id"] = value["id"];
|
||||
value["id"] = value["tree_id"];
|
||||
if (value["tree_id"] !== value["tree_parent"]) {
|
||||
value["pId"] = value["tree_parent"];
|
||||
}
|
||||
value["name"] = value["value"]
|
||||
value["isParent"] = value["is_node"];
|
||||
value['name'] = value['value'];
|
||||
});
|
||||
zNodes = data;
|
||||
$.fn.zTree.init($("#assetTree"), setting, zNodes);
|
||||
zTree = $.fn.zTree.getZTreeObj("assetTree");
|
||||
rMenu = $("#rMenu");
|
||||
selectQueryNode();
|
||||
var root = zTree.getNodes()[0];
|
||||
zTree.expandNode(root);
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
initTree();
|
||||
initTable();
|
||||
})
|
||||
.on('click', '.asset_detail', function() {
|
||||
var data = asset_table.ajax.json();
|
||||
var asset_id = this.getAttribute("asset-id");
|
||||
var trs = '';
|
||||
var desc = {
|
||||
'hostname': "{% trans 'Hostname' %}",
|
||||
'ip': "{% trans 'IP' %}",
|
||||
'port': "{% trans 'Port' %}",
|
||||
'protocol': "{% trans 'Protocol' %}",
|
||||
'platform': "{% trans 'Platform' %}",
|
||||
'os': "{% trans 'OS' %}",
|
||||
'system_users_join': "{% trans 'System user' %}",
|
||||
'domain': "{% trans 'Domain' %}",
|
||||
'is_active': "{% trans 'Is active' %}",
|
||||
'comment': "{% trans 'Comment' %}"
|
||||
{#'date_joined': "{% trans 'Date joined' %}",#}
|
||||
};
|
||||
$.each(data, function(index, value){
|
||||
if(value.id === asset_id){
|
||||
for(var i in desc){
|
||||
trs += "<tr class='no-borders-tr'>\n" +
|
||||
"<td>"+ desc[i] + ":</td>"+
|
||||
"<td><b>"+ (value[i] === null?'':value[i]) + "</b></td>\n" +
|
||||
"</tr>";
|
||||
}
|
||||
}
|
||||
});
|
||||
$('#asset_detail_tbody').html(trs)
|
||||
});
|
||||
|
||||
function toggle() {
|
||||
if (show === 0) {
|
||||
$("#split-left").hide(500, function () {
|
||||
$("#split-right").attr("class", "col-lg-12");
|
||||
$("#toggle-icon").attr("class", "fa fa-angle-right fa-x");
|
||||
show = 1;
|
||||
});
|
||||
} else {
|
||||
$("#split-right").attr("class", "col-lg-9");
|
||||
$("#toggle-icon").attr("class", "fa fa-angle-left fa-x");
|
||||
$("#split-left").show(500);
|
||||
show = 0;
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,59 +1,79 @@
|
||||
# coding:utf-8
|
||||
from django.conf.urls import url
|
||||
from .. import api
|
||||
from django.urls import path
|
||||
from rest_framework_nested import routers
|
||||
# from rest_framework.routers import DefaultRouter
|
||||
from rest_framework_bulk.routes import BulkRouter
|
||||
|
||||
from .. import api
|
||||
|
||||
app_name = 'assets'
|
||||
|
||||
|
||||
router = BulkRouter()
|
||||
router.register(r'v1/assets', api.AssetViewSet, 'asset')
|
||||
router.register(r'v1/admin-user', api.AdminUserViewSet, 'admin-user')
|
||||
router.register(r'v1/system-user', api.SystemUserViewSet, 'system-user')
|
||||
router.register(r'v1/labels', api.LabelViewSet, 'label')
|
||||
router.register(r'v1/nodes', api.NodeViewSet, 'node')
|
||||
router.register(r'v1/domain', api.DomainViewSet, 'domain')
|
||||
router.register(r'v1/gateway', api.GatewayViewSet, 'gateway')
|
||||
router.register(r'assets', api.AssetViewSet, 'asset')
|
||||
router.register(r'admin-user', api.AdminUserViewSet, 'admin-user')
|
||||
router.register(r'system-user', api.SystemUserViewSet, 'system-user')
|
||||
router.register(r'labels', api.LabelViewSet, 'label')
|
||||
router.register(r'nodes', api.NodeViewSet, 'node')
|
||||
router.register(r'domain', api.DomainViewSet, 'domain')
|
||||
router.register(r'gateway', api.GatewayViewSet, 'gateway')
|
||||
router.register(r'cmd-filter', api.CommandFilterViewSet, 'cmd-filter')
|
||||
|
||||
cmd_filter_router = routers.NestedDefaultRouter(router, r'cmd-filter', lookup='filter')
|
||||
cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-rule')
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^v1/assets-bulk/$', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'),
|
||||
url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/auth-info/', api.SystemUserAuthInfoApi.as_view(),
|
||||
name='system-user-auth-info'),
|
||||
url(r'^v1/assets/(?P<pk>[0-9a-zA-Z\-]{36})/refresh/$',
|
||||
api.AssetRefreshHardwareApi.as_view(), name='asset-refresh'),
|
||||
url(r'^v1/assets/(?P<pk>[0-9a-zA-Z\-]{36})/alive/$',
|
||||
api.AssetAdminUserTestApi.as_view(), name='asset-alive-test'),
|
||||
url(r'^v1/admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/$',
|
||||
api.ReplaceNodesAdminUserApi.as_view(), name='replace-nodes-admin-user'),
|
||||
url(r'^v1/admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/auth/$',
|
||||
api.AdminUserAuthApi.as_view(), name='admin-user-auth'),
|
||||
url(r'^v1/admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/connective/$',
|
||||
api.AdminUserTestConnectiveApi.as_view(), name='admin-user-connective'),
|
||||
url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/push/$',
|
||||
api.SystemUserPushApi.as_view(), name='system-user-push'),
|
||||
url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/connective/$',
|
||||
api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'),
|
||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/children/$',
|
||||
api.NodeChildrenApi.as_view(), name='node-children'),
|
||||
url(r'^v1/nodes/children/$', api.NodeChildrenApi.as_view(), name='node-children-2'),
|
||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/children/add/$',
|
||||
api.NodeAddChildrenApi.as_view(), name='node-add-children'),
|
||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$',
|
||||
api.NodeAssetsApi.as_view(), name='node-assets'),
|
||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/add/$',
|
||||
api.NodeAddAssetsApi.as_view(), name='node-add-assets'),
|
||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/replace/$',
|
||||
api.NodeReplaceAssetsApi.as_view(), name='node-replace-assets'),
|
||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/remove/$',
|
||||
api.NodeRemoveAssetsApi.as_view(), name='node-remove-assets'),
|
||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/refresh-hardware-info/$',
|
||||
api.RefreshNodeHardwareInfoApi.as_view(), name='node-refresh-hardware-info'),
|
||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/test-connective/$',
|
||||
api.TestNodeConnectiveApi.as_view(), name='node-test-connective'),
|
||||
path('assets-bulk/', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'),
|
||||
path('system-user/<uuid:pk>/auth-info/',
|
||||
api.SystemUserAuthInfoApi.as_view(), name='system-user-auth-info'),
|
||||
path('system-user/<uuid:pk>/assets/',
|
||||
api.SystemUserAssetsListView.as_view(), name='system-user-assets'),
|
||||
path('assets/<uuid:pk>/refresh/',
|
||||
api.AssetRefreshHardwareApi.as_view(), name='asset-refresh'),
|
||||
path('assets/<uuid:pk>/alive/',
|
||||
api.AssetAdminUserTestApi.as_view(), name='asset-alive-test'),
|
||||
path('assets/<uuid:pk>/gateway/',
|
||||
api.AssetGatewayApi.as_view(), name='asset-gateway'),
|
||||
path('admin-user/<uuid:pk>/nodes/',
|
||||
api.ReplaceNodesAdminUserApi.as_view(), name='replace-nodes-admin-user'),
|
||||
path('admin-user/<uuid:pk>/auth/',
|
||||
api.AdminUserAuthApi.as_view(), name='admin-user-auth'),
|
||||
path('admin-user/<uuid:pk>/connective/',
|
||||
api.AdminUserTestConnectiveApi.as_view(), name='admin-user-connective'),
|
||||
|
||||
url(r'^v1/gateway/(?P<pk>[0-9a-zA-Z\-]{36})/test-connective/$',
|
||||
api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'),
|
||||
path('system-user/<uuid:pk>/push/',
|
||||
api.SystemUserPushApi.as_view(), name='system-user-push'),
|
||||
path('system-user/<uuid:pk>/asset/<uuid:aid>/push/',
|
||||
api.SystemUserPushToAssetApi.as_view(), name='system-user-push-to-asset'),
|
||||
path('system-user/<uuid:pk>/asset/<uuid:aid>/test/',
|
||||
api.SystemUserTestAssetConnectabilityApi.as_view(), name='system-user-test-to-asset'),
|
||||
path('system-user/<uuid:pk>/connective/',
|
||||
api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'),
|
||||
|
||||
path('system-user/<uuid:pk>/cmd-filter-rules/',
|
||||
api.SystemUserCommandFilterRuleListApi.as_view(), name='system-user-cmd-filter-rule-list'),
|
||||
|
||||
path('nodes/<uuid:pk>/children/',
|
||||
api.NodeChildrenApi.as_view(), name='node-children'),
|
||||
path('nodes/children/', api.NodeChildrenApi.as_view(), name='node-children-2'),
|
||||
path('nodes/<uuid:pk>/children/add/',
|
||||
api.NodeAddChildrenApi.as_view(), name='node-add-children'),
|
||||
path('nodes/<uuid:pk>/assets/',
|
||||
api.NodeAssetsApi.as_view(), name='node-assets'),
|
||||
path('nodes/<uuid:pk>/assets/add/',
|
||||
api.NodeAddAssetsApi.as_view(), name='node-add-assets'),
|
||||
path('nodes/<uuid:pk>/assets/replace/',
|
||||
api.NodeReplaceAssetsApi.as_view(), name='node-replace-assets'),
|
||||
path('nodes/<uuid:pk>/assets/remove/',
|
||||
api.NodeRemoveAssetsApi.as_view(), name='node-remove-assets'),
|
||||
path('nodes/<uuid:pk>/refresh-hardware-info/',
|
||||
api.RefreshNodeHardwareInfoApi.as_view(), name='node-refresh-hardware-info'),
|
||||
path('nodes/<uuid:pk>/test-connective/',
|
||||
api.TestNodeConnectiveApi.as_view(), name='node-test-connective'),
|
||||
|
||||
path('gateway/<uuid:pk>/test-connective/',
|
||||
api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'),
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
||||
urlpatterns += router.urls + cmd_filter_router.urls
|
||||
|
||||
|
||||
@@ -1,53 +1,60 @@
|
||||
# coding:utf-8
|
||||
from django.conf.urls import url
|
||||
from django.urls import path
|
||||
from .. import views
|
||||
|
||||
app_name = 'assets'
|
||||
|
||||
urlpatterns = [
|
||||
# Resource asset url
|
||||
url(r'^$', views.AssetListView.as_view(), name='asset-index'),
|
||||
url(r'^asset/$', views.AssetListView.as_view(), name='asset-list'),
|
||||
url(r'^asset/create/$', views.AssetCreateView.as_view(), name='asset-create'),
|
||||
url(r'^asset/export/$', views.AssetExportView.as_view(), name='asset-export'),
|
||||
url(r'^asset/import/$', views.BulkImportAssetView.as_view(), name='asset-import'),
|
||||
url(r'^asset/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.AssetDetailView.as_view(), name='asset-detail'),
|
||||
url(r'^asset/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.AssetUpdateView.as_view(), name='asset-update'),
|
||||
url(r'^asset/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.AssetDeleteView.as_view(), name='asset-delete'),
|
||||
url(r'^asset/update/$', views.AssetBulkUpdateView.as_view(), name='asset-bulk-update'),
|
||||
path('', views.AssetListView.as_view(), name='asset-index'),
|
||||
path('asset/', views.AssetListView.as_view(), name='asset-list'),
|
||||
path('asset/create/', views.AssetCreateView.as_view(), name='asset-create'),
|
||||
path('asset/export/', views.AssetExportView.as_view(), name='asset-export'),
|
||||
path('asset/import/', views.BulkImportAssetView.as_view(), name='asset-import'),
|
||||
path('asset/<uuid:pk>/', views.AssetDetailView.as_view(), name='asset-detail'),
|
||||
path('asset/<uuid:pk>/update/', views.AssetUpdateView.as_view(), name='asset-update'),
|
||||
path('asset/<uuid:pk>/delete/', views.AssetDeleteView.as_view(), name='asset-delete'),
|
||||
path('asset/update/', views.AssetBulkUpdateView.as_view(), name='asset-bulk-update'),
|
||||
|
||||
# User asset view
|
||||
url(r'^user-asset/$', views.UserAssetListView.as_view(), name='user-asset-list'),
|
||||
path('user-asset/', views.UserAssetListView.as_view(), name='user-asset-list'),
|
||||
|
||||
# Resource admin user url
|
||||
url(r'^admin-user/$', views.AdminUserListView.as_view(), name='admin-user-list'),
|
||||
url(r'^admin-user/create/$', views.AdminUserCreateView.as_view(), name='admin-user-create'),
|
||||
url(r'^admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.AdminUserDetailView.as_view(), name='admin-user-detail'),
|
||||
url(r'^admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.AdminUserUpdateView.as_view(), name='admin-user-update'),
|
||||
url(r'^admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.AdminUserDeleteView.as_view(), name='admin-user-delete'),
|
||||
url(r'^admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$', views.AdminUserAssetsView.as_view(), name='admin-user-assets'),
|
||||
path('admin-user/', views.AdminUserListView.as_view(), name='admin-user-list'),
|
||||
path('admin-user/create/', views.AdminUserCreateView.as_view(), name='admin-user-create'),
|
||||
path('admin-user/<uuid:pk>/', views.AdminUserDetailView.as_view(), name='admin-user-detail'),
|
||||
path('admin-user/<uuid:pk>/update/', views.AdminUserUpdateView.as_view(), name='admin-user-update'),
|
||||
path('admin-user/<uuid:pk>/delete/', views.AdminUserDeleteView.as_view(), name='admin-user-delete'),
|
||||
path('admin-user/<uuid:pk>/assets/', views.AdminUserAssetsView.as_view(), name='admin-user-assets'),
|
||||
|
||||
# Resource system user url
|
||||
url(r'^system-user/$', views.SystemUserListView.as_view(), name='system-user-list'),
|
||||
url(r'^system-user/create/$', views.SystemUserCreateView.as_view(), name='system-user-create'),
|
||||
url(r'^system-user/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.SystemUserDetailView.as_view(), name='system-user-detail'),
|
||||
url(r'^system-user/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.SystemUserUpdateView.as_view(), name='system-user-update'),
|
||||
url(r'^system-user/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.SystemUserDeleteView.as_view(), name='system-user-delete'),
|
||||
url(r'^system-user/(?P<pk>[0-9a-zA-Z\-]{36})/asset/$', views.SystemUserAssetView.as_view(), name='system-user-asset'),
|
||||
path('system-user/', views.SystemUserListView.as_view(), name='system-user-list'),
|
||||
path('system-user/create/', views.SystemUserCreateView.as_view(), name='system-user-create'),
|
||||
path('system-user/<uuid:pk>/', views.SystemUserDetailView.as_view(), name='system-user-detail'),
|
||||
path('system-user/<uuid:pk>/update/', views.SystemUserUpdateView.as_view(), name='system-user-update'),
|
||||
path('system-user/<uuid:pk>/delete/', views.SystemUserDeleteView.as_view(), name='system-user-delete'),
|
||||
path('system-user/<uuid:pk>/asset/', views.SystemUserAssetView.as_view(), name='system-user-asset'),
|
||||
|
||||
url(r'^label/$', views.LabelListView.as_view(), name='label-list'),
|
||||
url(r'^label/create/$', views.LabelCreateView.as_view(), name='label-create'),
|
||||
url(r'^label/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.LabelUpdateView.as_view(), name='label-update'),
|
||||
url(r'^label/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.LabelDeleteView.as_view(), name='label-delete'),
|
||||
path('label/', views.LabelListView.as_view(), name='label-list'),
|
||||
path('label/create/', views.LabelCreateView.as_view(), name='label-create'),
|
||||
path('label/<uuid:pk>/update/', views.LabelUpdateView.as_view(), name='label-update'),
|
||||
path('label/<uuid:pk>/delete/', views.LabelDeleteView.as_view(), name='label-delete'),
|
||||
|
||||
url(r'^domain/$', views.DomainListView.as_view(), name='domain-list'),
|
||||
url(r'^domain/create/$', views.DomainCreateView.as_view(), name='domain-create'),
|
||||
url(r'^domain/(?P<pk>[0-9a-zA-Z\-]{36})/$', views.DomainDetailView.as_view(), name='domain-detail'),
|
||||
url(r'^domain/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.DomainUpdateView.as_view(), name='domain-update'),
|
||||
url(r'^domain/(?P<pk>[0-9a-zA-Z\-]{36})/delete/$', views.DomainDeleteView.as_view(), name='domain-delete'),
|
||||
url(r'^domain/(?P<pk>[0-9a-zA-Z\-]{36})/gateway/$', views.DomainGatewayListView.as_view(), name='domain-gateway-list'),
|
||||
path('domain/', views.DomainListView.as_view(), name='domain-list'),
|
||||
path('domain/create/', views.DomainCreateView.as_view(), name='domain-create'),
|
||||
path('domain/<uuid:pk>/', views.DomainDetailView.as_view(), name='domain-detail'),
|
||||
path('domain/<uuid:pk>/update/', views.DomainUpdateView.as_view(), name='domain-update'),
|
||||
path('domain/<uuid:pk>/delete/', views.DomainDeleteView.as_view(), name='domain-delete'),
|
||||
path('domain/<uuid:pk>/gateway/', views.DomainGatewayListView.as_view(), name='domain-gateway-list'),
|
||||
|
||||
url(r'^domain/(?P<pk>[0-9a-zA-Z\-]{36})/gateway/create/$', views.DomainGatewayCreateView.as_view(), name='domain-gateway-create'),
|
||||
url(r'^domain/gateway/(?P<pk>[0-9a-zA-Z\-]{36})/update/$', views.DomainGatewayUpdateView.as_view(), name='domain-gateway-update'),
|
||||
path('domain/<uuid:pk>/gateway/create/', views.DomainGatewayCreateView.as_view(), name='domain-gateway-create'),
|
||||
path('domain/gateway/<uuid:pk>/update/', views.DomainGatewayUpdateView.as_view(), name='domain-gateway-update'),
|
||||
|
||||
path('cmd-filter/', views.CommandFilterListView.as_view(), name='cmd-filter-list'),
|
||||
path('cmd-filter/create/', views.CommandFilterCreateView.as_view(), name='cmd-filter-create'),
|
||||
path('cmd-filter/<uuid:pk>/update/', views.CommandFilterUpdateView.as_view(), name='cmd-filter-update'),
|
||||
path('cmd-filter/<uuid:pk>/', views.CommandFilterDetailView.as_view(), name='cmd-filter-detail'),
|
||||
path('cmd-filter/<uuid:pk>/rule/', views.CommandFilterRuleListView.as_view(), name='cmd-filter-rule-list'),
|
||||
path('cmd-filter/<uuid:filter_pk>/rule/create/', views.CommandFilterRuleCreateView.as_view(), name='cmd-filter-rule-create'),
|
||||
path('cmd-filter/<uuid:filter_pk>/rule/<uuid:pk>/update/', views.CommandFilterRuleUpdateView.as_view(), name='cmd-filter-rule-update'),
|
||||
]
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ def get_assets_by_id_list(id_list):
|
||||
return Asset.objects.filter(id__in=id_list)
|
||||
|
||||
|
||||
def get_assets_by_hostname_list(hostname_list):
|
||||
return Asset.objects.filter(hostname__in=hostname_list)
|
||||
def get_assets_by_fullname_list(hostname_list):
|
||||
return Asset.get_queryset_by_fullname_list(hostname_list)
|
||||
|
||||
|
||||
def get_system_user_by_name(name):
|
||||
@@ -54,7 +54,8 @@ def test_gateway_connectability(gateway):
|
||||
proxy.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
|
||||
try:
|
||||
proxy.connect(gateway.ip, username=gateway.username,
|
||||
proxy.connect(gateway.ip, gateway.port,
|
||||
username=gateway.username,
|
||||
password=gateway.password,
|
||||
pkey=gateway.private_key_obj)
|
||||
except(paramiko.AuthenticationException,
|
||||
|
||||
@@ -4,3 +4,4 @@ from .system_user import *
|
||||
from .admin_user import *
|
||||
from .label import *
|
||||
from .domain import *
|
||||
from .cmd_filter import *
|
||||
|
||||
@@ -11,7 +11,7 @@ from django.views.generic.detail import DetailView, SingleObjectMixin
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from .. import forms
|
||||
from ..models import AdminUser, Node
|
||||
from ..hands import AdminUserRequiredMixin
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
|
||||
__all__ = [
|
||||
'AdminUserCreateView', 'AdminUserDetailView',
|
||||
|
||||
@@ -8,8 +8,8 @@ import codecs
|
||||
import chardet
|
||||
from io import StringIO
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import transaction
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import TemplateView, ListView, View
|
||||
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
|
||||
@@ -25,11 +25,12 @@ from django.shortcuts import redirect
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
|
||||
from common.mixins import JSONResponseMixin
|
||||
from common.utils import get_object_or_none, get_logger, is_uuid
|
||||
from common.utils import get_object_or_none, get_logger
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from orgs.utils import current_org
|
||||
from .. import forms
|
||||
from ..models import Asset, AdminUser, SystemUser, Label, Node, Domain
|
||||
from ..hands import AdminUserRequiredMixin
|
||||
|
||||
|
||||
__all__ = [
|
||||
@@ -73,14 +74,6 @@ class AssetCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
template_name = 'assets/asset_create.html'
|
||||
success_url = reverse_lazy('assets:asset-list')
|
||||
|
||||
# def form_valid(self, form):
|
||||
# print("form valid")
|
||||
# asset = form.save()
|
||||
# asset.created_by = self.request.user.username or 'Admin'
|
||||
# asset.date_created = timezone.now()
|
||||
# asset.save()
|
||||
# return super().form_valid(form)
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
form = super().get_form(form_class=form_class)
|
||||
node_id = self.request.GET.get("node_id")
|
||||
@@ -103,29 +96,12 @@ class AssetCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
return create_success_msg % ({"name": cleaned_data["hostname"]})
|
||||
|
||||
|
||||
# class AssetModalListView(AdminUserRequiredMixin, ListView):
|
||||
# paginate_by = settings.DISPLAY_PER_PAGE
|
||||
# model = Asset
|
||||
# context_object_name = 'asset_modal_list'
|
||||
# template_name = 'assets/_asset_list_modal.html'
|
||||
#
|
||||
# def get_context_data(self, **kwargs):
|
||||
# assets = Asset.objects.all()
|
||||
# assets_id = self.request.GET.get('assets_id', '')
|
||||
# assets_id_list = [i for i in assets_id.split(',') if i.isdigit()]
|
||||
# context = {
|
||||
# 'all_assets': assets_id_list,
|
||||
# 'assets': assets
|
||||
# }
|
||||
# kwargs.update(context)
|
||||
# return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AssetBulkUpdateView(AdminUserRequiredMixin, ListView):
|
||||
model = Asset
|
||||
form_class = forms.AssetBulkUpdateForm
|
||||
template_name = 'assets/asset_bulk_update.html'
|
||||
success_url = reverse_lazy('assets:asset-list')
|
||||
success_message = _("Bulk update asset success")
|
||||
id_list = None
|
||||
form = None
|
||||
|
||||
@@ -147,6 +123,7 @@ class AssetBulkUpdateView(AdminUserRequiredMixin, ListView):
|
||||
form = self.form_class(request.POST)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, self.success_message)
|
||||
return redirect(self.success_url)
|
||||
else:
|
||||
return self.get(request, form=form, *args, **kwargs)
|
||||
@@ -186,7 +163,7 @@ class AssetDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||
success_url = reverse_lazy('assets:asset-list')
|
||||
|
||||
|
||||
class AssetDetailView(DetailView):
|
||||
class AssetDetailView(LoginRequiredMixin, DetailView):
|
||||
model = Asset
|
||||
context_object_name = 'asset'
|
||||
template_name = 'assets/asset_detail.html'
|
||||
@@ -203,7 +180,7 @@ class AssetDetailView(DetailView):
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class AssetExportView(View):
|
||||
class AssetExportView(LoginRequiredMixin, View):
|
||||
def get(self, request):
|
||||
spm = request.GET.get('spm', '')
|
||||
assets_id_default = [Asset.objects.first().id] if Asset.objects.first() else []
|
||||
@@ -211,7 +188,7 @@ class AssetExportView(View):
|
||||
fields = [
|
||||
field for field in Asset._meta.fields
|
||||
if field.name not in [
|
||||
'date_created'
|
||||
'date_created', 'org_id'
|
||||
]
|
||||
]
|
||||
filename = 'assets-{}.csv'.format(
|
||||
@@ -234,13 +211,13 @@ class AssetExportView(View):
|
||||
def post(self, request, *args, **kwargs):
|
||||
try:
|
||||
assets_id = json.loads(request.body).get('assets_id', [])
|
||||
assets_node_id = json.loads(request.body).get('node_id', None)
|
||||
node_id = json.loads(request.body).get('node_id', None)
|
||||
except ValueError:
|
||||
return HttpResponse('Json object not valid', status=400)
|
||||
|
||||
if not assets_id and assets_node_id:
|
||||
assets_node = get_object_or_none(Node, id=assets_node_id)
|
||||
assets = assets_node.get_all_assets()
|
||||
if not assets_id:
|
||||
node = get_object_or_none(Node, id=node_id) if node_id else Node.root()
|
||||
assets = node.get_all_assets()
|
||||
for asset in assets:
|
||||
assets_id.append(asset.id)
|
||||
|
||||
|
||||
168
apps/assets/views/cmd_filter.py
Normal file
168
apps/assets/views/cmd_filter.py
Normal file
@@ -0,0 +1,168 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.views.generic import TemplateView, CreateView, \
|
||||
UpdateView, DeleteView, DetailView
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.urls import reverse_lazy
|
||||
from django.shortcuts import get_object_or_404, reverse
|
||||
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from ..models import CommandFilter, CommandFilterRule, SystemUser
|
||||
from ..forms import CommandFilterForm, CommandFilterRuleForm
|
||||
|
||||
|
||||
__all__ = (
|
||||
"CommandFilterListView", "CommandFilterCreateView",
|
||||
"CommandFilterUpdateView",
|
||||
"CommandFilterRuleListView", "CommandFilterRuleCreateView",
|
||||
"CommandFilterRuleUpdateView", "CommandFilterDetailView",
|
||||
)
|
||||
|
||||
|
||||
class CommandFilterListView(AdminUserRequiredMixin, TemplateView):
|
||||
template_name = 'assets/cmd_filter_list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Command filter list'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class CommandFilterCreateView(AdminUserRequiredMixin, CreateView):
|
||||
model = CommandFilter
|
||||
template_name = 'assets/cmd_filter_create_update.html'
|
||||
form_class = CommandFilterForm
|
||||
success_url = reverse_lazy('assets:cmd-filter-list')
|
||||
success_message = create_success_msg
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Create command filter'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class CommandFilterUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
model = CommandFilter
|
||||
template_name = 'assets/cmd_filter_create_update.html'
|
||||
form_class = CommandFilterForm
|
||||
success_url = reverse_lazy('assets:cmd-filter-list')
|
||||
success_message = update_success_msg
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Update command filter'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class CommandFilterDetailView(AdminUserRequiredMixin, DetailView):
|
||||
model = CommandFilter
|
||||
template_name = 'assets/cmd_filter_detail.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
system_users_remain = SystemUser.objects\
|
||||
.exclude(cmd_filters=self.object)\
|
||||
.exclude(protocol='rdp')
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Command filter detail'),
|
||||
'system_users_remain': system_users_remain
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class CommandFilterRuleListView(AdminUserRequiredMixin, SingleObjectMixin, TemplateView):
|
||||
template_name = 'assets/cmd_filter_rule_list.html'
|
||||
model = CommandFilter
|
||||
object = None
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(queryset=self.model.objects.all())
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Command filter rule list'),
|
||||
'object': self.get_object()
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class CommandFilterRuleCreateView(AdminUserRequiredMixin, CreateView):
|
||||
template_name = 'assets/cmd_filter_rule_create_update.html'
|
||||
model = CommandFilterRule
|
||||
form_class = CommandFilterRuleForm
|
||||
success_message = create_success_msg
|
||||
cmd_filter = None
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('assets:cmd-filter-rule-list', kwargs={
|
||||
'pk': self.cmd_filter.id
|
||||
})
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
form = super().get_form(form_class=form_class)
|
||||
form['filter'].initial = self.cmd_filter
|
||||
form['filter'].field.widget.attrs['readonly'] = 1
|
||||
return form
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
filter_pk = self.kwargs.get('filter_pk')
|
||||
self.cmd_filter = get_object_or_404(CommandFilter, pk=filter_pk)
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Create command filter rule'),
|
||||
'object': self.cmd_filter,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class CommandFilterRuleUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
template_name = 'assets/cmd_filter_rule_create_update.html'
|
||||
model = CommandFilterRule
|
||||
form_class = CommandFilterRuleForm
|
||||
success_message = create_success_msg
|
||||
cmd_filter = None
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('assets:cmd-filter-rule-list', kwargs={
|
||||
'pk': self.cmd_filter.id
|
||||
})
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
form = super().get_form(form_class=form_class)
|
||||
form['filter'].initial = self.cmd_filter
|
||||
form['filter'].field.widget.attrs['readonly'] = 1
|
||||
return form
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
filter_pk = self.kwargs.get('filter_pk')
|
||||
self.cmd_filter = get_object_or_404(CommandFilter, pk=filter_pk)
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Update command filter rule'),
|
||||
'object': self.cmd_filter,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
@@ -7,7 +7,7 @@ from django.views.generic.detail import SingleObjectMixin
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.urls import reverse_lazy, reverse
|
||||
|
||||
from common.mixins import AdminUserRequiredMixin
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from common.utils import get_object_or_none
|
||||
from ..models import Domain, Gateway
|
||||
@@ -140,11 +140,6 @@ class DomainGatewayUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
domain = self.object.domain
|
||||
return reverse('assets:domain-gateway-list', kwargs={"pk": domain.id})
|
||||
|
||||
def form_valid(self, form):
|
||||
response = super().form_valid(form)
|
||||
print(form.cleaned_data)
|
||||
return response
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
|
||||
@@ -6,7 +6,7 @@ from django.views.generic import TemplateView, CreateView, \
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
from common.mixins import AdminUserRequiredMixin
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from ..models import Label
|
||||
from ..forms import LabelForm
|
||||
@@ -36,6 +36,7 @@ class LabelCreateView(AdminUserRequiredMixin, CreateView):
|
||||
form_class = LabelForm
|
||||
success_url = reverse_lazy('assets:label-list')
|
||||
success_message = create_success_msg
|
||||
disable_name = ['draw', 'search', 'limit', 'offset', '_']
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
@@ -45,6 +46,16 @@ class LabelCreateView(AdminUserRequiredMixin, CreateView):
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def form_valid(self, form):
|
||||
name = form.cleaned_data.get('name')
|
||||
if name in self.disable_name:
|
||||
msg = _(
|
||||
'Tips: Avoid using label names reserved internally: {}'
|
||||
).format(', '.join(self.disable_name))
|
||||
form.add_error("name", msg)
|
||||
return self.form_invalid(form)
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class LabelUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
model = Label
|
||||
|
||||
@@ -9,8 +9,8 @@ from django.views.generic.detail import DetailView
|
||||
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from ..forms import SystemUserForm
|
||||
from ..models import SystemUser, Node
|
||||
from ..hands import AdminUserRequiredMixin
|
||||
from ..models import SystemUser, Node, CommandFilter
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
|
||||
|
||||
__all__ = [
|
||||
@@ -73,7 +73,7 @@ class SystemUserDetailView(AdminUserRequiredMixin, DetailView):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('System user detail'),
|
||||
'nodes_remain': Node.objects.exclude(systemuser=self.object)
|
||||
'cmd_filters_remain': CommandFilter.objects.exclude(system_users=self.object)
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
@@ -91,9 +91,11 @@ class SystemUserAssetView(AdminUserRequiredMixin, DetailView):
|
||||
context_object_name = 'system_user'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
nodes_remain = sorted(Node.objects.exclude(systemuser=self.object), reverse=True)
|
||||
context = {
|
||||
'app': _('assets'),
|
||||
'action': _('System user asset'),
|
||||
'nodes_remain': nodes_remain
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
from rest_framework import viewsets
|
||||
|
||||
from common.permissions import IsSuperUserOrAppUser
|
||||
from common.permissions import IsOrgAdminOrAppUser
|
||||
from .models import FTPLog
|
||||
from .serializers import FTPLogSerializer
|
||||
|
||||
@@ -11,4 +11,4 @@ from .serializers import FTPLogSerializer
|
||||
class FTPLogViewSet(viewsets.ModelViewSet):
|
||||
queryset = FTPLog.objects.all()
|
||||
serializer_class = FTPLogSerializer
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
@@ -3,3 +3,6 @@ from django.apps import AppConfig
|
||||
|
||||
class AuditsConfig(AppConfig):
|
||||
name = 'audits'
|
||||
|
||||
def ready(self):
|
||||
from . import signals_handler
|
||||
|
||||
4
apps/audits/hands.py
Normal file
4
apps/audits/hands.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from users.models import LoginLog
|
||||
@@ -3,8 +3,15 @@ import uuid
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins import OrgModelMixin
|
||||
from .hands import LoginLog
|
||||
|
||||
class FTPLog(models.Model):
|
||||
__all__ = [
|
||||
'FTPLog', 'OperateLog', 'PasswordChangeLog', 'UserLoginLog',
|
||||
]
|
||||
|
||||
|
||||
class FTPLog(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
user = models.CharField(max_length=128, verbose_name=_('User'))
|
||||
remote_addr = models.CharField(max_length=15, verbose_name=_("Remote addr"), blank=True, null=True)
|
||||
@@ -14,3 +21,40 @@ class FTPLog(models.Model):
|
||||
filename = models.CharField(max_length=1024, verbose_name=_("Filename"))
|
||||
is_success = models.BooleanField(default=True, verbose_name=_("Success"))
|
||||
date_start = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
|
||||
class OperateLog(OrgModelMixin):
|
||||
ACTION_CREATE = 'create'
|
||||
ACTION_UPDATE = 'update'
|
||||
ACTION_DELETE = 'delete'
|
||||
ACTION_CHOICES = (
|
||||
(ACTION_CREATE, _("Create")),
|
||||
(ACTION_UPDATE, _("Update")),
|
||||
(ACTION_DELETE, _("Delete"))
|
||||
)
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
user = models.CharField(max_length=128, verbose_name=_('User'))
|
||||
action = models.CharField(max_length=16, choices=ACTION_CHOICES, verbose_name=_("Action"))
|
||||
resource_type = models.CharField(max_length=64, verbose_name=_("Resource Type"))
|
||||
resource = models.CharField(max_length=128, verbose_name=_("Resource"))
|
||||
remote_addr = models.CharField(max_length=15, verbose_name=_("Remote addr"), blank=True, null=True)
|
||||
datetime = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return "<{}> {} <{}>".format(self.user, self.action, self.resource)
|
||||
|
||||
|
||||
class PasswordChangeLog(models.Model):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
user = models.CharField(max_length=128, verbose_name=_('User'))
|
||||
change_by = models.CharField(max_length=128, verbose_name=_("Change by"))
|
||||
remote_addr = models.CharField(max_length=15, verbose_name=_("Remote addr"), blank=True, null=True)
|
||||
datetime = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return "{} change {}'s password".format(self.change_by, self.user)
|
||||
|
||||
|
||||
class UserLoginLog(LoginLog):
|
||||
class Meta:
|
||||
proxy = True
|
||||
|
||||
59
apps/audits/signals_handler.py
Normal file
59
apps/audits/signals_handler.py
Normal file
@@ -0,0 +1,59 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.db.models.signals import post_save, post_delete
|
||||
from django.dispatch import receiver
|
||||
from django.db import transaction
|
||||
|
||||
from jumpserver.utils import current_request
|
||||
from common.utils import get_request_ip
|
||||
from users.models import User
|
||||
from .models import OperateLog, PasswordChangeLog
|
||||
|
||||
|
||||
MODELS_NEED_RECORD = (
|
||||
'User', 'UserGroup', 'Asset', 'Node', 'AdminUser', 'SystemUser',
|
||||
'Domain', 'Gateway', 'Organization', 'AssetPermission',
|
||||
)
|
||||
|
||||
|
||||
def create_operate_log(action, sender, resource):
|
||||
user = current_request.user if current_request else None
|
||||
if not user or not user.is_authenticated:
|
||||
return
|
||||
model_name = sender._meta.object_name
|
||||
if model_name not in MODELS_NEED_RECORD:
|
||||
return
|
||||
resource_type = sender._meta.verbose_name
|
||||
remote_addr = get_request_ip(current_request)
|
||||
with transaction.atomic():
|
||||
OperateLog.objects.create(
|
||||
user=user, action=action, resource_type=resource_type,
|
||||
resource=resource, remote_addr=remote_addr
|
||||
)
|
||||
|
||||
|
||||
@receiver(post_save, dispatch_uid="my_unique_identifier")
|
||||
def on_object_created_or_update(sender, instance=None, created=False, **kwargs):
|
||||
if created:
|
||||
action = OperateLog.ACTION_CREATE
|
||||
else:
|
||||
action = OperateLog.ACTION_UPDATE
|
||||
create_operate_log(action, sender, instance)
|
||||
|
||||
|
||||
@receiver(post_delete, dispatch_uid="my_unique_identifier")
|
||||
def on_object_delete(sender, instance=None, **kwargs):
|
||||
create_operate_log(OperateLog.ACTION_DELETE, sender, instance)
|
||||
|
||||
|
||||
@receiver(post_save, sender=User, dispatch_uid="my_unique_identifier")
|
||||
def on_user_change_password(sender, instance=None, **kwargs):
|
||||
if hasattr(instance, '_set_password'):
|
||||
if not current_request or not current_request.user.is_authenticated:
|
||||
return
|
||||
with transaction.atomic():
|
||||
PasswordChangeLog.objects.create(
|
||||
user=instance, change_by=current_request.user,
|
||||
remote_addr=get_request_ip(current_request),
|
||||
)
|
||||
@@ -66,7 +66,6 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block table_head %}
|
||||
<th class="text-center"></th>
|
||||
{# <th class="text-center">{% trans 'ID' %}</th>#}
|
||||
<th class="text-center">{% trans 'User' %}</th>
|
||||
<th class="text-center">{% trans 'Asset' %}</th>
|
||||
@@ -82,7 +81,6 @@
|
||||
{% block table_body %}
|
||||
{% for object in object_list %}
|
||||
<tr class="gradeX">
|
||||
<td class="text-center"><input type="checkbox" value="{{ object.id }}"></td>
|
||||
{# <td class="text-center">#}
|
||||
{# <a href="{% url 'terminal:object-detail' pk=object.id %}">{{ forloop.counter }}</a>#}
|
||||
{# </td>#}
|
||||
|
||||
@@ -51,6 +51,9 @@
|
||||
<th class="text-center">{% trans 'UA' %}</th>
|
||||
<th class="text-center">{% trans 'IP' %}</th>
|
||||
<th class="text-center">{% trans 'City' %}</th>
|
||||
<th class="text-center">{% trans 'MFA' %}</th>
|
||||
<th class="text-center">{% trans 'Reason' %}</th>
|
||||
<th class="text-center">{% trans 'Status' %}</th>
|
||||
<th class="text-center">{% trans 'Date' %}</th>
|
||||
{% endblock %}
|
||||
|
||||
@@ -65,6 +68,9 @@
|
||||
</td>
|
||||
<td class="text-center">{{ login_log.ip }}</td>
|
||||
<td class="text-center">{{ login_log.city }}</td>
|
||||
<td class="text-center">{{ login_log.get_mfa_display }}</td>
|
||||
<td class="text-center">{{ login_log.get_reason_display }}</td>
|
||||
<td class="text-center">{{ login_log.get_status_display }}</td>
|
||||
<td class="text-center">{{ login_log.datetime }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@@ -74,12 +80,7 @@
|
||||
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('table').DataTable({
|
||||
"searching": false,
|
||||
"bInfo" : false,
|
||||
"paging": false,
|
||||
"order": []
|
||||
});
|
||||
jumpserver.initStaticTable('table');
|
||||
$('#date .input-daterange').datepicker({
|
||||
format: "yyyy-mm-dd",
|
||||
todayBtn: "linked",
|
||||
119
apps/audits/templates/audits/operate_log_list.html
Normal file
119
apps/audits/templates/audits/operate_log_list.html
Normal file
@@ -0,0 +1,119 @@
|
||||
{% extends '_base_list.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load terminal_tags %}
|
||||
{% load common_tags %}
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static 'css/plugins/datepicker/datepicker3.css' %}" rel="stylesheet">
|
||||
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
|
||||
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
||||
<style>
|
||||
#search_btn {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content_left_head %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block table_search %}
|
||||
<form id="search_form" method="get" action="" class="pull-right form-inline">
|
||||
<div class="form-group" id="date">
|
||||
<div class="input-daterange input-group" id="datepicker">
|
||||
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
|
||||
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_from" value="{{ date_from|date:'Y-m-d' }}">
|
||||
<span class="input-group-addon">to</span>
|
||||
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_to" value="{{ date_to|date:'Y-m-d' }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<select class="select2 form-control" name="user">
|
||||
<option value="">{% trans 'User' %}</option>
|
||||
{% for u in user_list %}
|
||||
<option value="{{ u }}" {% if u == user %} selected {% endif %}>{{ u }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<select class="select2 form-control" name="asset">
|
||||
<option value="">{% trans 'Action' %}</option>
|
||||
{% for k, v in actions.items %}
|
||||
<option value="{{ k }}" {% if k == action %} selected {% endif %}>{{ v }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<select class="select2 form-control" name="system_user">
|
||||
<option value="">{% trans 'Resource Type' %}</option>
|
||||
{% for r in resource_type_list %}
|
||||
<option value="{{ r }}" {% if r == resource_type %} selected {% endif %}>{{ r }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<div class="input-group-btn">
|
||||
<button id='search_btn' type="submit" class="btn btn-sm btn-primary">
|
||||
{% trans 'Search' %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_head %}
|
||||
<th class="text-center">{% trans 'User' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
<th class="text-center">{% trans 'Resource Type' %}</th>
|
||||
<th class="text-center">{% trans 'Resource' %}</th>
|
||||
<th class="text-center">{% trans 'Remote addr' %}</th>
|
||||
<th class="text-center">{% trans 'Datetime' %}</th>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_body %}
|
||||
{% for object in object_list %}
|
||||
<tr class="gradeX">
|
||||
{# <td class="text-center">#}
|
||||
{# <a href="{% url 'terminal:object-detail' pk=object.id %}">{{ forloop.counter }}</a>#}
|
||||
{# </td>#}
|
||||
<td class="text-center">{{ object.user }}</td>
|
||||
<td class="text-center">{{ object.get_action_display }}</td>
|
||||
<td class="text-center">{{ object.resource_type }}</td>
|
||||
<td class="text-center">{{ object.resource }}</td>
|
||||
<td class="text-center">{{ object.remote_addr }}</td>
|
||||
<td class="text-center">{{ object.datetime }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content_bottom_left %}
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('table').DataTable({
|
||||
"searching": false,
|
||||
"paging": false,
|
||||
"bInfo" : false,
|
||||
"order": [],
|
||||
"language": jumpserver.language
|
||||
});
|
||||
$('.select2').select2({
|
||||
dropdownAutoWidth: true,
|
||||
width: "auto"
|
||||
});
|
||||
$('.input-daterange.input-group').datepicker({
|
||||
format: "yyyy-mm-dd",
|
||||
todayBtn: "linked",
|
||||
keyboardNavigation: false,
|
||||
forceParse: false,
|
||||
calendarWeeks: true,
|
||||
autoclose: true
|
||||
});
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
96
apps/audits/templates/audits/password_change_log_list.html
Normal file
96
apps/audits/templates/audits/password_change_log_list.html
Normal file
@@ -0,0 +1,96 @@
|
||||
{% extends '_base_list.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load terminal_tags %}
|
||||
{% load common_tags %}
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static 'css/plugins/datepicker/datepicker3.css' %}" rel="stylesheet">
|
||||
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
|
||||
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
||||
<style>
|
||||
#search_btn {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content_left_head %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block table_search %}
|
||||
<form id="search_form" method="get" action="" class="pull-right form-inline">
|
||||
<div class="form-group" id="date">
|
||||
<div class="input-daterange input-group" id="datepicker">
|
||||
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
|
||||
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_from" value="{{ date_from|date:'Y-m-d' }}">
|
||||
<span class="input-group-addon">to</span>
|
||||
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_to" value="{{ date_to|date:'Y-m-d' }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<select class="select2 form-control" name="user">
|
||||
<option value="">{% trans 'User' %}</option>
|
||||
{% for u in user_list %}
|
||||
<option value="{{ u }}" {% if u == user %} selected {% endif %}>{{ u }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<div class="input-group-btn">
|
||||
<button id='search_btn' type="submit" class="btn btn-sm btn-primary">
|
||||
{% trans 'Search' %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_head %}
|
||||
<th class="text-center">{% trans 'User' %}</th>
|
||||
<th class="text-center">{% trans 'Change by' %}</th>
|
||||
<th class="text-center">{% trans 'Remote addr' %}</th>
|
||||
<th class="text-center">{% trans 'Datetime' %}</th>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_body %}
|
||||
{% for object in object_list %}
|
||||
<tr class="gradeX">
|
||||
<td class="text-center">{{ object.user }}</td>
|
||||
<td class="text-center">{{ object.change_by }}</td>
|
||||
<td class="text-center">{{ object.remote_addr }}</td>
|
||||
<td class="text-center">{{ object.datetime }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content_bottom_left %}
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('table').DataTable({
|
||||
"searching": false,
|
||||
"paging": false,
|
||||
"bInfo" : false,
|
||||
"order": [],
|
||||
"language": jumpserver.language
|
||||
});
|
||||
$('.select2').select2({
|
||||
dropdownAutoWidth: true,
|
||||
width: "auto"
|
||||
});
|
||||
$('.input-daterange.input-group').datepicker({
|
||||
format: "yyyy-mm-dd",
|
||||
todayBtn: "linked",
|
||||
keyboardNavigation: false,
|
||||
forceParse: false,
|
||||
calendarWeeks: true,
|
||||
autoclose: true
|
||||
});
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -9,10 +9,9 @@ from .. import api
|
||||
app_name = "audits"
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r'v1/ftp-log', api.FTPLogViewSet, 'ftp-log')
|
||||
router.register(r'ftp-log', api.FTPLogViewSet, 'ftp-log')
|
||||
|
||||
urlpatterns = [
|
||||
# url(r'^v1/celery/task/(?P<pk>[0-9a-zA-Z\-]{36})/log/$', api.CeleryTaskLogApi.as_view(), name='celery-task-log'),
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
from django.conf.urls import url
|
||||
from django.urls import path
|
||||
from .. import views
|
||||
|
||||
__all__ = ["urlpatterns"]
|
||||
@@ -10,5 +9,8 @@ __all__ = ["urlpatterns"]
|
||||
app_name = "audits"
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^ftp-log/$', views.FTPLogListView.as_view(), name='ftp-log-list'),
|
||||
path('login-log/', views.LoginLogListView.as_view(), name='login-log-list'),
|
||||
path('ftp-log/', views.FTPLogListView.as_view(), name='ftp-log-list'),
|
||||
path('operate-log/', views.OperateLogListView.as_view(), name='operate-log-list'),
|
||||
path('password-change-log/', views.PasswordChangeLogList.as_view(), name='password-change-log-list'),
|
||||
]
|
||||
|
||||
@@ -1,10 +1,26 @@
|
||||
from django.conf import settings
|
||||
from django.views.generic import ListView
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.db.models import Q
|
||||
|
||||
from common.mixins import AdminUserRequiredMixin, DatetimeSearchMixin
|
||||
from common.mixins import DatetimeSearchMixin
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
|
||||
from .models import FTPLog
|
||||
from orgs.utils import current_org
|
||||
from .models import FTPLog, OperateLog, PasswordChangeLog, UserLoginLog
|
||||
|
||||
|
||||
def get_resource_type_list():
|
||||
from users.models import User, UserGroup
|
||||
from assets.models import Asset, Node, AdminUser, SystemUser, Domain, Gateway
|
||||
from orgs.models import Organization
|
||||
from perms.models import AssetPermission
|
||||
|
||||
models = [
|
||||
User, UserGroup, Asset, Node, AdminUser, SystemUser, Domain,
|
||||
Gateway, Organization, AssetPermission
|
||||
]
|
||||
return [model._meta.verbose_name for model in models]
|
||||
|
||||
|
||||
class FTPLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
|
||||
@@ -52,3 +68,129 @@ class FTPLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class OperateLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
|
||||
model = OperateLog
|
||||
template_name = 'audits/operate_log_list.html'
|
||||
paginate_by = settings.DISPLAY_PER_PAGE
|
||||
user = action = resource_type = ''
|
||||
date_from = date_to = None
|
||||
actions_dict = dict(OperateLog.ACTION_CHOICES)
|
||||
|
||||
def get_queryset(self):
|
||||
self.queryset = super().get_queryset()
|
||||
self.user = self.request.GET.get('user')
|
||||
self.action = self.request.GET.get('action')
|
||||
self.resource_type = self.request.GET.get('resource_type')
|
||||
|
||||
filter_kwargs = dict()
|
||||
filter_kwargs['datetime__gt'] = self.date_from
|
||||
filter_kwargs['datetime__lt'] = self.date_to
|
||||
if self.user:
|
||||
filter_kwargs['user'] = self.user
|
||||
if self.action:
|
||||
filter_kwargs['action'] = self.action
|
||||
if self.resource_type:
|
||||
filter_kwargs['resource_type'] = self.resource_type
|
||||
if filter_kwargs:
|
||||
self.queryset = self.queryset.filter(**filter_kwargs).order_by('-datetime')
|
||||
return self.queryset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'user_list': current_org.get_org_users(),
|
||||
'actions': self.actions_dict,
|
||||
'resource_type_list': get_resource_type_list(),
|
||||
'date_from': self.date_from,
|
||||
'date_to': self.date_to,
|
||||
'user': self.user,
|
||||
'action': self.action,
|
||||
'resource_type': self.resource_type,
|
||||
"app": _("Audits"),
|
||||
"action": _("Operate log"),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class PasswordChangeLogList(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
|
||||
model = PasswordChangeLog
|
||||
template_name = 'audits/password_change_log_list.html'
|
||||
paginate_by = settings.DISPLAY_PER_PAGE
|
||||
user = ''
|
||||
date_from = date_to = None
|
||||
|
||||
def get_queryset(self):
|
||||
self.queryset = super().get_queryset()
|
||||
self.user = self.request.GET.get('user')
|
||||
|
||||
filter_kwargs = dict()
|
||||
filter_kwargs['datetime__gt'] = self.date_from
|
||||
filter_kwargs['datetime__lt'] = self.date_to
|
||||
if self.user:
|
||||
filter_kwargs['user'] = self.user
|
||||
if filter_kwargs:
|
||||
self.queryset = self.queryset.filter(**filter_kwargs).order_by('-datetime')
|
||||
return self.queryset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'user_list': current_org.get_org_users(),
|
||||
'date_from': self.date_from,
|
||||
'date_to': self.date_to,
|
||||
'user': self.user,
|
||||
"app": _("Audits"),
|
||||
"action": _("Password change log"),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class LoginLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
|
||||
template_name = 'audits/login_log_list.html'
|
||||
model = UserLoginLog
|
||||
paginate_by = settings.DISPLAY_PER_PAGE
|
||||
user = keyword = ""
|
||||
date_to = date_from = None
|
||||
|
||||
@staticmethod
|
||||
def get_org_users():
|
||||
users = current_org.get_org_users().values_list('username', flat=True)
|
||||
return users
|
||||
|
||||
def get_queryset(self):
|
||||
if current_org.is_default():
|
||||
queryset = super().get_queryset()
|
||||
else:
|
||||
users = self.get_org_users()
|
||||
queryset = super().get_queryset().filter(username__in=users)
|
||||
|
||||
self.user = self.request.GET.get('user', '')
|
||||
self.keyword = self.request.GET.get("keyword", '')
|
||||
|
||||
queryset = queryset.filter(
|
||||
datetime__gt=self.date_from, datetime__lt=self.date_to
|
||||
)
|
||||
if self.user:
|
||||
queryset = queryset.filter(username=self.user)
|
||||
if self.keyword:
|
||||
queryset = queryset.filter(
|
||||
Q(ip__contains=self.keyword) |
|
||||
Q(city__contains=self.keyword) |
|
||||
Q(username__contains=self.keyword)
|
||||
)
|
||||
return queryset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Users'),
|
||||
'action': _('Login log'),
|
||||
'date_from': self.date_from,
|
||||
'date_to': self.date_to,
|
||||
'user': self.user,
|
||||
'keyword': self.keyword,
|
||||
'user_list': self.get_org_users(),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
0
apps/authentication/__init__.py
Normal file
0
apps/authentication/__init__.py
Normal file
1
apps/authentication/admin.py
Normal file
1
apps/authentication/admin.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
10
apps/authentication/apps.py
Normal file
10
apps/authentication/apps.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AuthenticationConfig(AppConfig):
|
||||
name = 'authentication'
|
||||
|
||||
def ready(self):
|
||||
from . import signals_handlers
|
||||
super().ready()
|
||||
|
||||
1
apps/authentication/models.py
Normal file
1
apps/authentication/models.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
20
apps/authentication/openid/__init__.py
Normal file
20
apps/authentication/openid/__init__.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.conf import settings
|
||||
from .models import Client
|
||||
|
||||
|
||||
def new_client():
|
||||
"""
|
||||
:return: authentication.models.Client
|
||||
"""
|
||||
return Client(
|
||||
server_url=settings.AUTH_OPENID_SERVER_URL,
|
||||
realm_name=settings.AUTH_OPENID_REALM_NAME,
|
||||
client_id=settings.AUTH_OPENID_CLIENT_ID,
|
||||
client_secret=settings.AUTH_OPENID_CLIENT_SECRET
|
||||
)
|
||||
|
||||
|
||||
client = new_client()
|
||||
90
apps/authentication/openid/backends.py
Normal file
90
apps/authentication/openid/backends.py
Normal file
@@ -0,0 +1,90 @@
|
||||
# coding:utf-8
|
||||
#
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.conf import settings
|
||||
|
||||
from . import client
|
||||
from common.utils import get_logger
|
||||
from authentication.openid.models import OIDT_ACCESS_TOKEN
|
||||
|
||||
UserModel = get_user_model()
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
BACKEND_OPENID_AUTH_CODE = \
|
||||
'authentication.openid.backends.OpenIDAuthorizationCodeBackend'
|
||||
|
||||
|
||||
class BaseOpenIDAuthorizationBackend(object):
|
||||
|
||||
@staticmethod
|
||||
def user_can_authenticate(user):
|
||||
"""
|
||||
Reject users with is_active=False. Custom user models that don't have
|
||||
that attribute are allowed.
|
||||
"""
|
||||
is_active = getattr(user, 'is_active', None)
|
||||
return is_active or is_active is None
|
||||
|
||||
def get_user(self, user_id):
|
||||
try:
|
||||
user = UserModel._default_manager.get(pk=user_id)
|
||||
except UserModel.DoesNotExist:
|
||||
return None
|
||||
|
||||
return user if self.user_can_authenticate(user) else None
|
||||
|
||||
|
||||
class OpenIDAuthorizationCodeBackend(BaseOpenIDAuthorizationBackend):
|
||||
|
||||
def authenticate(self, request, **kwargs):
|
||||
logger.info('1.openid code backend')
|
||||
|
||||
code = kwargs.get('code')
|
||||
redirect_uri = kwargs.get('redirect_uri')
|
||||
|
||||
if not code or not redirect_uri:
|
||||
return None
|
||||
|
||||
try:
|
||||
oidt_profile = client.update_or_create_from_code(
|
||||
code=code,
|
||||
redirect_uri=redirect_uri
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
|
||||
else:
|
||||
# Check openid user single logout or not with access_token
|
||||
request.session[OIDT_ACCESS_TOKEN] = oidt_profile.access_token
|
||||
|
||||
user = oidt_profile.user
|
||||
|
||||
return user if self.user_can_authenticate(user) else None
|
||||
|
||||
|
||||
class OpenIDAuthorizationPasswordBackend(BaseOpenIDAuthorizationBackend):
|
||||
|
||||
def authenticate(self, request, username=None, password=None, **kwargs):
|
||||
logger.info('2.openid password backend')
|
||||
|
||||
if not settings.AUTH_OPENID:
|
||||
return None
|
||||
|
||||
elif not username:
|
||||
return None
|
||||
|
||||
try:
|
||||
oidt_profile = client.update_or_create_from_password(
|
||||
username=username, password=password
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
|
||||
else:
|
||||
user = oidt_profile.user
|
||||
return user if self.user_can_authenticate(user) else None
|
||||
|
||||
42
apps/authentication/openid/middleware.py
Normal file
42
apps/authentication/openid/middleware.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# coding:utf-8
|
||||
#
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import logout
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
from django.contrib.auth import BACKEND_SESSION_KEY
|
||||
|
||||
from . import client
|
||||
from common.utils import get_logger
|
||||
from .backends import BACKEND_OPENID_AUTH_CODE
|
||||
from authentication.openid.models import OIDT_ACCESS_TOKEN
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
class OpenIDAuthenticationMiddleware(MiddlewareMixin):
|
||||
"""
|
||||
Check openid user single logout (with access_token)
|
||||
"""
|
||||
|
||||
def process_request(self, request):
|
||||
|
||||
# Don't need openid auth if AUTH_OPENID is False
|
||||
if not settings.AUTH_OPENID:
|
||||
return
|
||||
|
||||
# Don't need check single logout if user not authenticated
|
||||
if not request.user.is_authenticated:
|
||||
return
|
||||
|
||||
elif request.session[BACKEND_SESSION_KEY] != BACKEND_OPENID_AUTH_CODE:
|
||||
return
|
||||
|
||||
# Check openid user single logout or not with access_token
|
||||
try:
|
||||
client.openid_connect_client.userinfo(
|
||||
token=request.session.get(OIDT_ACCESS_TOKEN))
|
||||
|
||||
except Exception as e:
|
||||
logout(request)
|
||||
logger.error(e)
|
||||
159
apps/authentication/openid/models.py
Normal file
159
apps/authentication/openid/models.py
Normal file
@@ -0,0 +1,159 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.db import transaction
|
||||
from django.contrib.auth import get_user_model
|
||||
from keycloak.realm import KeycloakRealm
|
||||
from keycloak.keycloak_openid import KeycloakOpenID
|
||||
from ..signals import post_create_openid_user
|
||||
|
||||
OIDT_ACCESS_TOKEN = 'oidt_access_token'
|
||||
|
||||
|
||||
class OpenIDTokenProfile(object):
|
||||
|
||||
def __init__(self, user, access_token, refresh_token):
|
||||
"""
|
||||
:param user: User object
|
||||
:param access_token:
|
||||
:param refresh_token:
|
||||
"""
|
||||
self.user = user
|
||||
self.access_token = access_token
|
||||
self.refresh_token = refresh_token
|
||||
|
||||
def __str__(self):
|
||||
return "{}'s OpenID token profile".format(self.user.username)
|
||||
|
||||
|
||||
class Client(object):
|
||||
|
||||
def __init__(self, server_url, realm_name, client_id, client_secret):
|
||||
self.server_url = server_url
|
||||
self.realm_name = realm_name
|
||||
self.client_id = client_id
|
||||
self.client_secret = client_secret
|
||||
self.realm = self.new_realm()
|
||||
self.openid_client = self.new_openid_client()
|
||||
self.openid_connect_client = self.new_openid_connect_client()
|
||||
|
||||
def new_realm(self):
|
||||
"""
|
||||
:param authentication.openid.models.Realm realm:
|
||||
:return keycloak.realm.Realm:
|
||||
"""
|
||||
return KeycloakRealm(
|
||||
server_url=self.server_url,
|
||||
realm_name=self.realm_name,
|
||||
headers={}
|
||||
)
|
||||
|
||||
def new_openid_connect_client(self):
|
||||
"""
|
||||
:rtype: keycloak.openid_connect.KeycloakOpenidConnect
|
||||
"""
|
||||
openid_connect = self.realm.open_id_connect(
|
||||
client_id=self.client_id,
|
||||
client_secret=self.client_secret
|
||||
)
|
||||
return openid_connect
|
||||
|
||||
def new_openid_client(self):
|
||||
"""
|
||||
:rtype: keycloak.keycloak_openid.KeycloakOpenID
|
||||
"""
|
||||
|
||||
return KeycloakOpenID(
|
||||
server_url='%sauth/' % self.server_url,
|
||||
realm_name=self.realm_name,
|
||||
client_id=self.client_id,
|
||||
client_secret_key=self.client_secret,
|
||||
)
|
||||
|
||||
def update_or_create_from_password(self, username, password):
|
||||
"""
|
||||
Update or create an user based on an authentication username and password.
|
||||
|
||||
:param str username: authentication username
|
||||
:param str password: authentication password
|
||||
:return: authentication.models.OpenIDTokenProfile
|
||||
"""
|
||||
token_response = self.openid_client.token(
|
||||
username=username, password=password
|
||||
)
|
||||
|
||||
return self._update_or_create(token_response=token_response)
|
||||
|
||||
def update_or_create_from_code(self, code, redirect_uri):
|
||||
"""
|
||||
Update or create an user based on an authentication code.
|
||||
Response as specified in:
|
||||
|
||||
https://tools.ietf.org/html/rfc6749#section-4.1.4
|
||||
|
||||
:param str code: authentication code
|
||||
:param str redirect_uri:
|
||||
:rtype: authentication.models.OpenIDTokenProfile
|
||||
"""
|
||||
|
||||
token_response = self.openid_connect_client.authorization_code(
|
||||
code=code, redirect_uri=redirect_uri)
|
||||
|
||||
return self._update_or_create(token_response=token_response)
|
||||
|
||||
def _update_or_create(self, token_response):
|
||||
"""
|
||||
Update or create an user based on a token response.
|
||||
|
||||
`token_response` contains the items returned by the OpenIDConnect Token API
|
||||
end-point:
|
||||
- id_token
|
||||
- access_token
|
||||
- expires_in
|
||||
- refresh_token
|
||||
- refresh_expires_in
|
||||
|
||||
:param dict token_response:
|
||||
:rtype: authentication.openid.models.OpenIDTokenProfile
|
||||
"""
|
||||
|
||||
userinfo = self.openid_connect_client.userinfo(
|
||||
token=token_response['access_token'])
|
||||
|
||||
with transaction.atomic():
|
||||
user, _ = get_user_model().objects.update_or_create(
|
||||
username=userinfo.get('preferred_username', ''),
|
||||
defaults={
|
||||
'email': userinfo.get('email', ''),
|
||||
'first_name': userinfo.get('given_name', ''),
|
||||
'last_name': userinfo.get('family_name', '')
|
||||
}
|
||||
)
|
||||
|
||||
oidt_profile = OpenIDTokenProfile(
|
||||
user=user,
|
||||
access_token=token_response['access_token'],
|
||||
refresh_token=token_response['refresh_token'],
|
||||
)
|
||||
|
||||
if user:
|
||||
post_create_openid_user.send(sender=user.__class__, user=user)
|
||||
|
||||
return oidt_profile
|
||||
|
||||
def __str__(self):
|
||||
return self.client_id
|
||||
|
||||
|
||||
class Nonce(object):
|
||||
"""
|
||||
The openid-login is stored in cache as a temporary object, recording the
|
||||
user's redirect_uri and next_pat
|
||||
"""
|
||||
|
||||
def __init__(self, redirect_uri, next_path):
|
||||
import uuid
|
||||
self.state = uuid.uuid4()
|
||||
self.redirect_uri = redirect_uri
|
||||
self.next_path = next_path
|
||||
|
||||
0
apps/authentication/openid/tests.py
Normal file
0
apps/authentication/openid/tests.py
Normal file
102
apps/authentication/openid/views.py
Normal file
102
apps/authentication/openid/views.py
Normal file
@@ -0,0 +1,102 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
from django.urls import reverse
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.views.generic.base import RedirectView
|
||||
from django.contrib.auth import authenticate, login
|
||||
from django.http.response import (
|
||||
HttpResponseBadRequest,
|
||||
HttpResponseServerError,
|
||||
HttpResponseRedirect
|
||||
)
|
||||
|
||||
from . import client
|
||||
from .models import Nonce
|
||||
from users.models import LoginLog
|
||||
from users.tasks import write_login_log_async
|
||||
from common.utils import get_request_ip
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_base_site_url():
|
||||
return settings.BASE_SITE_URL
|
||||
|
||||
|
||||
class LoginView(RedirectView):
|
||||
|
||||
def get_redirect_url(self, *args, **kwargs):
|
||||
nonce = Nonce(
|
||||
redirect_uri=get_base_site_url() + reverse(
|
||||
"authentication:openid-login-complete"),
|
||||
|
||||
next_path=self.request.GET.get('next')
|
||||
)
|
||||
|
||||
cache.set(str(nonce.state), nonce, 24*3600)
|
||||
|
||||
self.request.session['openid_state'] = str(nonce.state)
|
||||
|
||||
authorization_url = client.openid_connect_client.\
|
||||
authorization_url(
|
||||
redirect_uri=nonce.redirect_uri, scope='code',
|
||||
state=str(nonce.state)
|
||||
)
|
||||
|
||||
return authorization_url
|
||||
|
||||
|
||||
class LoginCompleteView(RedirectView):
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if 'error' in request.GET:
|
||||
return HttpResponseServerError(self.request.GET['error'])
|
||||
|
||||
if 'code' not in self.request.GET and 'state' not in self.request.GET:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
if self.request.GET['state'] != self.request.session['openid_state']:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
nonce = cache.get(self.request.GET['state'])
|
||||
|
||||
if not nonce:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
user = authenticate(
|
||||
request=self.request,
|
||||
code=self.request.GET['code'],
|
||||
redirect_uri=nonce.redirect_uri
|
||||
)
|
||||
|
||||
cache.delete(str(nonce.state))
|
||||
|
||||
if not user:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
login(self.request, user)
|
||||
|
||||
data = {
|
||||
'username': user.username,
|
||||
'mfa': int(user.otp_enabled),
|
||||
'reason': LoginLog.REASON_NOTHING,
|
||||
'status': True
|
||||
}
|
||||
self.write_login_log(data)
|
||||
|
||||
return HttpResponseRedirect(nonce.next_path or '/')
|
||||
|
||||
def write_login_log(self, data):
|
||||
login_ip = get_request_ip(self.request)
|
||||
user_agent = self.request.META.get('HTTP_USER_AGENT', '')
|
||||
tmp_data = {
|
||||
'ip': login_ip,
|
||||
'type': 'W',
|
||||
'user_agent': user_agent
|
||||
}
|
||||
data.update(tmp_data)
|
||||
write_login_log_async.delay(**data)
|
||||
4
apps/authentication/signals.py
Normal file
4
apps/authentication/signals.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from django.dispatch import Signal
|
||||
|
||||
|
||||
post_create_openid_user = Signal(providing_args=('user',))
|
||||
33
apps/authentication/signals_handlers.py
Normal file
33
apps/authentication/signals_handlers.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from django.http.request import QueryDict
|
||||
from django.contrib.auth.signals import user_logged_out
|
||||
from django.dispatch import receiver
|
||||
from django.conf import settings
|
||||
from .openid import client
|
||||
from .signals import post_create_openid_user
|
||||
|
||||
|
||||
@receiver(user_logged_out)
|
||||
def on_user_logged_out(sender, request, user, **kwargs):
|
||||
if not settings.AUTH_OPENID:
|
||||
return
|
||||
|
||||
query = QueryDict('', mutable=True)
|
||||
query.update({
|
||||
'redirect_uri': settings.BASE_SITE_URL
|
||||
})
|
||||
|
||||
openid_logout_url = "%s?%s" % (
|
||||
client.openid_connect_client.get_url(
|
||||
name='end_session_endpoint'),
|
||||
query.urlencode()
|
||||
)
|
||||
|
||||
request.COOKIES['next'] = openid_logout_url
|
||||
|
||||
|
||||
@receiver(post_create_openid_user)
|
||||
def on_post_create_openid_user(sender, user=None, **kwargs):
|
||||
if user and user.username != 'admin':
|
||||
user.source = user.SOURCE_OPENID
|
||||
user.save()
|
||||
|
||||
1
apps/authentication/tests.py
Normal file
1
apps/authentication/tests.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
apps/authentication/urls/api_urls.py
Normal file
1
apps/authentication/urls/api_urls.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
16
apps/authentication/urls/view_urls.py
Normal file
16
apps/authentication/urls/view_urls.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# coding:utf-8
|
||||
#
|
||||
|
||||
from django.urls import path
|
||||
from authentication.openid import views
|
||||
|
||||
app_name = 'authentication'
|
||||
|
||||
urlpatterns = [
|
||||
# openid
|
||||
path('openid/login/', views.LoginView.as_view(), name='openid-login'),
|
||||
path('openid/login/complete/', views.LoginCompleteView.as_view(),
|
||||
name='openid-login-complete'),
|
||||
|
||||
# other
|
||||
]
|
||||
1
apps/authentication/views.py
Normal file
1
apps/authentication/views.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import os
|
||||
import json
|
||||
import jms_storage
|
||||
|
||||
from rest_framework.views import Response, APIView
|
||||
from ldap3 import Server, Connection
|
||||
@@ -8,12 +11,13 @@ from django.core.mail import get_connection, send_mail
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf import settings
|
||||
|
||||
from .permissions import IsSuperUser
|
||||
from .permissions import IsOrgAdmin, IsSuperUser
|
||||
from .serializers import MailTestSerializer, LDAPTestSerializer
|
||||
from .models import Setting
|
||||
|
||||
|
||||
class MailTestingAPI(APIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = MailTestSerializer
|
||||
success_message = _("Test mail sent to {}, please check")
|
||||
|
||||
@@ -37,7 +41,7 @@ class MailTestingAPI(APIView):
|
||||
|
||||
|
||||
class LDAPTestingAPI(APIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = LDAPTestSerializer
|
||||
success_message = _("Test ldap success")
|
||||
|
||||
@@ -48,7 +52,7 @@ class LDAPTestingAPI(APIView):
|
||||
bind_dn = serializer.validated_data["AUTH_LDAP_BIND_DN"]
|
||||
password = serializer.validated_data["AUTH_LDAP_BIND_PASSWORD"]
|
||||
use_ssl = serializer.validated_data.get("AUTH_LDAP_START_TLS", False)
|
||||
search_ou = serializer.validated_data["AUTH_LDAP_SEARCH_OU"]
|
||||
search_ougroup = serializer.validated_data["AUTH_LDAP_SEARCH_OU"]
|
||||
search_filter = serializer.validated_data["AUTH_LDAP_SEARCH_FILTER"]
|
||||
attr_map = serializer.validated_data["AUTH_LDAP_USER_ATTR_MAP"]
|
||||
|
||||
@@ -64,18 +68,19 @@ class LDAPTestingAPI(APIView):
|
||||
except Exception as e:
|
||||
return Response({"error": str(e)}, status=401)
|
||||
|
||||
ok = conn.search(search_ou, search_filter % ({"user": "*"}),
|
||||
attributes=list(attr_map.values()))
|
||||
if not ok:
|
||||
return Response({"error": "Search no entry matched"}, status=401)
|
||||
|
||||
users = []
|
||||
for entry in conn.entries:
|
||||
user = {}
|
||||
for attr, mapping in attr_map.items():
|
||||
if hasattr(entry, mapping):
|
||||
user[attr] = getattr(entry, mapping)
|
||||
users.append(user)
|
||||
for search_ou in str(search_ougroup).split("|"):
|
||||
ok = conn.search(search_ou, search_filter % ({"user": "*"}),
|
||||
attributes=list(attr_map.values()))
|
||||
if not ok:
|
||||
return Response({"error": _("Search no entry matched in ou {}").format(search_ou)}, status=401)
|
||||
|
||||
for entry in conn.entries:
|
||||
user = {}
|
||||
for attr, mapping in attr_map.items():
|
||||
if hasattr(entry, mapping):
|
||||
user[attr] = getattr(entry, mapping)
|
||||
users.append(user)
|
||||
if len(users) > 0:
|
||||
return Response({"msg": _("Match {} s users").format(len(users))})
|
||||
else:
|
||||
@@ -84,9 +89,93 @@ class LDAPTestingAPI(APIView):
|
||||
return Response({"error": str(serializer.errors)}, status=401)
|
||||
|
||||
|
||||
class ReplayStorageCreateAPI(APIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
def post(self, request):
|
||||
storage_data = request.data
|
||||
|
||||
if storage_data.get('TYPE') == 'ceph':
|
||||
port = storage_data.get('PORT')
|
||||
if port.isdigit():
|
||||
storage_data['PORT'] = int(storage_data.get('PORT'))
|
||||
|
||||
storage_name = storage_data.pop('NAME')
|
||||
data = {storage_name: storage_data}
|
||||
|
||||
if not self.is_valid(storage_data):
|
||||
return Response({"error": _("Error: Account invalid")}, status=401)
|
||||
|
||||
Setting.save_storage('TERMINAL_REPLAY_STORAGE', data)
|
||||
return Response({"msg": _('Create succeed')}, status=200)
|
||||
|
||||
@staticmethod
|
||||
def is_valid(storage_data):
|
||||
if storage_data.get('TYPE') == 'server':
|
||||
return True
|
||||
storage = jms_storage.get_object_storage(storage_data)
|
||||
target = 'tests.py'
|
||||
src = os.path.join(settings.BASE_DIR, 'common', target)
|
||||
return storage.is_valid(src, target)
|
||||
|
||||
|
||||
class ReplayStorageDeleteAPI(APIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
def post(self, request):
|
||||
storage_name = str(request.data.get('name'))
|
||||
Setting.delete_storage('TERMINAL_REPLAY_STORAGE', storage_name)
|
||||
return Response({"msg": _('Delete succeed')}, status=200)
|
||||
|
||||
|
||||
class CommandStorageCreateAPI(APIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
def post(self, request):
|
||||
storage_data = request.data
|
||||
storage_name = storage_data.pop('NAME')
|
||||
data = {storage_name: storage_data}
|
||||
if not self.is_valid(storage_data):
|
||||
return Response({"error": _("Error: Account invalid")}, status=401)
|
||||
|
||||
Setting.save_storage('TERMINAL_COMMAND_STORAGE', data)
|
||||
return Response({"msg": _('Create succeed')}, status=200)
|
||||
|
||||
@staticmethod
|
||||
def is_valid(storage_data):
|
||||
if storage_data.get('TYPE') == 'server':
|
||||
return True
|
||||
try:
|
||||
storage = jms_storage.get_log_storage(storage_data)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
return storage.ping()
|
||||
|
||||
|
||||
class CommandStorageDeleteAPI(APIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
|
||||
def post(self, request):
|
||||
storage_name = str(request.data.get('name'))
|
||||
Setting.delete_storage('TERMINAL_COMMAND_STORAGE', storage_name)
|
||||
return Response({"msg": _('Delete succeed')}, status=200)
|
||||
|
||||
|
||||
class DjangoSettingsAPI(APIView):
|
||||
def get(self, request):
|
||||
return Response('Danger, Close now')
|
||||
if not settings.DEBUG:
|
||||
return Response("Not in debug mode")
|
||||
|
||||
data = {}
|
||||
for k, v in settings.__dict__.items():
|
||||
if k and k.isupper():
|
||||
try:
|
||||
json.dumps(v)
|
||||
data[k] = v
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
data[k] = str(v)
|
||||
return Response(data)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ from .utils import get_signer
|
||||
signer = get_signer()
|
||||
|
||||
|
||||
class DictField(forms.Field):
|
||||
class FormDictField(forms.Field):
|
||||
widget = forms.Textarea
|
||||
|
||||
def to_python(self, value):
|
||||
@@ -23,6 +23,7 @@ class DictField(forms.Field):
|
||||
# RadioSelect will provide. Because bool("True") == bool('1') == True,
|
||||
# we don't need to handle that explicitly.
|
||||
if isinstance(value, six.string_types):
|
||||
value = value.replace("'", '"')
|
||||
try:
|
||||
value = json.loads(value)
|
||||
return value
|
||||
@@ -62,7 +63,7 @@ class EncryptMixin:
|
||||
def get_prep_value(self, value):
|
||||
if value is None:
|
||||
return value
|
||||
return signer.sign(value).decode('utf-8')
|
||||
return signer.sign(value)
|
||||
|
||||
|
||||
class EncryptTextField(EncryptMixin, models.TextField):
|
||||
@@ -74,3 +75,32 @@ class EncryptCharField(EncryptMixin, models.CharField):
|
||||
kwargs['max_length'] = 2048
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class FormEncryptMixin:
|
||||
pass
|
||||
|
||||
|
||||
class FormEncryptCharField(FormEncryptMixin, forms.CharField):
|
||||
pass
|
||||
|
||||
|
||||
class FormEncryptDictField(FormEncryptMixin, FormDictField):
|
||||
pass
|
||||
|
||||
|
||||
class ChoiceDisplayField(serializers.ChoiceField):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ChoiceDisplayField, self).__init__(*args, **kwargs)
|
||||
self.choice_strings_to_display = {
|
||||
six.text_type(key): value for key, value in self.choices.items()
|
||||
}
|
||||
|
||||
def to_representation(self, value):
|
||||
if value is None:
|
||||
return value
|
||||
return {
|
||||
'value': self.choice_strings_to_values.get(six.text_type(value),
|
||||
value),
|
||||
'display': self.choice_strings_to_display.get(six.text_type(value),
|
||||
value),
|
||||
}
|
||||
|
||||
@@ -1,69 +1,63 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import json
|
||||
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.html import escape
|
||||
from django.db import transaction
|
||||
from django.conf import settings
|
||||
|
||||
from .models import Setting
|
||||
from .fields import DictField
|
||||
|
||||
|
||||
def to_model_value(value):
|
||||
try:
|
||||
return json.dumps(value)
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
|
||||
|
||||
def to_form_value(value):
|
||||
try:
|
||||
data = json.loads(value)
|
||||
if isinstance(data, dict):
|
||||
data = value
|
||||
return data
|
||||
except json.JSONDecodeError:
|
||||
return ""
|
||||
from .models import Setting, common_settings
|
||||
from .fields import FormDictField, FormEncryptCharField, \
|
||||
FormEncryptMixin, FormEncryptDictField
|
||||
|
||||
|
||||
class BaseForm(forms.Form):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
db_settings = Setting.objects.all()
|
||||
for name, field in self.fields.items():
|
||||
db_value = getattr(db_settings, name).value
|
||||
db_value = getattr(common_settings, name)
|
||||
django_value = getattr(settings, name) if hasattr(settings, name) else None
|
||||
|
||||
if db_value is None and django_value is None:
|
||||
continue
|
||||
|
||||
if db_value is False or db_value:
|
||||
field.initial = to_form_value(db_value)
|
||||
if isinstance(db_value, dict):
|
||||
db_value = json.dumps(db_value)
|
||||
initial_value = db_value
|
||||
elif django_value is False or django_value:
|
||||
field.initial = to_form_value(to_model_value(django_value))
|
||||
initial_value = django_value
|
||||
else:
|
||||
initial_value = ''
|
||||
field.initial = initial_value
|
||||
|
||||
def save(self, category="default"):
|
||||
if not self.is_bound:
|
||||
raise ValueError("Form is not bound")
|
||||
|
||||
db_settings = Setting.objects.all()
|
||||
if self.is_valid():
|
||||
with transaction.atomic():
|
||||
for name, value in self.cleaned_data.items():
|
||||
field = self.fields[name]
|
||||
if isinstance(field.widget, forms.PasswordInput) and not value:
|
||||
continue
|
||||
if value == to_form_value(getattr(db_settings, name).value):
|
||||
continue
|
||||
|
||||
defaults = {
|
||||
'name': name,
|
||||
'category': category,
|
||||
'value': to_model_value(value)
|
||||
}
|
||||
Setting.objects.update_or_create(defaults=defaults, name=name)
|
||||
else:
|
||||
# db_settings = Setting.objects.all()
|
||||
if not self.is_valid():
|
||||
raise ValueError(self.errors)
|
||||
|
||||
with transaction.atomic():
|
||||
for name, value in self.cleaned_data.items():
|
||||
field = self.fields[name]
|
||||
if isinstance(field.widget, forms.PasswordInput) and not value:
|
||||
continue
|
||||
if value == getattr(common_settings, name):
|
||||
continue
|
||||
|
||||
encrypted = True if isinstance(field, FormEncryptMixin) else False
|
||||
try:
|
||||
setting = Setting.objects.get(name=name)
|
||||
except Setting.DoesNotExist:
|
||||
setting = Setting()
|
||||
setting.name = name
|
||||
setting.category = category
|
||||
setting.encrypted = encrypted
|
||||
setting.cleaned_value = value
|
||||
setting.save()
|
||||
|
||||
|
||||
class BasicSettingForm(BaseForm):
|
||||
SITE_URL = forms.URLField(
|
||||
@@ -88,7 +82,7 @@ class EmailSettingForm(BaseForm):
|
||||
EMAIL_HOST_USER = forms.CharField(
|
||||
max_length=128, label=_("SMTP user"), initial='noreply@jumpserver.org'
|
||||
)
|
||||
EMAIL_HOST_PASSWORD = forms.CharField(
|
||||
EMAIL_HOST_PASSWORD = FormEncryptCharField(
|
||||
max_length=1024, label=_("SMTP password"), widget=forms.PasswordInput,
|
||||
required=False, help_text=_("Some provider use token except password")
|
||||
)
|
||||
@@ -109,26 +103,24 @@ class LDAPSettingForm(BaseForm):
|
||||
AUTH_LDAP_BIND_DN = forms.CharField(
|
||||
label=_("Bind DN"), initial='cn=admin,dc=jumpserver,dc=org'
|
||||
)
|
||||
AUTH_LDAP_BIND_PASSWORD = forms.CharField(
|
||||
AUTH_LDAP_BIND_PASSWORD = FormEncryptCharField(
|
||||
label=_("Password"), initial='',
|
||||
widget=forms.PasswordInput, required=False
|
||||
)
|
||||
AUTH_LDAP_SEARCH_OU = forms.CharField(
|
||||
label=_("User OU"), initial='ou=tech,dc=jumpserver,dc=org'
|
||||
label=_("User OU"), initial='ou=tech,dc=jumpserver,dc=org',
|
||||
help_text=_("Use | split User OUs")
|
||||
)
|
||||
AUTH_LDAP_SEARCH_FILTER = forms.CharField(
|
||||
label=_("User search filter"), initial='(cn=%(user)s)',
|
||||
help_text=_("Choice may be (cn|uid|sAMAccountName)=%(user)s)")
|
||||
)
|
||||
AUTH_LDAP_USER_ATTR_MAP = DictField(
|
||||
AUTH_LDAP_USER_ATTR_MAP = FormDictField(
|
||||
label=_("User attr map"),
|
||||
initial=json.dumps({
|
||||
"username": "cn",
|
||||
"name": "sn",
|
||||
"email": "mail"
|
||||
}),
|
||||
help_text=_(
|
||||
"User attr map present how to map LDAP user attr to jumpserver, username,name,email is jumpserver attr")
|
||||
"User attr map present how to map LDAP user attr to jumpserver, "
|
||||
"username,name,email is jumpserver attr"
|
||||
)
|
||||
)
|
||||
# AUTH_LDAP_GROUP_SEARCH_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU
|
||||
# AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER
|
||||
@@ -143,34 +135,26 @@ class TerminalSettingForm(BaseForm):
|
||||
('hostname', _('Hostname')),
|
||||
('ip', _('IP')),
|
||||
)
|
||||
TERMINAL_ASSET_LIST_SORT_BY = forms.ChoiceField(
|
||||
choices=SORT_BY_CHOICES, initial='hostname', label=_("List sort by")
|
||||
)
|
||||
TERMINAL_HEARTBEAT_INTERVAL = forms.IntegerField(
|
||||
initial=5, label=_("Heartbeat interval"), help_text=_("Units: seconds")
|
||||
)
|
||||
TERMINAL_PASSWORD_AUTH = forms.BooleanField(
|
||||
initial=True, required=False, label=_("Password auth")
|
||||
)
|
||||
TERMINAL_PUBLIC_KEY_AUTH = forms.BooleanField(
|
||||
initial=True, required=False, label=_("Public key auth")
|
||||
)
|
||||
TERMINAL_COMMAND_STORAGE = DictField(
|
||||
label=_("Command storage"), help_text=_(
|
||||
"Set terminal storage setting, `default` is the using as default,"
|
||||
"You can set other storage and some terminal using"
|
||||
)
|
||||
TERMINAL_HEARTBEAT_INTERVAL = forms.IntegerField(
|
||||
initial=5, label=_("Heartbeat interval"), help_text=_("Units: seconds")
|
||||
)
|
||||
TERMINAL_REPLAY_STORAGE = DictField(
|
||||
label=_("Replay storage"), help_text=_(
|
||||
"Set replay storage setting, `default` is the using as default,"
|
||||
"You can set other storage and some terminal using"
|
||||
)
|
||||
TERMINAL_ASSET_LIST_SORT_BY = forms.ChoiceField(
|
||||
choices=SORT_BY_CHOICES, initial='hostname', label=_("List sort by")
|
||||
)
|
||||
|
||||
|
||||
class TerminalCommandStorage(BaseForm):
|
||||
pass
|
||||
|
||||
|
||||
class SecuritySettingForm(BaseForm):
|
||||
# MFA全局设置
|
||||
# MFA global setting
|
||||
SECURITY_MFA_AUTH = forms.BooleanField(
|
||||
initial=False, required=False,
|
||||
label=_("MFA Secondary certification"),
|
||||
@@ -179,38 +163,60 @@ class SecuritySettingForm(BaseForm):
|
||||
'authentication (valid for all users, including administrators)'
|
||||
)
|
||||
)
|
||||
# 最小长度
|
||||
# limit login count
|
||||
SECURITY_LOGIN_LIMIT_COUNT = forms.IntegerField(
|
||||
initial=7, min_value=3,
|
||||
label=_("Limit the number of login failures")
|
||||
)
|
||||
# limit login time
|
||||
SECURITY_LOGIN_LIMIT_TIME = forms.IntegerField(
|
||||
initial=30, min_value=5,
|
||||
label=_("No logon interval"),
|
||||
help_text=_(
|
||||
"Tip :(unit/minute) if the user has failed to log in for a limited "
|
||||
"number of times, no login is allowed during this time interval."
|
||||
)
|
||||
)
|
||||
SECURITY_MAX_IDLE_TIME = forms.IntegerField(
|
||||
initial=30, required=False,
|
||||
label=_("Connection max idle time"),
|
||||
help_text=_(
|
||||
'If idle time more than it, disconnect connection(only ssh now) '
|
||||
'Unit: minute'
|
||||
),
|
||||
)
|
||||
# min length
|
||||
SECURITY_PASSWORD_MIN_LENGTH = forms.IntegerField(
|
||||
initial=6, label=_("Password minimum length"),
|
||||
min_value=6
|
||||
)
|
||||
# 大写字母
|
||||
# upper case
|
||||
SECURITY_PASSWORD_UPPER_CASE = forms.BooleanField(
|
||||
|
||||
initial=False, required=False,
|
||||
label=_("Must contain capital letters"),
|
||||
help_text=_(
|
||||
'After opening, the user password changes '
|
||||
'and resets must contain uppercase letters')
|
||||
)
|
||||
# 小写字母
|
||||
# lower case
|
||||
SECURITY_PASSWORD_LOWER_CASE = forms.BooleanField(
|
||||
initial=False, required=False,
|
||||
label=_("Must contain lowercase letters"),
|
||||
help_text=_('After opening, the user password changes '
|
||||
'and resets must contain lowercase letters')
|
||||
)
|
||||
# 数字
|
||||
# number
|
||||
SECURITY_PASSWORD_NUMBER = forms.BooleanField(
|
||||
initial=False, required=False,
|
||||
label=_("Must contain numeric characters"),
|
||||
help_text=_('After opening, the user password changes '
|
||||
'and resets must contain numeric characters')
|
||||
)
|
||||
# 特殊字符
|
||||
SECURITY_PASSWORD_SPECIAL_CHAR= forms.BooleanField(
|
||||
# special char
|
||||
SECURITY_PASSWORD_SPECIAL_CHAR = forms.BooleanField(
|
||||
initial=False, required=False,
|
||||
label=_("Must contain special characters"),
|
||||
help_text=_('After opening, the user password changes '
|
||||
'and resets must contain special characters')
|
||||
)
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user