Unverified Commit a5b9b4e1 authored by 老广's avatar 老广 Committed by GitHub

Merge pull request #2206 from jumpserver/dev

Dev
parents ac7e3e7f b3079a4a
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
# limitations under the License. # limitations under the License.
from django.db import transaction from django.db import transaction
from django.shortcuts import get_object_or_404
from rest_framework import generics from rest_framework import generics
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet from rest_framework_bulk import BulkModelViewSet
...@@ -24,13 +25,14 @@ from common.utils import get_logger ...@@ -24,13 +25,14 @@ from common.utils import get_logger
from ..hands import IsOrgAdmin from ..hands import IsOrgAdmin
from ..models import AdminUser, Asset from ..models import AdminUser, Asset
from .. import serializers from .. import serializers
from ..tasks import test_admin_user_connectability_manual from ..tasks import test_admin_user_connectivity_manual
logger = get_logger(__file__) logger = get_logger(__file__)
__all__ = [ __all__ = [
'AdminUserViewSet', 'ReplaceNodesAdminUserApi', 'AdminUserViewSet', 'ReplaceNodesAdminUserApi',
'AdminUserTestConnectiveApi', 'AdminUserAuthApi', 'AdminUserTestConnectiveApi', 'AdminUserAuthApi',
'AdminUserAssetsListView',
] ]
...@@ -81,12 +83,29 @@ class ReplaceNodesAdminUserApi(generics.UpdateAPIView): ...@@ -81,12 +83,29 @@ class ReplaceNodesAdminUserApi(generics.UpdateAPIView):
class AdminUserTestConnectiveApi(generics.RetrieveAPIView): class AdminUserTestConnectiveApi(generics.RetrieveAPIView):
""" """
Test asset admin user connectivity Test asset admin user assets_connectivity
""" """
queryset = AdminUser.objects.all() queryset = AdminUser.objects.all()
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
admin_user = self.get_object() admin_user = self.get_object()
task = test_admin_user_connectability_manual.delay(admin_user) task = test_admin_user_connectivity_manual.delay(admin_user)
return Response({"task": task.id}) return Response({"task": task.id})
class AdminUserAssetsListView(generics.ListAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetSimpleSerializer
pagination_class = LimitOffsetPagination
filter_fields = ("hostname", "ip")
http_method_names = ['get']
search_fields = filter_fields
def get_object(self):
pk = self.kwargs.get('pk')
return get_object_or_404(AdminUser, pk=pk)
def get_queryset(self):
admin_user = self.get_object()
return admin_user.get_related_assets()
...@@ -17,7 +17,7 @@ from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser ...@@ -17,7 +17,7 @@ from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from ..models import Asset, AdminUser, Node from ..models import Asset, AdminUser, Node
from .. import serializers from .. import serializers
from ..tasks import update_asset_hardware_info_manual, \ from ..tasks import update_asset_hardware_info_manual, \
test_asset_connectability_manual test_asset_connectivity_manual
from ..utils import LabelFilter from ..utils import LabelFilter
...@@ -41,40 +41,46 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet): ...@@ -41,40 +41,46 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
pagination_class = LimitOffsetPagination pagination_class = LimitOffsetPagination
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
def filter_node(self): def filter_node(self, queryset):
node_id = self.request.query_params.get("node_id") node_id = self.request.query_params.get("node_id")
if not node_id: if not node_id:
return return queryset
node = get_object_or_404(Node, id=node_id) node = get_object_or_404(Node, id=node_id)
show_current_asset = self.request.query_params.get("show_current_asset") in ('1', 'true') show_current_asset = self.request.query_params.get("show_current_asset") in ('1', 'true')
if node.is_root(): if node.is_root() and show_current_asset:
if show_current_asset: queryset = queryset.filter(
self.queryset = self.queryset.filter( Q(nodes=node_id) | Q(nodes__isnull=True)
Q(nodes=node_id) | Q(nodes__isnull=True) )
) elif node.is_root() and not show_current_asset:
return pass
if show_current_asset: elif not node.is_root() and show_current_asset:
self.queryset = self.queryset.filter(nodes=node) queryset = queryset.filter(nodes=node)
else: else:
self.queryset = self.queryset.filter( queryset = queryset.filter(
nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key), nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key),
) )
return queryset
def filter_admin_user_id(self): def filter_admin_user_id(self, queryset):
admin_user_id = self.request.query_params.get('admin_user_id') admin_user_id = self.request.query_params.get('admin_user_id')
if admin_user_id: if not admin_user_id:
admin_user = get_object_or_404(AdminUser, id=admin_user_id) return queryset
self.queryset = self.queryset.filter(admin_user=admin_user) admin_user = get_object_or_404(AdminUser, id=admin_user_id)
queryset = queryset.filter(admin_user=admin_user)
return queryset
def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
queryset = self.filter_node(queryset)
queryset = self.filter_admin_user_id(queryset)
return queryset
def get_queryset(self): def get_queryset(self):
self.queryset = super().get_queryset()\ queryset = super().get_queryset().distinct()
.prefetch_related('labels', 'nodes')\ queryset = self.get_serializer_class().setup_eager_loading(queryset)
.select_related('admin_user') return queryset
self.filter_admin_user_id()
self.filter_node()
return self.queryset.distinct()
class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView): class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
...@@ -103,7 +109,7 @@ class AssetRefreshHardwareApi(generics.RetrieveAPIView): ...@@ -103,7 +109,7 @@ class AssetRefreshHardwareApi(generics.RetrieveAPIView):
class AssetAdminUserTestApi(generics.RetrieveAPIView): class AssetAdminUserTestApi(generics.RetrieveAPIView):
""" """
Test asset admin user connectivity Test asset admin user assets_connectivity
""" """
queryset = Asset.objects.all() queryset = Asset.objects.all()
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
...@@ -111,7 +117,7 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView): ...@@ -111,7 +117,7 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView):
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
asset_id = kwargs.get('pk') asset_id = kwargs.get('pk')
asset = get_object_or_404(Asset, pk=asset_id) asset = get_object_or_404(Asset, pk=asset_id)
task = test_asset_connectability_manual.delay(asset) task = test_asset_connectivity_manual.delay(asset)
return Response({"task": task.id}) return Response({"task": task.id})
......
...@@ -17,15 +17,14 @@ from rest_framework import generics, mixins, viewsets ...@@ -17,15 +17,14 @@ from rest_framework import generics, mixins, viewsets
from rest_framework.serializers import ValidationError from rest_framework.serializers import ValidationError
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.db.models import Count
from common.utils import get_logger, get_object_or_none from common.utils import get_logger, get_object_or_none
from common.tree import TreeNodeSerializer
from ..hands import IsOrgAdmin from ..hands import IsOrgAdmin
from ..models import Node from ..models import Node
from ..tasks import update_assets_hardware_info_util, test_asset_connectability_util from ..tasks import update_assets_hardware_info_util, test_asset_connectivity_util
from .. import serializers from .. import serializers
...@@ -34,7 +33,8 @@ __all__ = [ ...@@ -34,7 +33,8 @@ __all__ = [
'NodeViewSet', 'NodeChildrenApi', 'NodeAssetsApi', 'NodeViewSet', 'NodeChildrenApi', 'NodeAssetsApi',
'NodeAddAssetsApi', 'NodeRemoveAssetsApi', 'NodeReplaceAssetsApi', 'NodeAddAssetsApi', 'NodeRemoveAssetsApi', 'NodeReplaceAssetsApi',
'NodeAddChildrenApi', 'RefreshNodeHardwareInfoApi', 'NodeAddChildrenApi', 'RefreshNodeHardwareInfoApi',
'TestNodeConnectiveApi' 'TestNodeConnectiveApi', 'NodeListAsTreeApi',
'NodeChildrenAsTreeApi',
] ]
...@@ -43,22 +43,89 @@ class NodeViewSet(viewsets.ModelViewSet): ...@@ -43,22 +43,89 @@ class NodeViewSet(viewsets.ModelViewSet):
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeSerializer serializer_class = serializers.NodeSerializer
def perform_create(self, serializer):
child_key = Node.root().get_next_child_key()
serializer.validated_data["key"] = child_key
serializer.save()
def update(self, request, *args, **kwargs): class NodeListAsTreeApi(generics.ListAPIView):
node = self.get_object() """
if node.is_root(): 获取节点列表树
node_value = node.value [
post_value = request.data.get('value') {
if node_value != post_value: "id": "",
return Response( "name": "",
{"msg": _("You can't update the root node name")}, "pId": "",
status=400 "meta": ""
) }
return super().update(request, *args, **kwargs) ]
"""
permission_classes = (IsOrgAdmin,)
serializer_class = TreeNodeSerializer
def get_queryset(self):
queryset = [node.as_tree_node() for node in Node.objects.all()]
return queryset
def filter_queryset(self, queryset):
if self.request.query_params.get('refresh', '0') == '1':
queryset = self.refresh_nodes(queryset)
return queryset
@staticmethod
def refresh_nodes(queryset):
Node.expire_nodes_assets_amount()
Node.expire_nodes_full_value()
return queryset
class NodeChildrenAsTreeApi(generics.ListAPIView):
"""
节点子节点作为树返回,
[
{
"id": "",
"name": "",
"pId": "",
"meta": ""
}
]
"""
permission_classes = (IsOrgAdmin,)
serializer_class = TreeNodeSerializer
node = None
is_root = False
def get_queryset(self):
node_key = self.request.query_params.get('key')
if node_key:
self.node = Node.objects.get(key=node_key)
queryset = self.node.get_children(with_self=False)
else:
self.is_root = True
self.node = Node.root()
queryset = list(self.node.get_children(with_self=True))
nodes_invalid = Node.objects.exclude(key__startswith=self.node.key)
queryset.extend(list(nodes_invalid))
queryset = [node.as_tree_node() for node in queryset]
return queryset
def filter_assets(self, queryset):
include_assets = self.request.query_params.get('assets', '0') == '1'
if not include_assets:
return queryset
assets = self.node.get_assets()
for asset in assets:
queryset.append(asset.as_tree_node(self.node))
return queryset
def filter_queryset(self, queryset):
queryset = self.filter_assets(queryset)
queryset = self.filter_refresh_nodes(queryset)
return queryset
def filter_refresh_nodes(self, queryset):
if self.request.query_params.get('refresh', '0') == '1':
Node.expire_nodes_assets_amount()
Node.expire_nodes_full_value()
return queryset
class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView): class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
...@@ -67,19 +134,10 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView): ...@@ -67,19 +134,10 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
serializer_class = serializers.NodeSerializer serializer_class = serializers.NodeSerializer
instance = None instance = None
def counter(self):
values = [
child.value[child.value.rfind(' '):]
for child in self.get_object().get_children()
if child.value.startswith("新节点 ")
]
values = [int(value) for value in values if value.strip().isdigit()]
count = max(values)+1 if values else 1
return count
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
instance = self.get_object()
if not request.data.get("value"): if not request.data.get("value"):
request.data["value"] = _("New node {}").format(self.counter()) request.data["value"] = instance.get_next_child_preset_name()
return super().post(request, *args, **kwargs) return super().post(request, *args, **kwargs)
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
...@@ -91,10 +149,7 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView): ...@@ -91,10 +149,7 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
'The same level node name cannot be the same' 'The same level node name cannot be the same'
) )
node = instance.create_child(value=value) node = instance.create_child(value=value)
return Response( return Response(self.serializer_class(instance=node).data, status=201)
{"id": node.id, "key": node.key, "value": node.value},
status=201,
)
def get_object(self): def get_object(self):
pk = self.kwargs.get('pk') or self.request.query_params.get('id') pk = self.kwargs.get('pk') or self.request.query_params.get('id')
...@@ -107,7 +162,6 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView): ...@@ -107,7 +162,6 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
def get_queryset(self): def get_queryset(self):
queryset = [] queryset = []
query_all = self.request.query_params.get("all") query_all = self.request.query_params.get("all")
query_assets = self.request.query_params.get('assets')
node = self.get_object() node = self.get_object()
if node is None: if node is None:
...@@ -120,23 +174,8 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView): ...@@ -120,23 +174,8 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
else: else:
children = node.get_children() children = node.get_children()
queryset.extend(list(children)) queryset.extend(list(children))
if query_assets:
assets = node.get_assets()
for asset in assets:
node_fake = Node()
node_fake.assets__count = 0
node_fake.id = asset.id
node_fake.is_node = False
node_fake.key = node.key + ':0'
node_fake.value = asset.hostname
queryset.append(node_fake)
queryset = sorted(queryset, key=lambda x: x.is_node, reverse=True)
return queryset return queryset
def get(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
class NodeAssetsApi(generics.ListAPIView): class NodeAssetsApi(generics.ListAPIView):
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
...@@ -234,5 +273,5 @@ class TestNodeConnectiveApi(APIView): ...@@ -234,5 +273,5 @@ class TestNodeConnectiveApi(APIView):
assets = node.assets.all() 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_name = _("Test if the assets under the node are connectable: {}".format(node.name))
task = test_asset_connectability_util.delay(assets, task_name=task_name) task = test_asset_connectivity_util.delay(assets, task_name=task_name)
return Response({"task": task.id}) return Response({"task": task.id})
...@@ -24,8 +24,8 @@ from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser ...@@ -24,8 +24,8 @@ from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from ..models import SystemUser, Asset from ..models import SystemUser, Asset
from .. import serializers from .. import serializers
from ..tasks import push_system_user_to_assets_manual, \ from ..tasks import push_system_user_to_assets_manual, \
test_system_user_connectability_manual, push_system_user_a_asset_manual, \ test_system_user_connectivity_manual, push_system_user_a_asset_manual, \
test_system_user_connectability_a_asset test_system_user_connectivity_a_asset
logger = get_logger(__file__) logger = get_logger(__file__)
...@@ -33,7 +33,7 @@ __all__ = [ ...@@ -33,7 +33,7 @@ __all__ = [
'SystemUserViewSet', 'SystemUserAuthInfoApi', 'SystemUserViewSet', 'SystemUserAuthInfoApi',
'SystemUserPushApi', 'SystemUserTestConnectiveApi', 'SystemUserPushApi', 'SystemUserTestConnectiveApi',
'SystemUserAssetsListView', 'SystemUserPushToAssetApi', 'SystemUserAssetsListView', 'SystemUserPushToAssetApi',
'SystemUserTestAssetConnectabilityApi', 'SystemUserCommandFilterRuleListApi', 'SystemUserTestAssetConnectivityApi', 'SystemUserCommandFilterRuleListApi',
] ]
...@@ -93,15 +93,16 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView): ...@@ -93,15 +93,16 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView):
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
system_user = self.get_object() system_user = self.get_object()
task = test_system_user_connectability_manual.delay(system_user) task = test_system_user_connectivity_manual.delay(system_user)
return Response({"task": task.id}) return Response({"task": task.id})
class SystemUserAssetsListView(generics.ListAPIView): class SystemUserAssetsListView(generics.ListAPIView):
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetSerializer serializer_class = serializers.AssetSimpleSerializer
pagination_class = LimitOffsetPagination pagination_class = LimitOffsetPagination
filter_fields = ("hostname", "ip") filter_fields = ("hostname", "ip")
http_method_names = ['get']
search_fields = filter_fields search_fields = filter_fields
def get_object(self): def get_object(self):
...@@ -125,7 +126,7 @@ class SystemUserPushToAssetApi(generics.RetrieveAPIView): ...@@ -125,7 +126,7 @@ class SystemUserPushToAssetApi(generics.RetrieveAPIView):
return Response({"task": task.id}) return Response({"task": task.id})
class SystemUserTestAssetConnectabilityApi(generics.RetrieveAPIView): class SystemUserTestAssetConnectivityApi(generics.RetrieveAPIView):
queryset = SystemUser.objects.all() queryset = SystemUser.objects.all()
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
...@@ -133,7 +134,7 @@ class SystemUserTestAssetConnectabilityApi(generics.RetrieveAPIView): ...@@ -133,7 +134,7 @@ class SystemUserTestAssetConnectabilityApi(generics.RetrieveAPIView):
system_user = self.get_object() system_user = self.get_object()
asset_id = self.kwargs.get('aid') asset_id = self.kwargs.get('aid')
asset = get_object_or_404(Asset, id=asset_id) asset = get_object_or_404(Asset, id=asset_id)
task = test_system_user_connectability_a_asset.delay(system_user, asset) task = test_system_user_connectivity_a_asset.delay(system_user, asset)
return Response({"task": task.id}) return Response({"task": task.id})
......
...@@ -99,8 +99,8 @@ class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm): ...@@ -99,8 +99,8 @@ class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm):
auto_generate_key = self.cleaned_data.get('auto_generate_key', False) auto_generate_key = self.cleaned_data.get('auto_generate_key', False)
private_key, public_key = super().gen_keys() private_key, public_key = super().gen_keys()
if login_mode == SystemUser.MANUAL_LOGIN or \ if login_mode == SystemUser.LOGIN_MANUAL or \
protocol in [SystemUser.RDP_PROTOCOL, SystemUser.TELNET_PROTOCOL]: protocol in [SystemUser.PROTOCOL_RDP, SystemUser.PROTOCOL_TELNET]:
system_user.auto_push = 0 system_user.auto_push = 0
auto_generate_key = False auto_generate_key = False
system_user.save() system_user.save()
...@@ -124,7 +124,7 @@ class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm): ...@@ -124,7 +124,7 @@ class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm):
validated = super().is_valid() validated = super().is_valid()
username = self.cleaned_data.get('username') username = self.cleaned_data.get('username')
login_mode = self.cleaned_data.get('login_mode') login_mode = self.cleaned_data.get('login_mode')
if login_mode == SystemUser.AUTO_LOGIN and not username: if login_mode == SystemUser.LOGIN_AUTO and not username:
self.add_error( self.add_error(
"username", _('* Automatic login mode,' "username", _('* Automatic login mode,'
' must fill in the username.') ' must fill in the username.')
......
...@@ -13,36 +13,36 @@ class Migration(migrations.Migration): ...@@ -13,36 +13,36 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='adminuser', model_name='adminuser',
name='org_id', name='org_id',
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'), field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='asset', model_name='asset',
name='org_id', name='org_id',
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'), field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='domain', model_name='domain',
name='org_id', name='org_id',
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'), field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='gateway', model_name='gateway',
name='org_id', name='org_id',
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'), field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='label', model_name='label',
name='org_id', name='org_id',
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'), field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='node', model_name='node',
name='org_id', name='org_id',
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'), field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='systemuser', model_name='systemuser',
name='org_id', name='org_id',
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'), field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
), ),
] ]
...@@ -16,7 +16,7 @@ class Migration(migrations.Migration): ...@@ -16,7 +16,7 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='CommandFilter', name='CommandFilter',
fields=[ fields=[
('org_id', models.CharField(blank=True, default='', max_length=36, verbose_name='Organization')), ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=64, verbose_name='Name')), ('name', models.CharField(max_length=64, verbose_name='Name')),
('is_active', models.BooleanField(default=True, verbose_name='Is active')), ('is_active', models.BooleanField(default=True, verbose_name='Is active')),
...@@ -32,7 +32,7 @@ class Migration(migrations.Migration): ...@@ -32,7 +32,7 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='CommandFilterRule', name='CommandFilterRule',
fields=[ fields=[
('org_id', models.CharField(blank=True, default='', max_length=36, verbose_name='Organization')), ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('type', models.CharField(choices=[('regex', 'Regex'), ('command', 'Command')], default='command', max_length=16, verbose_name='Type')), ('type', models.CharField(choices=[('regex', 'Regex'), ('command', 'Command')], default='command', max_length=16, verbose_name='Type')),
('priority', models.IntegerField(default=50, help_text='1-100, the lower will be match first', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name='Priority')), ('priority', models.IntegerField(default=50, help_text='1-100, the lower will be match first', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name='Priority')),
......
...@@ -13,7 +13,6 @@ from django.db.models import Q ...@@ -13,7 +13,6 @@ from django.db.models import Q
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.core.cache import cache from django.core.cache import cache
from ..const import ASSET_ADMIN_CONN_CACHE_KEY
from .user import AdminUser, SystemUser from .user import AdminUser, SystemUser
from orgs.mixins import OrgModelMixin, OrgManager from orgs.mixins import OrgModelMixin, OrgManager
...@@ -75,63 +74,48 @@ class Asset(OrgModelMixin): ...@@ -75,63 +74,48 @@ class Asset(OrgModelMixin):
protocol = models.CharField(max_length=128, default=SSH_PROTOCOL, choices=PROTOCOL_CHOICES, verbose_name=_('Protocol')) protocol = models.CharField(max_length=128, default=SSH_PROTOCOL, choices=PROTOCOL_CHOICES, verbose_name=_('Protocol'))
port = models.IntegerField(default=22, verbose_name=_('Port')) 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, domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL)
related_name='assets', verbose_name=_("Domain"), nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes"))
on_delete=models.SET_NULL)
nodes = models.ManyToManyField('assets.Node', default=default_node,
related_name='assets',
verbose_name=_("Nodes"))
is_active = models.BooleanField(default=True, verbose_name=_('Is active')) is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
# Auth # Auth
admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, null=True, verbose_name=_("Admin user"))
null=True, verbose_name=_("Admin user"))
# Some information # Some information
public_ip = models.GenericIPAddressField(max_length=32, blank=True, null=True, verbose_name=_('Public IP')) 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')) number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number'))
# Collect # Collect
vendor = models.CharField(max_length=64, null=True, blank=True, vendor = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Vendor'))
verbose_name=_('Vendor')) model = models.CharField(max_length=54, null=True, blank=True, verbose_name=_('Model'))
model = models.CharField(max_length=54, null=True, blank=True, sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number'))
verbose_name=_('Model'))
sn = models.CharField(max_length=128, null=True, blank=True, cpu_model = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('CPU model'))
verbose_name=_('Serial number'))
cpu_model = models.CharField(max_length=64, null=True, blank=True,
verbose_name=_('CPU model'))
cpu_count = models.IntegerField(null=True, verbose_name=_('CPU count')) cpu_count = models.IntegerField(null=True, verbose_name=_('CPU count'))
cpu_cores = models.IntegerField(null=True, verbose_name=_('CPU cores')) cpu_cores = models.IntegerField(null=True, verbose_name=_('CPU cores'))
cpu_vcpus = models.IntegerField(null=True, verbose_name=_('CPU vcpus')) cpu_vcpus = models.IntegerField(null=True, verbose_name=_('CPU vcpus'))
memory = models.CharField(max_length=64, null=True, blank=True, memory = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Memory'))
verbose_name=_('Memory')) disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total'))
disk_total = models.CharField(max_length=1024, null=True, blank=True, disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info'))
verbose_name=_('Disk total'))
disk_info = models.CharField(max_length=1024, null=True, blank=True, os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS'))
verbose_name=_('Disk info')) os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version'))
os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch'))
os = models.CharField(max_length=128, null=True, blank=True, hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw'))
verbose_name=_('OS'))
os_version = models.CharField(max_length=16, null=True, blank=True, labels = models.ManyToManyField('assets.Label', blank=True, related_name='assets', verbose_name=_("Labels"))
verbose_name=_('OS version')) created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
os_arch = models.CharField(max_length=16, blank=True, null=True, date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
verbose_name=_('OS arch')) comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
hostname_raw = models.CharField(max_length=128, blank=True, null=True,
verbose_name=_('Hostname raw'))
labels = models.ManyToManyField('assets.Label', blank=True,
related_name='assets',
verbose_name=_("Labels"))
created_by = models.CharField(max_length=32, null=True, blank=True,
verbose_name=_('Created by'))
date_created = models.DateTimeField(auto_now_add=True, null=True,
blank=True,
verbose_name=_('Date created'))
comment = models.TextField(max_length=128, default='', blank=True,
verbose_name=_('Comment'))
objects = OrgManager.from_queryset(AssetQuerySet)() objects = OrgManager.from_queryset(AssetQuerySet)()
CONNECTIVITY_CACHE_KEY = '_JMS_ASSET_CONNECTIVITY_{}'
UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3)
CONNECTIVITY_CHOICES = (
(UNREACHABLE, _("Unreachable")),
(REACHABLE, _('Reachable')),
(UNKNOWN, _("Unknown")),
)
def __str__(self): def __str__(self):
return '{0.hostname}({0.ip})'.format(self) return '{0.hostname}({0.ip})'.format(self)
...@@ -197,25 +181,17 @@ class Asset(OrgModelMixin): ...@@ -197,25 +181,17 @@ class Asset(OrgModelMixin):
return '' return ''
@property @property
def is_connective(self): def connectivity(self):
if not self.is_unixlike(): if not self.is_unixlike():
return True return self.UNKNOWN
val = cache.get(ASSET_ADMIN_CONN_CACHE_KEY.format(self.hostname)) key = self.CONNECTIVITY_CACHE_KEY.format(str(self.id))
if val == 1: cached = cache.get(key, None)
return True return cached if cached is not None else self.UNKNOWN
else:
return False
def to_json(self): @connectivity.setter
info = { def connectivity(self, value):
'id': self.id, key = self.CONNECTIVITY_CACHE_KEY.format(str(self.id))
'hostname': self.hostname, cache.set(key, value, 3600*2)
'ip': self.ip,
'port': self.port,
}
if self.domain and self.domain.gateway_set.all():
info["gateways"] = [d.id for d in self.domain.gateway_set.all()]
return info
def get_auth_info(self): def get_auth_info(self):
if self.admin_user: if self.admin_user:
...@@ -236,11 +212,20 @@ class Asset(OrgModelMixin): ...@@ -236,11 +212,20 @@ class Asset(OrgModelMixin):
fake_node.is_node = False fake_node.is_node = False
return fake_node return fake_node
def to_json(self):
info = {
'id': self.id,
'hostname': self.hostname,
'ip': self.ip,
'port': self.port,
}
if self.domain and self.domain.gateway_set.all():
info["gateways"] = [d.id for d in self.domain.gateway_set.all()]
return info
def _to_secret_json(self): def _to_secret_json(self):
""" """
Ansible use it create inventory, First using asset user, Ansible use it create inventory
otherwise using cluster admin user
Todo: May be move to ops implements it Todo: May be move to ops implements it
""" """
data = self.to_json() data = self.to_json()
...@@ -255,6 +240,36 @@ class Asset(OrgModelMixin): ...@@ -255,6 +240,36 @@ class Asset(OrgModelMixin):
}) })
return data return data
def as_tree_node(self, parent_node):
from common.tree import TreeNode
icon_skin = 'file'
if self.platform.lower() == 'windows':
icon_skin = 'windows'
elif self.platform.lower() == 'linux':
icon_skin = 'linux'
data = {
'id': str(self.id),
'name': self.hostname,
'title': self.ip,
'pId': parent_node.key,
'isParent': False,
'open': False,
'iconSkin': icon_skin,
'meta': {
'type': 'asset',
'asset': {
'id': self.id,
'hostname': self.hostname,
'ip': self.ip,
'port': self.port,
'platform': self.platform,
'protocol': self.protocol,
}
}
}
tree_node = TreeNode(**data)
return tree_node
class Meta: class Meta:
unique_together = [('org_id', 'hostname')] unique_together = [('org_id', 'hostname')]
verbose_name = _("Asset") verbose_name = _("Asset")
......
...@@ -29,6 +29,13 @@ class AssetUser(OrgModelMixin): ...@@ -29,6 +29,13 @@ class AssetUser(OrgModelMixin):
date_updated = models.DateTimeField(auto_now=True) date_updated = models.DateTimeField(auto_now=True)
created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by')) created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3)
CONNECTIVITY_CHOICES = (
(UNREACHABLE, _("Unreachable")),
(REACHABLE, _('Reachable')),
(UNKNOWN, _("Unknown")),
)
@property @property
def password(self): def password(self):
if self._password: if self._password:
......
...@@ -5,6 +5,7 @@ import uuid ...@@ -5,6 +5,7 @@ import uuid
from django.db import models, transaction from django.db import models, transaction
from django.db.models import Q from django.db.models import Q
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext
from django.core.cache import cache from django.core.cache import cache
from orgs.mixins import OrgModelMixin from orgs.mixins import OrgModelMixin
...@@ -22,7 +23,9 @@ class Node(OrgModelMixin): ...@@ -22,7 +23,9 @@ class Node(OrgModelMixin):
date_create = models.DateTimeField(auto_now_add=True) date_create = models.DateTimeField(auto_now_add=True)
is_node = True is_node = True
_full_value_cache_key_prefix = '_NODE_VALUE_{}' _assets_amount = None
_full_value_cache_key = '_NODE_VALUE_{}'
_assets_amount_cache_key = '_NODE_ASSETS_AMOUNT_{}'
class Meta: class Meta:
verbose_name = _("Node") verbose_name = _("Node")
...@@ -49,30 +52,65 @@ class Node(OrgModelMixin): ...@@ -49,30 +52,65 @@ class Node(OrgModelMixin):
def name(self): def name(self):
return self.value return self.value
@property
def assets_amount(self):
"""
获取节点下所有资产数量速度太慢,所以需要重写,使用cache等方案
:return:
"""
if self._assets_amount is not None:
return self._assets_amount
cache_key = self._assets_amount_cache_key.format(self.key)
cached = cache.get(cache_key)
if cached is not None:
return cached
assets_amount = self.get_all_assets().count()
cache.set(cache_key, assets_amount, 3600)
return assets_amount
@assets_amount.setter
def assets_amount(self, value):
self._assets_amount = value
def expire_assets_amount(self):
ancestor_keys = self.get_ancestor_keys(with_self=True)
cache_keys = [self._assets_amount_cache_key.format(k) for k in ancestor_keys]
cache.delete_many(cache_keys)
@classmethod
def expire_nodes_assets_amount(cls, nodes=None):
if nodes:
for node in nodes:
node.expire_assets_amount()
return
key = cls._assets_amount_cache_key.format('*')
cache.delete_pattern(key)
@property @property
def full_value(self): def full_value(self):
key = self._full_value_cache_key_prefix.format(self.key) key = self._full_value_cache_key.format(self.key)
cached = cache.get(key) cached = cache.get(key)
if cached: if cached:
return 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(): if self.is_root():
return self.value return self.value
parent_full_value = self.parent.full_value parent_full_value = self.parent.full_value
value = parent_full_value + ' / ' + self.value value = parent_full_value + ' / ' + self.value
return value key = self._full_value_cache_key.format(self.key)
def cache_full_value(self, value):
key = self._full_value_cache_key_prefix.format(self.key)
cache.set(key, value, 3600) cache.set(key, value, 3600)
return value
def expire_full_value(self): def expire_full_value(self):
key = self._full_value_cache_key_prefix.format(self.key) key = self._full_value_cache_key.format(self.key)
cache.delete_pattern(key+'*')
@classmethod
def expire_nodes_full_value(cls, nodes=None):
if nodes:
for node in nodes:
node.expire_full_value()
return
key = cls._full_value_cache_key.format('*')
cache.delete_pattern(key+'*') cache.delete_pattern(key+'*')
@property @property
...@@ -85,6 +123,17 @@ class Node(OrgModelMixin): ...@@ -85,6 +123,17 @@ class Node(OrgModelMixin):
self.save() self.save()
return "{}:{}".format(self.key, mark) return "{}:{}".format(self.key, mark)
def get_next_child_preset_name(self):
name = ugettext("New node")
values = [
child.value[child.value.rfind(' '):]
for child in self.get_children()
if child.value.startswith(name)
]
values = [int(value) for value in values if value.strip().isdigit()]
count = max(values) + 1 if values else 1
return '{} {}'.format(name, count)
def create_child(self, value): def create_child(self, value):
with transaction.atomic(): with transaction.atomic():
child_key = self.get_next_child_key() child_key = self.get_next_child_key()
...@@ -134,7 +183,7 @@ class Node(OrgModelMixin): ...@@ -134,7 +183,7 @@ class Node(OrgModelMixin):
pattern = r'^{0}$|^{0}:'.format(self.key) pattern = r'^{0}$|^{0}:'.format(self.key)
args = [] args = []
kwargs = {} kwargs = {}
if self.is_default_node(): if self.is_root():
args.append(Q(nodes__key__regex=pattern) | Q(nodes=None)) args.append(Q(nodes__key__regex=pattern) | Q(nodes=None))
else: else:
kwargs['nodes__key__regex'] = pattern kwargs['nodes__key__regex'] = pattern
...@@ -182,17 +231,18 @@ class Node(OrgModelMixin): ...@@ -182,17 +231,18 @@ class Node(OrgModelMixin):
child.save() child.save()
self.save() self.save()
def get_ancestor(self, with_self=False): def get_ancestor_keys(self, with_self=False):
if self.is_root(): parent_keys = []
root = self.__class__.root() key_list = self.key.split(":")
return [root]
_key = self.key.split(':')
if not with_self: if not with_self:
_key.pop() key_list.pop()
ancestor_keys = [] for i in range(len(key_list)):
for i in range(len(_key)): parent_keys.append(":".join(key_list))
ancestor_keys.append(':'.join(_key)) key_list.pop()
_key.pop() return parent_keys
def get_ancestor(self, with_self=False):
ancestor_keys = self.get_ancestor_keys(with_self=with_self)
ancestor = self.__class__.objects.filter( ancestor = self.__class__.objects.filter(
key__in=ancestor_keys key__in=ancestor_keys
).order_by('key') ).order_by('key')
...@@ -227,9 +277,25 @@ class Node(OrgModelMixin): ...@@ -227,9 +277,25 @@ class Node(OrgModelMixin):
defaults = {'value': 'Default'} defaults = {'value': 'Default'}
return cls.objects.get_or_create(defaults=defaults, key='1') return cls.objects.get_or_create(defaults=defaults, key='1')
@classmethod def as_tree_node(self):
def get_tree_name_ref(cls): from common.tree import TreeNode
pass from ..serializers import NodeSerializer
name = '{} ({})'.format(self.value, self.assets_amount)
node_serializer = NodeSerializer(instance=self)
data = {
'id': self.key,
'name': name,
'title': name,
'pId': self.parent_key,
'isParent': True,
'open': self.is_root(),
'meta': {
'node': node_serializer.data,
'type': 'node'
}
}
tree_node = TreeNode(**data)
return tree_node
@classmethod @classmethod
def generate_fake(cls, count=100): def generate_fake(cls, count=100):
......
...@@ -14,7 +14,7 @@ from ..const import SYSTEM_USER_CONN_CACHE_KEY ...@@ -14,7 +14,7 @@ from ..const import SYSTEM_USER_CONN_CACHE_KEY
from .base import AssetUser from .base import AssetUser
__all__ = ['AdminUser', 'SystemUser',] __all__ = ['AdminUser', 'SystemUser']
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
signer = get_signer() signer = get_signer()
...@@ -31,6 +31,7 @@ class AdminUser(AssetUser): ...@@ -31,6 +31,7 @@ class AdminUser(AssetUser):
become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4) become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4)
become_user = models.CharField(default='root', max_length=64) become_user = models.CharField(default='root', max_length=64)
_become_pass = models.CharField(default='', max_length=128) _become_pass = models.CharField(default='', max_length=128)
CONNECTIVE_CACHE_KEY = '_JMS_ADMIN_USER_CONNECTIVE_{}'
def __str__(self): def __str__(self):
return self.name return self.name
...@@ -67,6 +68,23 @@ class AdminUser(AssetUser): ...@@ -67,6 +68,23 @@ class AdminUser(AssetUser):
def assets_amount(self): def assets_amount(self):
return self.get_related_assets().count() return self.get_related_assets().count()
@property
def connectivity(self):
from .asset import Asset
assets = self.get_related_assets().values_list('id', 'hostname', flat=True)
data = {
'unreachable': [],
'reachable': [],
}
for asset_id, hostname in assets:
key = Asset.CONNECTIVITY_CACHE_KEY.format(str(self.id))
value = cache.get(key, Asset.UNKNOWN)
if value == Asset.REACHABLE:
data['reachable'].append(hostname)
elif value == Asset.UNREACHABLE:
data['unreachable'].append(hostname)
return data
class Meta: class Meta:
ordering = ['name'] ordering = ['name']
unique_together = [('name', 'org_id')] unique_together = [('name', 'org_id')]
...@@ -94,34 +112,34 @@ class AdminUser(AssetUser): ...@@ -94,34 +112,34 @@ class AdminUser(AssetUser):
class SystemUser(AssetUser): class SystemUser(AssetUser):
SSH_PROTOCOL = 'ssh' PROTOCOL_SSH = 'ssh'
RDP_PROTOCOL = 'rdp' PROTOCOL_RDP = 'rdp'
TELNET_PROTOCOL = 'telnet' PROTOCOL_TELNET = 'telnet'
PROTOCOL_CHOICES = ( PROTOCOL_CHOICES = (
(SSH_PROTOCOL, 'ssh'), (PROTOCOL_SSH, 'ssh'),
(RDP_PROTOCOL, 'rdp'), (PROTOCOL_RDP, 'rdp'),
(TELNET_PROTOCOL, 'telnet (beta)'), (PROTOCOL_TELNET, 'telnet (beta)'),
) )
AUTO_LOGIN = 'auto' LOGIN_AUTO = 'auto'
MANUAL_LOGIN = 'manual' LOGIN_MANUAL = 'manual'
LOGIN_MODE_CHOICES = ( LOGIN_MODE_CHOICES = (
(AUTO_LOGIN, _('Automatic login')), (LOGIN_AUTO, _('Automatic login')),
(MANUAL_LOGIN, _('Manually login')) (LOGIN_MANUAL, _('Manually login'))
) )
nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes")) nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes"))
assets = models.ManyToManyField('assets.Asset', blank=True, verbose_name=_("Assets")) assets = models.ManyToManyField('assets.Asset', blank=True, verbose_name=_("Assets"))
priority = models.IntegerField(default=20, verbose_name=_("Priority"), priority = models.IntegerField(default=20, verbose_name=_("Priority"), validators=[MinValueValidator(1), MaxValueValidator(100)])
validators=[MinValueValidator(1), MaxValueValidator(100)])
protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol')) protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol'))
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push')) auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo')) sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo'))
shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell')) shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell'))
login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=AUTO_LOGIN, max_length=10, verbose_name=_('Login mode')) login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode'))
cmd_filters = models.ManyToManyField('CommandFilter', related_name='system_users', verbose_name=_("Command filter"), blank=True) cmd_filters = models.ManyToManyField('CommandFilter', related_name='system_users', verbose_name=_("Command filter"), blank=True)
cache_key = "__SYSTEM_USER_CACHED_{}" SYSTEM_USER_CACHE_KEY = "__SYSTEM_USER_CACHED_{}"
CONNECTIVE_CACHE_KEY = '_JMS_SYSTEM_USER_CONNECTIVE_{}'
def __str__(self): def __str__(self):
return '{0.name}({0.username})'.format(self) return '{0.name}({0.username})'.format(self)
...@@ -136,34 +154,61 @@ class SystemUser(AssetUser): ...@@ -136,34 +154,61 @@ class SystemUser(AssetUser):
'auto_push': self.auto_push, 'auto_push': self.auto_push,
} }
def get_assets(self): def get_related_assets(self):
assets = set(self.assets.all()) assets = set(self.assets.all())
return assets return assets
@property @property
def assets_connective(self): def connectivity(self):
_result = cache.get(SYSTEM_USER_CONN_CACHE_KEY.format(self.name), {}) cache_key = self.CONNECTIVE_CACHE_KEY.format(str(self.id))
return _result value = cache.get(cache_key, None)
if not value or 'unreachable' not in value:
return {'unreachable': [], 'reachable': []}
else:
return value
@connectivity.setter
def connectivity(self, value):
data = self.connectivity
unreachable = data['unreachable']
reachable = data['reachable']
for host in value.get('dark', {}).keys():
if host not in unreachable:
unreachable.append(host)
if host in reachable:
reachable.remove(host)
for host in value.get('contacted'):
if host not in reachable:
reachable.append(host)
if host in unreachable:
unreachable.remove(host)
cache_key = self.CONNECTIVE_CACHE_KEY.format(str(self.id))
cache.set(cache_key, data, 3600)
@property
def assets_unreachable(self):
return self.connectivity.get('unreachable')
@property @property
def unreachable_assets(self): def assets_reachable(self):
return list(self.assets_connective.get('dark', {}).keys()) return self.connectivity.get('reachable')
@property @property
def reachable_assets(self): def login_mode_display(self):
return self.assets_connective.get('contacted', []) return self.get_login_mode_display()
def is_need_push(self): def is_need_push(self):
if self.auto_push and self.protocol == self.__class__.SSH_PROTOCOL: if self.auto_push and self.protocol == self.PROTOCOL_SSH:
return True return True
else: else:
return False return False
def set_cache(self): def set_cache(self):
cache.set(self.cache_key.format(self.id), self, 3600) cache.set(self.SYSTEM_USER_CACHE_KEY.format(self.id), self, 3600)
def expire_cache(self): def expire_cache(self):
cache.delete(self.cache_key.format(self.id)) cache.delete(self.SYSTEM_USER_CACHE_KEY.format(self.id))
@property @property
def cmd_filter_rules(self): def cmd_filter_rules(self):
...@@ -184,7 +229,7 @@ class SystemUser(AssetUser): ...@@ -184,7 +229,7 @@ class SystemUser(AssetUser):
@classmethod @classmethod
def get_system_user_by_id_or_cached(cls, sid): def get_system_user_by_id_or_cached(cls, sid):
cached = cache.get(cls.cache_key.format(sid)) cached = cache.get(cls.SYSTEM_USER_CACHE_KEY.format(sid))
if cached: if cached:
return cached return cached
try: try:
......
...@@ -9,6 +9,7 @@ from .system_user import AssetSystemUserSerializer ...@@ -9,6 +9,7 @@ from .system_user import AssetSystemUserSerializer
__all__ = [ __all__ = [
'AssetSerializer', 'AssetGrantedSerializer', 'MyAssetGrantedSerializer', 'AssetSerializer', 'AssetGrantedSerializer', 'MyAssetGrantedSerializer',
'AssetAsNodeSerializer', 'AssetSimpleSerializer',
] ]
...@@ -22,14 +23,27 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer): ...@@ -22,14 +23,27 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
fields = '__all__' fields = '__all__'
validators = [] validators = []
@classmethod
def setup_eager_loading(cls, queryset):
""" Perform necessary eager loading of data. """
queryset = queryset.prefetch_related('labels', 'nodes')\
.select_related('admin_user')
return queryset
def get_field_names(self, declared_fields, info): def get_field_names(self, declared_fields, info):
fields = super().get_field_names(declared_fields, info) fields = super().get_field_names(declared_fields, info)
fields.extend([ fields.extend([
'hardware_info', 'is_connective', 'org_name' 'hardware_info', 'connectivity', 'org_name'
]) ])
return fields return fields
class AssetAsNodeSerializer(serializers.ModelSerializer):
class Meta:
model = Asset
fields = ['id', 'hostname', 'ip', 'port', 'platform', 'protocol']
class AssetGrantedSerializer(serializers.ModelSerializer): class AssetGrantedSerializer(serializers.ModelSerializer):
""" """
被授权资产的数据结构 被授权资产的数据结构
...@@ -64,3 +78,9 @@ class MyAssetGrantedSerializer(AssetGrantedSerializer): ...@@ -64,3 +78,9 @@ class MyAssetGrantedSerializer(AssetGrantedSerializer):
"is_active", "system_users_join", "org_name", "is_active", "system_users_join", "org_name",
"os", "platform", "comment", "org_id", "protocol" "os", "platform", "comment", "org_id", "protocol"
) )
class AssetSimpleSerializer(serializers.ModelSerializer):
class Meta:
model = Asset
fields = ['id', 'hostname', 'port', 'ip', 'connectivity']
...@@ -8,84 +8,33 @@ from .asset import AssetGrantedSerializer ...@@ -8,84 +8,33 @@ from .asset import AssetGrantedSerializer
__all__ = [ __all__ = [
'NodeSerializer', "NodeGrantedSerializer", "NodeAddChildrenSerializer", 'NodeSerializer', "NodeAddChildrenSerializer",
"NodeAssetsSerializer", "NodeAssetsSerializer",
] ]
class NodeGrantedSerializer(BulkSerializerMixin, serializers.ModelSerializer):
"""
授权资产组
"""
assets_granted = AssetGrantedSerializer(many=True, read_only=True)
assets_amount = serializers.SerializerMethodField()
parent = serializers.SerializerMethodField()
name = serializers.SerializerMethodField()
class Meta:
model = Node
fields = [
'id', 'key', 'name', 'value', 'parent',
'assets_granted', 'assets_amount', 'org_id',
]
@staticmethod
def get_assets_amount(obj):
return len(obj.assets_granted)
@staticmethod
def get_name(obj):
return obj.name
@staticmethod
def get_parent(obj):
return obj.parent.id
class NodeSerializer(serializers.ModelSerializer): class NodeSerializer(serializers.ModelSerializer):
assets_amount = serializers.SerializerMethodField() assets_amount = serializers.IntegerField(read_only=True)
tree_id = serializers.SerializerMethodField()
tree_parent = serializers.SerializerMethodField()
class Meta: class Meta:
model = Node model = Node
fields = [ fields = [
'id', 'key', 'value', 'assets_amount', 'id', 'key', 'value', 'assets_amount', 'org_id',
'is_node', 'org_id', 'tree_id', 'tree_parent', ]
read_only_fields = [
'id', 'key', 'assets_amount', 'org_id',
] ]
list_serializer_class = BulkListSerializer
def validate(self, data): def validate_value(self, data):
value = data.get('value')
instance = self.instance if self.instance else Node.root() instance = self.instance if self.instance else Node.root()
children = instance.parent.get_children().exclude(key=instance.key) children = instance.parent.get_children().exclude(key=instance.key)
values = [child.value for child in children] values = [child.value for child in children]
if value in values: if data in values:
raise serializers.ValidationError( raise serializers.ValidationError(
'The same level node name cannot be the same' 'The same level node name cannot be the same'
) )
return data return data
@staticmethod
def get_assets_amount(obj):
if hasattr(obj, 'assets_amount'):
return obj.assets_amount
return obj.get_all_assets().count()
@staticmethod
def get_tree_id(obj):
return obj.key
@staticmethod
def get_tree_parent(obj):
return obj.parent_key
def get_fields(self):
fields = super().get_fields()
field = fields["key"]
field.required = False
return fields
class NodeAssetsSerializer(serializers.ModelSerializer): class NodeAssetsSerializer(serializers.ModelSerializer):
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all()) assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
...@@ -97,3 +46,4 @@ class NodeAssetsSerializer(serializers.ModelSerializer): ...@@ -97,3 +46,4 @@ class NodeAssetsSerializer(serializers.ModelSerializer):
class NodeAddChildrenSerializer(serializers.Serializer): class NodeAddChildrenSerializer(serializers.Serializer):
nodes = serializers.ListField() nodes = serializers.ListField()
from rest_framework import serializers from rest_framework import serializers
from ..models import SystemUser from ..models import SystemUser, Asset
from .base import AuthSerializer from .base import AuthSerializer
...@@ -21,17 +21,17 @@ class SystemUserSerializer(serializers.ModelSerializer): ...@@ -21,17 +21,17 @@ class SystemUserSerializer(serializers.ModelSerializer):
def get_field_names(self, declared_fields, info): def get_field_names(self, declared_fields, info):
fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info) fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info)
fields.extend([ fields.extend([
'get_login_mode_display', 'login_mode_display',
]) ])
return fields return fields
@staticmethod @staticmethod
def get_unreachable_assets(obj): def get_unreachable_assets(obj):
return obj.unreachable_assets return obj.assets_unreachable
@staticmethod @staticmethod
def get_reachable_assets(obj): def get_reachable_assets(obj):
return obj.reachable_assets return obj.assets_reachable
def get_unreachable_amount(self, obj): def get_unreachable_amount(self, obj):
return len(self.get_unreachable_assets(obj)) return len(self.get_unreachable_assets(obj))
...@@ -41,7 +41,7 @@ class SystemUserSerializer(serializers.ModelSerializer): ...@@ -41,7 +41,7 @@ class SystemUserSerializer(serializers.ModelSerializer):
@staticmethod @staticmethod
def get_assets_amount(obj): def get_assets_amount(obj):
return len(obj.get_assets()) return len(obj.get_related_assets())
class SystemUserAuthSerializer(AuthSerializer): class SystemUserAuthSerializer(AuthSerializer):
...@@ -75,4 +75,7 @@ class SystemUserSimpleSerializer(serializers.ModelSerializer): ...@@ -75,4 +75,7 @@ class SystemUserSimpleSerializer(serializers.ModelSerializer):
""" """
class Meta: class Meta:
model = SystemUser model = SystemUser
fields = ('id', 'name', 'username') fields = ('id', 'name', 'username')
\ No newline at end of file
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from collections import defaultdict from collections import defaultdict
from django.db.models.signals import post_save, m2m_changed from django.db.models.signals import post_save, m2m_changed, post_delete
from django.dispatch import receiver from django.dispatch import receiver
from common.utils import get_logger from common.utils import get_logger
from .models import Asset, SystemUser, Node from .models import Asset, SystemUser, Node
from .tasks import update_assets_hardware_info_util, \ from .tasks import update_assets_hardware_info_util, \
test_asset_connectability_util, push_system_user_to_assets test_asset_connectivity_util, push_system_user_to_assets
logger = get_logger(__file__) logger = get_logger(__file__)
...@@ -19,8 +19,8 @@ def update_asset_hardware_info_on_created(asset): ...@@ -19,8 +19,8 @@ def update_asset_hardware_info_on_created(asset):
def test_asset_conn_on_created(asset): def test_asset_conn_on_created(asset):
logger.debug("Test asset `{}` connectability".format(asset)) logger.debug("Test asset `{}` connectivity".format(asset))
test_asset_connectability_util.delay([asset]) test_asset_connectivity_util.delay([asset])
def set_asset_root_node(asset): def set_asset_root_node(asset):
...@@ -35,6 +35,17 @@ def on_asset_created_or_update(sender, instance=None, created=False, **kwargs): ...@@ -35,6 +35,17 @@ def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
update_asset_hardware_info_on_created(instance) update_asset_hardware_info_on_created(instance)
test_asset_conn_on_created(instance) test_asset_conn_on_created(instance)
# 过期节点资产数量
nodes = instance.nodes.all()
Node.expire_nodes_assets_amount(nodes)
@receiver(post_delete, sender=Asset, dispatch_uid="my_unique_identifier")
def on_asset_delete(sender, instance=None, **kwargs):
# 过期节点资产数量
nodes = instance.nodes.all()
Node.expire_nodes_assets_amount(nodes)
@receiver(post_save, sender=SystemUser, dispatch_uid="my_unique_identifier") @receiver(post_save, sender=SystemUser, dispatch_uid="my_unique_identifier")
def on_system_user_update(sender, instance=None, created=True, **kwargs): def on_system_user_update(sender, instance=None, created=True, **kwargs):
...@@ -63,10 +74,11 @@ def on_system_user_assets_change(sender, instance=None, **kwargs): ...@@ -63,10 +74,11 @@ def on_system_user_assets_change(sender, instance=None, **kwargs):
@receiver(m2m_changed, sender=Asset.nodes.through) @receiver(m2m_changed, sender=Asset.nodes.through)
def on_asset_node_changed(sender, instance=None, **kwargs): def on_asset_node_changed(sender, instance=None, **kwargs):
logger.debug("Asset node change signal received")
if isinstance(instance, Asset): if isinstance(instance, Asset):
if kwargs['action'] == 'post_add': if kwargs['action'] == 'post_add':
logger.debug("Asset node change signal received")
nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set']) nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
Node.expire_nodes_assets_amount(nodes)
system_users_assets = defaultdict(set) system_users_assets = defaultdict(set)
system_users = SystemUser.objects.filter(nodes__in=nodes) system_users = SystemUser.objects.filter(nodes__in=nodes)
# 清理节点缓存 # 清理节点缓存
...@@ -79,9 +91,11 @@ def on_asset_node_changed(sender, instance=None, **kwargs): ...@@ -79,9 +91,11 @@ def on_asset_node_changed(sender, instance=None, **kwargs):
@receiver(m2m_changed, sender=Asset.nodes.through) @receiver(m2m_changed, sender=Asset.nodes.through)
def on_node_assets_changed(sender, instance=None, **kwargs): def on_node_assets_changed(sender, instance=None, **kwargs):
if isinstance(instance, Node): if isinstance(instance, Node):
logger.debug("Node assets change signal received")
# 当节点和资产关系发生改变时,过期资产数量缓存
instance.expire_assets_amount()
assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set']) assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
if kwargs['action'] == 'post_add': if kwargs['action'] == 'post_add':
logger.debug("Node assets change signal received")
# 重新关联系统用户和资产的关系 # 重新关联系统用户和资产的关系
system_users = SystemUser.objects.filter(nodes=instance) system_users = SystemUser.objects.filter(nodes=instance)
for system_user in system_users: for system_user in system_users:
......
...@@ -26,6 +26,23 @@ disk_pattern = re.compile(r'^hd|sd|xvd|vd') ...@@ -26,6 +26,23 @@ disk_pattern = re.compile(r'^hd|sd|xvd|vd')
PERIOD_TASK = os.environ.get("PERIOD_TASK", "off") PERIOD_TASK = os.environ.get("PERIOD_TASK", "off")
def clean_hosts(assets):
clean_assets = []
for asset in assets:
if not asset.is_active:
msg = _("Asset has been disabled, skipped: {}").format(asset)
logger.info(msg)
continue
if not asset.support_ansible():
msg = _("Asset may not be support ansible, skipped: {}").format(asset)
logger.info(msg)
continue
clean_assets.append(asset)
if not clean_assets:
logger.info(_("No assets matched, stop task"))
return clean_assets
@shared_task @shared_task
def set_assets_hardware_info(assets, result, **kwargs): def set_assets_hardware_info(assets, result, **kwargs):
""" """
...@@ -60,9 +77,12 @@ def set_assets_hardware_info(assets, result, **kwargs): ...@@ -60,9 +77,12 @@ def set_assets_hardware_info(assets, result, **kwargs):
___cpu_model = 'Unknown' ___cpu_model = 'Unknown'
___cpu_model = ___cpu_model[:64] ___cpu_model = ___cpu_model[:64]
___cpu_count = info.get('ansible_processor_count', 0) ___cpu_count = info.get('ansible_processor_count', 0)
___cpu_cores = info.get('ansible_processor_cores', None) or len(info.get('ansible_processor', [])) ___cpu_cores = info.get('ansible_processor_cores', None) or \
len(info.get('ansible_processor', []))
___cpu_vcpus = info.get('ansible_processor_vcpus', 0) ___cpu_vcpus = info.get('ansible_processor_vcpus', 0)
___memory = '%s %s' % capacity_convert('{} MB'.format(info.get('ansible_memtotal_mb'))) ___memory = '%s %s' % capacity_convert(
'{} MB'.format(info.get('ansible_memtotal_mb'))
)
disk_info = {} disk_info = {}
for dev, dev_info in info.get('ansible_devices', {}).items(): for dev, dev_info in info.get('ansible_devices', {}).items():
if disk_pattern.match(dev) and dev_info['removable'] == '0': if disk_pattern.match(dev) and dev_info['removable'] == '0':
...@@ -96,19 +116,8 @@ def update_assets_hardware_info_util(assets, task_name=None): ...@@ -96,19 +116,8 @@ def update_assets_hardware_info_util(assets, task_name=None):
if task_name is None: if task_name is None:
task_name = _("Update some assets hardware info") task_name = _("Update some assets hardware info")
tasks = const.UPDATE_ASSETS_HARDWARE_TASKS tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
hosts = [] hosts = clean_hosts(assets)
for asset in assets:
if not asset.is_active:
msg = _("Asset has been disabled, skipped: {}").format(asset)
logger.info(msg)
continue
if not asset.support_ansible():
msg = _("Asset may not be support ansible, skipped: {}").format(asset)
logger.info(msg)
continue
hosts.append(asset)
if not hosts: if not hosts:
logger.info(_("No assets matched, stop task"))
return {} return {}
created_by = str(assets[0].org_id) created_by = str(assets[0].org_id)
task, created = update_or_create_ansible_task( task, created = update_or_create_ansible_task(
...@@ -125,7 +134,6 @@ def update_assets_hardware_info_util(assets, task_name=None): ...@@ -125,7 +134,6 @@ def update_assets_hardware_info_util(assets, task_name=None):
@shared_task @shared_task
def update_asset_hardware_info_manual(asset): def update_asset_hardware_info_manual(asset):
task_name = _("Update asset hardware info: {}").format(asset.hostname) task_name = _("Update asset hardware info: {}").format(asset.hostname)
# task_name = _("更新资产硬件信息")
return update_assets_hardware_info_util( return update_assets_hardware_info_util(
[asset], task_name=task_name [asset], task_name=task_name
) )
...@@ -141,166 +149,92 @@ def update_assets_hardware_info_period(): ...@@ -141,166 +149,92 @@ def update_assets_hardware_info_period():
logger.debug("Period task disabled, update assets hardware info pass") logger.debug("Period task disabled, update assets hardware info pass")
return return
from ops.utils import update_or_create_ansible_task
from orgs.models import Organization
orgs = Organization.objects.all().values_list('id', flat=True)
orgs.append('')
task_name = _("Update assets hardware info period")
# for org_id in orgs:
# org_id = str(org_id)
# hostname_list = [
# asset for asset in Asset.objects.all()
# if asset.is_active and asset.is_unixlike()
# ]
# tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
#
# # Only create, schedule by celery beat
# update_or_create_ansible_task(
# task_name, hosts=hostname_list, tasks=tasks, pattern='all',
# options=const.TASK_OPTIONS, run_as_admin=True, created_by='System',
# interval=60*60*24, is_periodic=True, callback=set_assets_hardware_info.name,
# )
## ADMIN USER CONNECTIVE ## ## ADMIN USER CONNECTIVE ##
def set_admin_user_connectability_info(result, **kwargs):
admin_user = kwargs.get("admin_user")
task_name = kwargs.get("task_name")
if admin_user is None and task_name is not None:
admin_user = task_name.split(":")[-1]
raw, summary = result @shared_task
cache_key = const.ADMIN_USER_CONN_CACHE_KEY.format(admin_user) def test_asset_connectivity_util(assets, task_name=None):
cache.set(cache_key, summary, CACHE_MAX_TIME) from ops.utils import update_or_create_ansible_task
for i in summary.get('contacted', []): if task_name is None:
asset_conn_cache_key = const.ASSET_ADMIN_CONN_CACHE_KEY.format(i) task_name = _("Test assets connectivity")
cache.set(asset_conn_cache_key, 1, CACHE_MAX_TIME) hosts = clean_hosts(assets)
if not hosts:
return {}
tasks = const.TEST_ADMIN_USER_CONN_TASKS
created_by = assets[0].org_id
task, created = update_or_create_ansible_task(
task_name=task_name, hosts=hosts, tasks=tasks, pattern='all',
options=const.TASK_OPTIONS, run_as_admin=True, created_by=created_by,
)
result = task.run()
summary = result[1]
for asset in assets:
if asset.hostname in summary.get('dark', {}):
asset.connectivity = asset.UNREACHABLE
elif asset.hostname in summary.get('contacted', []):
asset.connectivity = asset.REACHABLE
else:
asset.connectivity = asset.UNKNOWN
return summary
for i, msg in summary.get('dark', {}).items():
asset_conn_cache_key = const.ASSET_ADMIN_CONN_CACHE_KEY.format(i) @shared_task
cache.set(asset_conn_cache_key, 0, CACHE_MAX_TIME) def test_asset_connectivity_manual(asset):
logger.error(msg) task_name = _("Test assets connectivity: {}").format(asset)
summary = test_asset_connectivity_util([asset], task_name=task_name)
if summary.get('dark'):
return False, summary['dark']
else:
return True, ""
@shared_task @shared_task
def test_admin_user_connectability_util(admin_user, task_name): def test_admin_user_connectivity_util(admin_user, task_name):
""" """
Test asset admin user can connect or not. Using ansible api do that Test asset admin user can connect or not. Using ansible api do that
:param admin_user: :param admin_user:
:param task_name: :param task_name:
:return: :return:
""" """
from ops.utils import update_or_create_ansible_task
assets = admin_user.get_related_assets() assets = admin_user.get_related_assets()
hosts = [] hosts = clean_hosts(assets)
for asset in assets:
if not asset.is_active:
msg = _("Asset has been disabled, skipped: {}").format(asset)
logger.info(msg)
continue
if not asset.support_ansible():
msg = _("Asset may not be support ansible, skipped: {}").format(asset)
logger.info(msg)
continue
hosts.append(asset)
if not hosts: if not hosts:
logger.info(_("No assets matched, stop task"))
return {} return {}
tasks = const.TEST_ADMIN_USER_CONN_TASKS summary = test_asset_connectivity_util(hosts, task_name)
task, created = update_or_create_ansible_task( return summary
task_name=task_name, hosts=hosts, tasks=tasks, pattern='all',
options=const.TASK_OPTIONS, run_as_admin=True, created_by=admin_user.org_id,
)
result = task.run()
set_admin_user_connectability_info(result, admin_user=admin_user.name)
return result
@shared_task @shared_task
@register_as_period_task(interval=3600) @register_as_period_task(interval=3600)
def test_admin_user_connectability_period(): def test_admin_user_connectivity_period():
""" """
A period task that update the ansible task period A period task that update the ansible task period
""" """
admin_users = AdminUser.objects.all() admin_users = AdminUser.objects.all()
for admin_user in admin_users: for admin_user in admin_users:
task_name = _("Test admin user connectability period: {}").format(admin_user.name) task_name = _("Test admin user connectivity period: {}").format(admin_user.name)
test_admin_user_connectability_util(admin_user, task_name) test_admin_user_connectivity_util(admin_user, task_name)
@shared_task @shared_task
def test_admin_user_connectability_manual(admin_user): def test_admin_user_connectivity_manual(admin_user):
task_name = _("Test admin user connectability: {}").format(admin_user.name) task_name = _("Test admin user connectivity: {}").format(admin_user.name)
# task_name = _("测试管理行号可连接性: {}").format(admin_user.name) return test_admin_user_connectivity_util(admin_user, task_name)
return test_admin_user_connectability_util(admin_user, task_name)
@shared_task
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 = []
for asset in assets:
if not asset.is_active:
msg = _("Asset has been disabled, skip: {}").format(asset)
logger.info(msg)
continue
if not asset.support_ansible():
msg = _("Asset may not be support ansible, skip: {}").format(asset)
logger.info(msg)
continue
hosts.append(asset)
if not hosts:
logger.info(_("No assets, task stop"))
return {}
tasks = const.TEST_ADMIN_USER_CONN_TASKS
created_by = assets[0].org_id
task, created = update_or_create_ansible_task(
task_name=task_name, hosts=hosts, tasks=tasks, pattern='all',
options=const.TASK_OPTIONS, run_as_admin=True, created_by=created_by,
)
result = task.run()
summary = result[1]
for k in summary.get('dark'):
cache.set(const.ASSET_ADMIN_CONN_CACHE_KEY.format(k), 0, CACHE_MAX_TIME)
for k in summary.get('contacted'):
cache.set(const.ASSET_ADMIN_CONN_CACHE_KEY.format(k), 1, CACHE_MAX_TIME)
return summary
@shared_task
def test_asset_connectability_manual(asset):
task_name = _("Test assets connectability: {}").format(asset)
summary = test_asset_connectability_util([asset], task_name=task_name)
if summary.get('dark'):
return False, summary['dark']
else:
return True, ""
## System user connective ## ## System user connective ##
@shared_task @shared_task
def set_system_user_connectablity_info(result, **kwargs): def set_system_user_connectivity_info(system_user, result):
summary = result[1] summary = result[1]
task_name = kwargs.get("task_name") system_user.connectivity = summary
system_user = kwargs.get("system_user")
if system_user is None:
system_user = task_name.split(":")[-1]
cache_key = const.SYSTEM_USER_CONN_CACHE_KEY.format(str(system_user.id))
cache.set(cache_key, summary, CACHE_MAX_TIME)
@shared_task @shared_task
def test_system_user_connectability_util(system_user, assets, task_name): def test_system_user_connectivity_util(system_user, assets, task_name):
""" """
Test system cant connect his assets or not. Test system cant connect his assets or not.
:param system_user: :param system_user:
...@@ -309,20 +243,9 @@ def test_system_user_connectability_util(system_user, assets, task_name): ...@@ -309,20 +243,9 @@ def test_system_user_connectability_util(system_user, assets, task_name):
:return: :return:
""" """
from ops.utils import update_or_create_ansible_task from ops.utils import update_or_create_ansible_task
hosts = []
tasks = const.TEST_SYSTEM_USER_CONN_TASKS tasks = const.TEST_SYSTEM_USER_CONN_TASKS
for asset in assets: hosts = clean_hosts(assets)
if not asset.is_active:
msg = _("Asset has been disabled, skip: {}").format(asset)
logger.info(msg)
continue
if not asset.support_ansible():
msg = _("Asset may not be support ansible, skip: {}").format(asset)
logger.info(msg)
continue
hosts.append(asset)
if not hosts: if not hosts:
logger.info(_("No assets matched, stop task"))
return {} return {}
task, created = update_or_create_ansible_task( task, created = update_or_create_ansible_task(
task_name, hosts=hosts, tasks=tasks, pattern='all', task_name, hosts=hosts, tasks=tasks, pattern='all',
...@@ -330,35 +253,35 @@ def test_system_user_connectability_util(system_user, assets, task_name): ...@@ -330,35 +253,35 @@ def test_system_user_connectability_util(system_user, assets, task_name):
run_as=system_user, created_by=system_user.org_id, run_as=system_user, created_by=system_user.org_id,
) )
result = task.run() result = task.run()
set_system_user_connectablity_info(result, system_user=system_user) set_system_user_connectivity_info(system_user, result)
return result return result
@shared_task @shared_task
def test_system_user_connectability_manual(system_user): def test_system_user_connectivity_manual(system_user):
task_name = _("Test system user connectability: {}").format(system_user) task_name = _("Test system user connectivity: {}").format(system_user)
assets = system_user.get_assets() assets = system_user.get_related_assets()
return test_system_user_connectability_util(system_user, assets, task_name) return test_system_user_connectivity_util(system_user, assets, task_name)
@shared_task @shared_task
def test_system_user_connectability_a_asset(system_user, asset): def test_system_user_connectivity_a_asset(system_user, asset):
task_name = _("Test system user connectability: {} => {}").format( task_name = _("Test system user connectivity: {} => {}").format(
system_user, asset system_user, asset
) )
return test_system_user_connectability_util(system_user, [asset], task_name) return test_system_user_connectivity_util(system_user, [asset], task_name)
@shared_task @shared_task
def test_system_user_connectability_period(): def test_system_user_connectivity_period():
if PERIOD_TASK != "on": if PERIOD_TASK != "on":
logger.debug("Period task disabled, test system user connectability pass") logger.debug("Period task disabled, test system user connectivity pass")
return return
system_users = SystemUser.objects.all() system_users = SystemUser.objects.all()
for system_user in system_users: for system_user in system_users:
task_name = _("Test system user connectability period: {}").format(system_user) task_name = _("Test system user connectivity period: {}").format(system_user)
# task_name = _("定期测试系统用户可连接性: {}".format(system_user)) assets = system_user.get_related_assets()
test_system_user_connectability_util(system_user, task_name) test_system_user_connectivity_util(system_user, assets, task_name)
#### Push system user tasks #### #### Push system user tasks ####
...@@ -380,6 +303,24 @@ def get_push_system_user_tasks(system_user): ...@@ -380,6 +303,24 @@ def get_push_system_user_tasks(system_user):
), ),
} }
}) })
tasks.extend([
{
'name': 'Check home dir exists',
'action': {
'module': 'stat',
'args': 'path=/home/{}'.format(system_user.username)
},
'register': 'home_existed'
},
{
'name': "Set home dir permission",
'action': {
'module': 'file',
'args': "path=/home/{0} owner={0} group={0} mode=700".format(system_user.username)
},
'when': 'home_existed.stat.exists == true'
}
])
if system_user.public_key: if system_user.public_key:
tasks.append({ tasks.append({
'name': 'Set {} authorized key'.format(system_user.username), 'name': 'Set {} authorized key'.format(system_user.username),
...@@ -416,19 +357,8 @@ def push_system_user_util(system_user, assets, task_name): ...@@ -416,19 +357,8 @@ def push_system_user_util(system_user, assets, task_name):
return return
tasks = get_push_system_user_tasks(system_user) tasks = get_push_system_user_tasks(system_user)
hosts = [] hosts = clean_hosts(assets)
for asset in assets:
if not asset.is_active:
msg = _("Asset has been disabled, skip: {}").format(asset)
logger.info(msg)
continue
if not asset.support_ansible():
msg = _("Asset may not be support ansible, skip: {}").format(asset)
logger.info(msg)
continue
hosts.append(asset)
if not hosts: if not hosts:
logger.info(_("No assets matched, stop task"))
return {} return {}
task, created = update_or_create_ansible_task( task, created = update_or_create_ansible_task(
task_name=task_name, hosts=hosts, tasks=tasks, pattern='all', task_name=task_name, hosts=hosts, tasks=tasks, pattern='all',
...@@ -440,7 +370,7 @@ def push_system_user_util(system_user, assets, task_name): ...@@ -440,7 +370,7 @@ def push_system_user_util(system_user, assets, task_name):
@shared_task @shared_task
def push_system_user_to_assets_manual(system_user): def push_system_user_to_assets_manual(system_user):
assets = system_user.get_assets() assets = system_user.get_related_assets()
task_name = _("Push system users to assets: {}").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) return push_system_user_util(system_user, assets, task_name=task_name)
...@@ -459,6 +389,18 @@ def push_system_user_to_assets(system_user, assets): ...@@ -459,6 +389,18 @@ def push_system_user_to_assets(system_user, assets):
return push_system_user_util(system_user, assets, task_name) return push_system_user_util(system_user, assets, task_name)
@shared_task
@after_app_shutdown_clean
def test_system_user_connectability_period():
pass
@shared_task
@after_app_shutdown_clean
def test_admin_user_connectability_period():
pass
# @shared_task # @shared_task
# @register_as_period_task(interval=3600) # @register_as_period_task(interval=3600)
# @after_app_ready_start # @after_app_ready_start
......
...@@ -57,6 +57,10 @@ ...@@ -57,6 +57,10 @@
<script> <script>
var zTree2, asset_table2 = 0; var zTree2, asset_table2 = 0;
function initTable2() { function initTable2() {
if(asset_table2){
return
}
var options = { var options = {
ele: $('#asset_list_modal_table'), ele: $('#asset_list_modal_table'),
ajax_url: '{% url "api-assets:asset-list" %}?show_current_asset=1', ajax_url: '{% url "api-assets:asset-list" %}?show_current_asset=1',
...@@ -71,14 +75,14 @@ function initTable2() { ...@@ -71,14 +75,14 @@ function initTable2() {
function onSelected2(event, treeNode) { function onSelected2(event, treeNode) {
var url = asset_table2.ajax.url(); var url = asset_table2.ajax.url();
url = setUrlParam(url, "node_id", treeNode.node_id); url = setUrlParam(url, "node_id", treeNode.meta.node.id);
setCookie('node_selected', treeNode.id);
asset_table2.ajax.url(url); asset_table2.ajax.url(url);
asset_table2.ajax.reload(); asset_table2.ajax.reload();
} }
function initTree2() { function initTree2() {
var url = '{% url 'api-assets:node-children-tree' %}?assets=0';
var setting = { var setting = {
view: { view: {
dblClickExpand: false, dblClickExpand: false,
...@@ -89,33 +93,22 @@ function initTree2() { ...@@ -89,33 +93,22 @@ function initTree2() {
enable: true enable: true
} }
}, },
async: {
enable: true,
url: url,
autoParam: ["id=key", "name=n", "level=lv"],
type: 'get'
},
callback: { callback: {
onSelected: onSelected2 onSelected: onSelected2
} }
}; };
zTree2 = $.fn.zTree.init($("#assetTree2"), setting);
var zNodes = [];
$.get("{% url 'api-assets:node-list' %}", function(data, status){
$.each(data, function (index, value) {
value["node_id"] = value["id"];
value["id"] = value["tree_id"];
value["pId"] = value["tree_parent"];
{#value["open"] = true;#}
if (value["key"] === "0") {
value["open"] = true;
}
value["name"] = value["value"] + ' (' + value['assets_amount'] + ')';
});
zNodes = data;
$.fn.zTree.init($("#assetTree2"), setting, zNodes);
zTree2 = $.fn.zTree.getZTreeObj("assetTree2");
var root = zTree2.getNodes()[0];
zTree2.expandNode(root);
});
} }
$(document).ready(function(){ $(document).ready(function(){
}).on('show.bs.modal', function () {
initTable2(); initTable2();
initTree2(); initTree2();
}) })
......
...@@ -45,13 +45,11 @@ ...@@ -45,13 +45,11 @@
<table class="table table-striped table-bordered table-hover" id="asset_list_table"> <table class="table table-striped table-bordered table-hover" id="asset_list_table">
<thead> <thead>
<tr> <tr>
<th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" >
</th>
<th>{% trans 'Hostname' %}</th> <th>{% trans 'Hostname' %}</th>
<th>{% trans 'IP' %}</th> <th>{% trans 'IP' %}</th>
<th>{% trans 'Port' %}</th> <th>{% trans 'Port' %}</th>
<th>{% trans 'Reachable' %}</th> <th>{% trans 'Reachable' %}</th>
<th>{% trans 'Action' %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
...@@ -91,26 +89,36 @@ ...@@ -91,26 +89,36 @@
<script> <script>
function initTable() { function initTable() {
var reachable = {{ admin_user.REACHABLE }};
var unreachable = {{ admin_user.UNREACHABLE }};
var options = { var options = {
ele: $('#asset_list_table'), ele: $('#asset_list_table'),
buttons: [], buttons: [],
order: [], order: [],
columnDefs: [ columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) { {targets: 0, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url "assets:asset-detail" pk=DEFAULT_PK %}" data-aid="'+rowData.id+'">' + cellData + '</a>'; var detail_btn = '<a href="{% url "assets:asset-detail" pk=DEFAULT_PK %}" data-aid="'+rowData.id+'">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}}, }},
{targets: 4, createdCell: function (td, cellData) { {targets: 3, createdCell: function (td, cellData) {
if (!cellData) { if (cellData === unreachable) {
$(td).html('<i class="fa fa-times text-danger"></i>') $(td).html('<i class="fa fa-times text-danger"></i>')
} else { } else if (cellData === reachable) {
$(td).html('<i class="fa fa-check text-navy"></i>') $(td).html('<i class="fa fa-check text-navy"></i>')
} } else {
}}], $(td).html('')
ajax_url: '{% url "api-assets:asset-list" %}?admin_user_id={{ admin_user.id }}', }
}},
{targets: 4, createdCell: function (td, cellData) {
var test_btn = ' <a class="btn btn-xs btn-info btn-test-asset" data-uid="{{ DEFAULT_PK }}" >{% trans "Test" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
$(td).html(test_btn);
}}
],
ajax_url: '{% url "api-assets:admin-user-assets" pk=admin_user.id %}',
columns: [ columns: [
{data: function(){return ""}}, {data: "hostname" }, {data: "ip" }, {data: "hostname" }, {data: "ip" },
{data: "port" }, {data: "is_connective" }], {data: "port" }, {data: "connectivity" }, {data: "id"}],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
jumpserver.initServerSideDataTable(options); jumpserver.initServerSideDataTable(options);
...@@ -119,6 +127,21 @@ function initTable() { ...@@ -119,6 +127,21 @@ function initTable() {
$(document).ready(function () { $(document).ready(function () {
initTable(); initTable();
}) })
.on('click', '.btn-test-asset', function () {
var asset_id = $(this).data('uid');
var the_url = "{% url 'api-assets:asset-alive-test' pk=DEFAULT_PK %}".replace('{{ DEFAULT_PK }}', asset_id);
var success = function (data) {
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
APIUpdateAttr({
url: the_url,
method: 'GET',
success: success,
flash_message: false
});
})
.on('click', '.btn-test-connective', function () { .on('click', '.btn-test-connective', function () {
var the_url = "{% url 'api-assets:admin-user-connective' pk=admin_user.id %}"; var the_url = "{% url 'api-assets:admin-user-connective' pk=admin_user.id %}";
var success = function (data) { var success = function (data) {
......
...@@ -136,6 +136,7 @@ ...@@ -136,6 +136,7 @@
<li class="divider"></li> <li class="divider"></li>
<li id="show_current_asset" class="btn-show-current-asset" style="display: none;" tabindex="-1"><a><i class="fa fa-hand-o-up"></i> {% trans 'Display only current node assets' %}</a></li> <li id="show_current_asset" class="btn-show-current-asset" style="display: none;" tabindex="-1"><a><i class="fa fa-hand-o-up"></i> {% trans 'Display only current node assets' %}</a></li>
<li id="show_all_asset" class="btn-show-all-asset" style="display: none;" tabindex="-1"><a><i class="fa fa-th"></i> {% trans 'Displays all child node assets' %}</a></li> <li id="show_all_asset" class="btn-show-all-asset" style="display: none;" tabindex="-1"><a><i class="fa fa-th"></i> {% trans 'Displays all child node assets' %}</a></li>
<li id="fresh_tree" class="btn-refresh-tree" tabindex="-1"><a><i class="fa fa-refresh"></i> {% trans 'Refresh' %}</a></li>
</ul> </ul>
</div> </div>
...@@ -147,6 +148,8 @@ ...@@ -147,6 +148,8 @@
<script> <script>
var zTree, rMenu, asset_table, show = 0; var zTree, rMenu, asset_table, show = 0;
var update_node_action = ""; var update_node_action = "";
var current_node_id = null;
var current_node = null;
function initTable() { function initTable() {
var options = { var options = {
ele: $('#asset_list_table'), ele: $('#asset_list_table'),
...@@ -191,18 +194,20 @@ function addTreeNode() { ...@@ -191,18 +194,20 @@ function addTreeNode() {
if (!parentNode){ if (!parentNode){
return return
} }
var url = "{% url 'api-assets:node-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", parentNode.node_id ); var url = "{% url 'api-assets:node-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", parentNode.meta.node.id);
$.post(url, {}, function (data, status){ $.post(url, {}, function (data, status){
if (status === "success") { if (status === "success") {
var newNode = { var newNode = {
id: data["key"], id: data["key"],
name: data["value"], name: data["value"],
node_id: data["id"], pId: parentNode.id,
pId: parentNode.id meta: {
"node": data
}
}; };
newNode.checked = zTree.getSelectedNodes()[0].checked; newNode.checked = zTree.getSelectedNodes()[0].checked;
zTree.addNodes(parentNode, 0, newNode); zTree.addNodes(parentNode, 0, newNode);
var node = zTree.getNodeByParam('id', newNode.node_id, parentNode); var node = zTree.getNodeByParam('id', newNode.id, parentNode);
zTree.editName(node); zTree.editName(node);
} else { } else {
alert("{% trans 'Create node failed' %}") alert("{% trans 'Create node failed' %}")
...@@ -218,10 +223,10 @@ function removeTreeNode() { ...@@ -218,10 +223,10 @@ function removeTreeNode() {
} }
if (current_node.children && current_node.children.length > 0) { if (current_node.children && current_node.children.length > 0) {
toastr.error("{% trans 'Have child node, cancel' %}"); toastr.error("{% trans 'Have child node, cancel' %}");
} else if (current_node.assets_amount !== 0) { } else if (current_node.meta.node.assets_amount !== 0) {
toastr.error("{% trans 'Have assets, cancel' %}"); toastr.error("{% trans 'Have assets, cancel' %}");
} else { } else {
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.node_id ); var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node_id);
$.ajax({ $.ajax({
url: url, url: url,
method: "DELETE", method: "DELETE",
...@@ -238,8 +243,8 @@ function editTreeNode() { ...@@ -238,8 +243,8 @@ function editTreeNode() {
if (!current_node){ if (!current_node){
return return
} }
if (current_node.value) { if (current_node) {
current_node.name = current_node.value; current_node.name = current_node.meta.node.value;
} }
zTree.editName(current_node); zTree.editName(current_node);
} }
...@@ -281,7 +286,7 @@ function onBodyMouseDown(event){ ...@@ -281,7 +286,7 @@ function onBodyMouseDown(event){
function onRename(event, treeId, treeNode, isCancel){ function onRename(event, treeId, treeNode, isCancel){
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", treeNode.node_id); var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node_id);
var data = {"value": treeNode.name}; var data = {"value": treeNode.name};
if (isCancel){ if (isCancel){
return return
...@@ -291,13 +296,20 @@ function onRename(event, treeId, treeNode, isCancel){ ...@@ -291,13 +296,20 @@ function onRename(event, treeId, treeNode, isCancel){
body: JSON.stringify(data), body: JSON.stringify(data),
method: "PATCH", method: "PATCH",
success_message: "{% trans 'Rename success' %}", success_message: "{% trans 'Rename success' %}",
fail_message: "{% trans 'Rename failed, do not change the root node name' %}" fail_message: "{% trans 'Rename failed, do not change the root node name' %}",
success: function () {
treeNode.name = treeNode.name + ' (' + treeNode.meta.node.assets_amount + ')'
zTree.updateNode(treeNode);
console.log("Success: " + treeNode.name)
}
}) })
} }
function onSelected(event, treeNode) { function onSelected(event, treeNode) {
current_node = treeNode;
current_node_id = treeNode.meta.node.id;
var url = asset_table.ajax.url(); var url = asset_table.ajax.url();
url = setUrlParam(url, "node_id", treeNode.node_id); url = setUrlParam(url, "node_id", current_node_id);
url = setUrlParam(url, "show_current_asset", getCookie('show_current_asset')); url = setUrlParam(url, "show_current_asset", getCookie('show_current_asset'));
setCookie('node_selected', treeNode.node_id); setCookie('node_selected', treeNode.node_id);
asset_table.ajax.url(url); asset_table.ajax.url(url);
...@@ -305,6 +317,9 @@ function onSelected(event, treeNode) { ...@@ -305,6 +317,9 @@ function onSelected(event, treeNode) {
} }
function selectQueryNode() { function selectQueryNode() {
// TODO: 是否应该添加
// 暂时忽略之前选中的内容
return
var query_node_id = $.getUrlParam("node"); var query_node_id = $.getUrlParam("node");
var cookie_node_id = getCookie('node_selected'); var cookie_node_id = getCookie('node_selected');
var node; var node;
...@@ -355,6 +370,14 @@ function onDrop(event, treeId, treeNodes, targetNode, moveType) { ...@@ -355,6 +370,14 @@ function onDrop(event, treeId, treeNodes, targetNode, moveType) {
} }
function initTree() { function initTree() {
if (zTree) {
return
}
var url = '{% url 'api-assets:node-children-tree' %}?assets=0&all=';
var showCurrentAsset = getCookie('show_current_asset');
if (!showCurrentAsset) {
url += '1'
}
var setting = { var setting = {
view: { view: {
dblClickExpand: false, dblClickExpand: false,
...@@ -365,6 +388,12 @@ function initTree() { ...@@ -365,6 +388,12 @@ function initTree() {
enable: true enable: true
} }
}, },
async: {
enable: true,
url: url,
autoParam: ["id=key", "name=n", "level=lv"],
type: 'get'
},
edit: { edit: {
enable: true, enable: true,
showRemoveBtn: false, showRemoveBtn: false,
...@@ -387,26 +416,8 @@ function initTree() { ...@@ -387,26 +416,8 @@ function initTree() {
}; };
var zNodes = []; var zNodes = [];
$.get("{% url 'api-assets:node-list' %}", function(data, status){ zTree = $.fn.zTree.init($("#assetTree"), setting, zNodes);
$.each(data, function (index, value) { rMenu = $("#rMenu");
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();
});
} }
function toggle() { function toggle() {
...@@ -443,20 +454,15 @@ $(document).ready(function(){ ...@@ -443,20 +454,15 @@ $(document).ready(function(){
.on('click', '.btn_export', function () { .on('click', '.btn_export', function () {
var $data_table = $('#asset_list_table').DataTable(); var $data_table = $('#asset_list_table').DataTable();
var rows = $data_table.rows('.selected').data(); var rows = $data_table.rows('.selected').data();
var nodes = zTree.getSelectedNodes();
var current_node;
if (nodes && nodes.length === 1) {
current_node = nodes[0];
}
var assets = []; var assets = [];
$.each(rows, function (index, obj) { $.each(rows, function (index, obj) {
assets.push(obj.id) assets.push(obj.id)
}); });
var _node_id = current_node ? current_node.node_id : null;
$.ajax({ $.ajax({
url: "{% url "assets:asset-export" %}", url: "{% url "assets:asset-export" %}",
method: 'POST', method: 'POST',
data: JSON.stringify({assets_id: assets, node_id: _node_id}), data: JSON.stringify({assets_id: assets, node_id: current_node_id}),
dataType: "json", dataType: "json",
success: function (data, textStatus) { success: function (data, textStatus) {
window.open(data.redirect) window.open(data.redirect)
...@@ -469,12 +475,8 @@ $(document).ready(function(){ ...@@ -469,12 +475,8 @@ $(document).ready(function(){
.on('click', '#btn_asset_import', function () { .on('click', '#btn_asset_import', function () {
var $form = $('#fm_asset_import'); var $form = $('#fm_asset_import');
var action = $form.attr("action"); var action = $form.attr("action");
var nodes = zTree.getSelectedNodes(); if (current_node_id){
var current_node; action = setUrlParam(action, 'node_id', current_node_id);
if (nodes && nodes.length ===1 ){
current_node = nodes[0];
action = setUrlParam(action, 'node_id', current_node.node_id);
{#action += "?node_id=" + current_node.node_id;#}
$form.attr("action", action) $form.attr("action", action)
} }
$form.find('.help-block').remove(); $form.find('.help-block').remove();
...@@ -496,25 +498,14 @@ $(document).ready(function(){ ...@@ -496,25 +498,14 @@ $(document).ready(function(){
}) })
.on('click', '.btn-create-asset', function () { .on('click', '.btn-create-asset', function () {
var url = "{% url 'assets:asset-create' %}"; var url = "{% url 'assets:asset-create' %}";
var nodes = zTree.getSelectedNodes(); if (current_node_id) {
var current_node; url += "?node_id=" + current_node_id;
if (nodes && nodes.length ===1 ){
current_node = nodes[0];
url += "?node_id=" + current_node.node_id;
} }
window.open(url, '_self'); window.open(url, '_self');
}) })
.on('click', '.btn-refresh-hardware', function () { .on('click', '.btn-refresh-hardware', function () {
var url = "{% url 'api-assets:node-refresh-hardware-info' pk=DEFAULT_PK %}"; var url = "{% url 'api-assets:node-refresh-hardware-info' pk=DEFAULT_PK %}";
var nodes = zTree.getSelectedNodes(); var the_url = url.replace("{{ DEFAULT_PK }}", current_node_id);
var current_node;
if (nodes && nodes.length ===1 ){
current_node = nodes[0];
} else {
return null;
}
var the_url = url.replace("{{ DEFAULT_PK }}", current_node.node_id);
function success(data) { function success(data) {
rMenu.css({"visibility" : "hidden"}); rMenu.css({"visibility" : "hidden"});
var task_id = data.task; var task_id = data.task;
...@@ -531,15 +522,10 @@ $(document).ready(function(){ ...@@ -531,15 +522,10 @@ $(document).ready(function(){
}) })
.on('click', '.btn-test-connective', function () { .on('click', '.btn-test-connective', function () {
var url = "{% url 'api-assets:node-test-connective' pk=DEFAULT_PK %}"; var url = "{% url 'api-assets:node-test-connective' pk=DEFAULT_PK %}";
var nodes = zTree.getSelectedNodes(); if (!current_node_id) {
var current_node;
if (nodes && nodes.length ===1 ){
current_node = nodes[0];
} else {
return null; 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) { function success(data) {
rMenu.css({"visibility" : "hidden"}); rMenu.css({"visibility" : "hidden"});
var task_id = data.task; var task_id = data.task;
...@@ -567,6 +553,10 @@ $(document).ready(function(){ ...@@ -567,6 +553,10 @@ $(document).ready(function(){
setCookie('show_current_asset', ''); setCookie('show_current_asset', '');
location.reload(); location.reload();
}) })
.on('click', '.btn-test-connective', function () {
hideRMenu();
})
.on('click', '.btn_asset_delete', function () { .on('click', '.btn_asset_delete', function () {
var $this = $(this); var $this = $(this);
var $data_table = $("#asset_list_table").DataTable(); var $data_table = $("#asset_list_table").DataTable();
...@@ -660,11 +650,8 @@ $(document).ready(function(){ ...@@ -660,11 +650,8 @@ $(document).ready(function(){
} }
function doRemove() { function doRemove() {
var current_node;
var nodes = zTree.getSelectedNodes(); var nodes = zTree.getSelectedNodes();
if (nodes && nodes.length === 1) { if (!current_node_id) {
current_node = nodes[0]
} else {
return return
} }
...@@ -677,7 +664,7 @@ $(document).ready(function(){ ...@@ -677,7 +664,7 @@ $(document).ready(function(){
}; };
APIUpdateAttr({ APIUpdateAttr({
'url': '/api/assets/v1/nodes/' + current_node.node_id + '/assets/remove/', 'url': '/api/assets/v1/nodes/' + current_node_id + '/assets/remove/',
'method': 'PUT', 'method': 'PUT',
'body': JSON.stringify(data), 'body': JSON.stringify(data),
'success': success 'success': success
...@@ -706,11 +693,7 @@ $(document).ready(function(){ ...@@ -706,11 +693,7 @@ $(document).ready(function(){
}) })
.on('click', '#btn_asset_modal_confirm', function () { .on('click', '#btn_asset_modal_confirm', function () {
var assets_selected = asset_table2.selected; var assets_selected = asset_table2.selected;
var current_node; if (!current_node_id) {
var nodes = zTree.getSelectedNodes();
if (nodes && nodes.length === 1) {
current_node = nodes[0]
} else {
return return
} }
...@@ -722,9 +705,9 @@ $(document).ready(function(){ ...@@ -722,9 +705,9 @@ $(document).ready(function(){
var url = ''; var url = '';
if (update_node_action === "move") { if (update_node_action === "move") {
url = "{% url 'api-assets:node-replace-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.node_id); url = "{% url 'api-assets:node-replace-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node_id);
} else { } else {
url = "{% url 'api-assets:node-add-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.node_id); url = "{% url 'api-assets:node-add-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node_id);
} }
APIUpdateAttr({ APIUpdateAttr({
......
...@@ -32,6 +32,7 @@ $(document).ready(function () { ...@@ -32,6 +32,7 @@ $(document).ready(function () {
}).on('click', '.select2-selection__rendered', function (e) { }).on('click', '.select2-selection__rendered', function (e) {
e.preventDefault(); e.preventDefault();
$("#asset_list_modal").modal(); $("#asset_list_modal").modal();
initSelectedAssets2Table();
}) })
.on('click', '#btn_asset_modal_confirm', function () { .on('click', '#btn_asset_modal_confirm', function () {
var assets = asset_table2.selected; var assets = asset_table2.selected;
......
...@@ -136,7 +136,7 @@ ...@@ -136,7 +136,7 @@
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <script>
function initAssetsTable() { function initAssetsTable() {
var unreachable = {{ system_user.unreachable_assets|safe }}; var connectivity = {{ system_user.connectivity | safe }};
var options = { var options = {
ele: $('#system_user_list'), ele: $('#system_user_list'),
buttons: [], buttons: [],
...@@ -147,11 +147,13 @@ function initAssetsTable() { ...@@ -147,11 +147,13 @@ function initAssetsTable() {
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}}, }},
{targets: 3, createdCell: function (td, cellData) { {targets: 3, createdCell: function (td, cellData) {
if (unreachable.indexOf(cellData) >= 0) { if (connectivity.unreachable.indexOf(cellData) >= 0) {
$(td).html('<i class="fa fa-times text-danger"></i>') $(td).html('<i class="fa fa-times text-danger"></i>')
} else { } else if (connectivity.reachable.indexOf(cellData) >= 0 ) {
$(td).html('<i class="fa fa-check text-navy"></i>') $(td).html('<i class="fa fa-check text-navy"></i>')
} } else {
$(td).html('')
}
}}, }},
{targets: 4, createdCell: function (td, cellData) { {targets: 4, createdCell: function (td, cellData) {
var push_btn = ''; var push_btn = '';
......
...@@ -95,7 +95,7 @@ function initTable() { ...@@ -95,7 +95,7 @@ function initTable() {
}}], }}],
ajax_url: '{% url "api-assets:system-user-list" %}', ajax_url: '{% url "api-assets:system-user-list" %}',
columns: [ columns: [
{data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"}, {data: "get_login_mode_display"}, {data: "assets_amount" }, {data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"}, {data: "login_mode_display"}, {data: "assets_amount" },
{data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment" }, {data: "id" } {data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment" }, {data: "id" }
], ],
op_html: $('#actions').html() op_html: $('#actions').html()
......
...@@ -24,35 +24,39 @@ cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-r ...@@ -24,35 +24,39 @@ cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-r
urlpatterns = [ urlpatterns = [
path('assets-bulk/', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'), 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/', path('assets/<uuid:pk>/refresh/',
api.AssetRefreshHardwareApi.as_view(), name='asset-refresh'), api.AssetRefreshHardwareApi.as_view(), name='asset-refresh'),
path('assets/<uuid:pk>/alive/', path('assets/<uuid:pk>/alive/',
api.AssetAdminUserTestApi.as_view(), name='asset-alive-test'), api.AssetAdminUserTestApi.as_view(), name='asset-alive-test'),
path('assets/<uuid:pk>/gateway/', path('assets/<uuid:pk>/gateway/',
api.AssetGatewayApi.as_view(), name='asset-gateway'), api.AssetGatewayApi.as_view(), name='asset-gateway'),
path('admin-user/<uuid:pk>/nodes/', path('admin-user/<uuid:pk>/nodes/',
api.ReplaceNodesAdminUserApi.as_view(), name='replace-nodes-admin-user'), api.ReplaceNodesAdminUserApi.as_view(), name='replace-nodes-admin-user'),
path('admin-user/<uuid:pk>/auth/', path('admin-user/<uuid:pk>/auth/',
api.AdminUserAuthApi.as_view(), name='admin-user-auth'), api.AdminUserAuthApi.as_view(), name='admin-user-auth'),
path('admin-user/<uuid:pk>/connective/', path('admin-user/<uuid:pk>/connective/',
api.AdminUserTestConnectiveApi.as_view(), name='admin-user-connective'), api.AdminUserTestConnectiveApi.as_view(), name='admin-user-connective'),
path('admin-user/<uuid:pk>/assets/',
api.AdminUserAssetsListView.as_view(), name='admin-user-assets'),
path('system-user/<uuid:pk>/auth-info/',
api.SystemUserAuthInfoApi.as_view(), name='system-user-auth-info'),
path('system-user/<uuid:pk>/assets/',
api.SystemUserAssetsListView.as_view(), name='system-user-assets'),
path('system-user/<uuid:pk>/push/', path('system-user/<uuid:pk>/push/',
api.SystemUserPushApi.as_view(), name='system-user-push'), api.SystemUserPushApi.as_view(), name='system-user-push'),
path('system-user/<uuid:pk>/asset/<uuid:aid>/push/', path('system-user/<uuid:pk>/asset/<uuid:aid>/push/',
api.SystemUserPushToAssetApi.as_view(), name='system-user-push-to-asset'), api.SystemUserPushToAssetApi.as_view(), name='system-user-push-to-asset'),
path('system-user/<uuid:pk>/asset/<uuid:aid>/test/', path('system-user/<uuid:pk>/asset/<uuid:aid>/test/',
api.SystemUserTestAssetConnectabilityApi.as_view(), name='system-user-test-to-asset'), api.SystemUserTestAssetConnectivityApi.as_view(), name='system-user-test-to-asset'),
path('system-user/<uuid:pk>/connective/', path('system-user/<uuid:pk>/connective/',
api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'), api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'),
path('system-user/<uuid:pk>/cmd-filter-rules/', path('system-user/<uuid:pk>/cmd-filter-rules/',
api.SystemUserCommandFilterRuleListApi.as_view(), name='system-user-cmd-filter-rule-list'), api.SystemUserCommandFilterRuleListApi.as_view(), name='system-user-cmd-filter-rule-list'),
path('nodes/tree/', api.NodeListAsTreeApi.as_view(), name='node-tree'),
path('nodes/children/tree/', api.NodeChildrenAsTreeApi.as_view(), name='node-children-tree'),
path('nodes/<uuid:pk>/children/', path('nodes/<uuid:pk>/children/',
api.NodeChildrenApi.as_view(), name='node-children'), api.NodeChildrenApi.as_view(), name='node-children'),
path('nodes/children/', api.NodeChildrenApi.as_view(), name='node-children-2'), path('nodes/children/', api.NodeChildrenApi.as_view(), name='node-children-2'),
......
...@@ -102,7 +102,7 @@ class AdminUserAssetsView(AdminUserRequiredMixin, SingleObjectMixin, ListView): ...@@ -102,7 +102,7 @@ class AdminUserAssetsView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
'app': _('Assets'), 'app': _('Assets'),
'action': _('Admin user detail'), 'action': _('Admin user detail'),
"total_amount": len(self.queryset), "total_amount": len(self.queryset),
'unreachable_amount': len([asset for asset in self.queryset if asset.is_connective is False]) 'unreachable_amount': len([asset for asset in self.queryset if asset.connectivity is False])
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
......
...@@ -216,7 +216,6 @@ class AssetExportView(LoginRequiredMixin, View): ...@@ -216,7 +216,6 @@ class AssetExportView(LoginRequiredMixin, View):
return HttpResponse('Json object not valid', status=400) return HttpResponse('Json object not valid', status=400)
if not assets_id: if not assets_id:
print(node_id)
node = get_object_or_none(Node, id=node_id) if node_id else Node.root() node = get_object_or_none(Node, id=node_id) if node_id else Node.root()
assets = node.get_all_assets() assets = node.get_all_assets()
for asset in assets: for asset in assets:
......
...@@ -13,6 +13,6 @@ class Migration(migrations.Migration): ...@@ -13,6 +13,6 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='ftplog', model_name='ftplog',
name='org_id', name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True), field=models.CharField(blank=True, db_index=True, default=None, max_length=36, null=True),
), ),
] ]
...@@ -13,6 +13,6 @@ class Migration(migrations.Migration): ...@@ -13,6 +13,6 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='ftplog', model_name='ftplog',
name='org_id', name='org_id',
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'), field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
), ),
] ]
...@@ -15,7 +15,7 @@ class Migration(migrations.Migration): ...@@ -15,7 +15,7 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='OperateLog', name='OperateLog',
fields=[ fields=[
('org_id', models.CharField(blank=True, default='', max_length=36, verbose_name='Organization')), ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('user', models.CharField(max_length=128, verbose_name='User')), ('user', models.CharField(max_length=128, verbose_name='User')),
('action', models.CharField(choices=[('create', 'Create'), ('update', 'Update'), ('delete', 'Delete')], max_length=16, verbose_name='Action')), ('action', models.CharField(choices=[('create', 'Create'), ('update', 'Update'), ('delete', 'Delete')], max_length=16, verbose_name='Action')),
......
...@@ -15,8 +15,6 @@ class BaseForm(forms.Form): ...@@ -15,8 +15,6 @@ class BaseForm(forms.Form):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
for name, field in self.fields.items(): for name, field in self.fields.items():
value = getattr(settings, name, None) value = getattr(settings, name, None)
# django_value = getattr(settings, name) if hasattr(settings, name) else None
if value is None: # and django_value is None: if value is None: # and django_value is None:
continue continue
...@@ -24,8 +22,6 @@ class BaseForm(forms.Form): ...@@ -24,8 +22,6 @@ class BaseForm(forms.Form):
if isinstance(value, dict): if isinstance(value, dict):
value = json.dumps(value) value = json.dumps(value)
initial_value = value initial_value = value
# elif django_value is False or django_value:
# initial_value = django_value
else: else:
initial_value = '' initial_value = ''
field.initial = initial_value field.initial = initial_value
...@@ -134,6 +130,14 @@ class TerminalSettingForm(BaseForm): ...@@ -134,6 +130,14 @@ class TerminalSettingForm(BaseForm):
('hostname', _('Hostname')), ('hostname', _('Hostname')),
('ip', _('IP')), ('ip', _('IP')),
) )
PAGE_SIZE_CHOICES = (
('all', _('All')),
('auto', _('Auto')),
(10, 10),
(15, 15),
(25, 25),
(50, 50),
)
TERMINAL_PASSWORD_AUTH = forms.BooleanField( TERMINAL_PASSWORD_AUTH = forms.BooleanField(
initial=True, required=False, label=_("Password auth") initial=True, required=False, label=_("Password auth")
) )
...@@ -146,6 +150,14 @@ class TerminalSettingForm(BaseForm): ...@@ -146,6 +150,14 @@ class TerminalSettingForm(BaseForm):
TERMINAL_ASSET_LIST_SORT_BY = forms.ChoiceField( TERMINAL_ASSET_LIST_SORT_BY = forms.ChoiceField(
choices=SORT_BY_CHOICES, initial='hostname', label=_("List sort by") choices=SORT_BY_CHOICES, initial='hostname', label=_("List sort by")
) )
TERMINAL_ASSET_LIST_PAGE_SIZE = forms.ChoiceField(
choices=PAGE_SIZE_CHOICES, initial='auto', label=_("List page size"),
)
TERMINAL_SESSION_KEEP_DURATION = forms.IntegerField(
label=_("Session keep duration"),
help_text=_("Units: days, Session, record, command will be delete "
"if more than duration, only in database")
)
class TerminalCommandStorage(BaseForm): class TerminalCommandStorage(BaseForm):
......
...@@ -45,6 +45,8 @@ class Setting(models.Model): ...@@ -45,6 +45,8 @@ class Setting(models.Model):
def cleaned_value(self): def cleaned_value(self):
try: try:
value = self.value value = self.value
if not isinstance(value, (str, bytes)):
return value
if self.encrypted: if self.encrypted:
value = signer.unsign(value) value = signer.unsign(value)
value = json.loads(value) value = json.loads(value)
......
...@@ -26,21 +26,20 @@ def refresh_settings_on_changed(sender, instance=None, **kwargs): ...@@ -26,21 +26,20 @@ def refresh_settings_on_changed(sender, instance=None, **kwargs):
def refresh_all_settings_on_django_ready(sender, **kwargs): def refresh_all_settings_on_django_ready(sender, **kwargs):
logger.debug("Receive django ready signal") logger.debug("Receive django ready signal")
logger.debug(" - fresh all settings") logger.debug(" - fresh all settings")
CACHE_KEY_PREFIX = '_SETTING_' cache_key_prefix = '_SETTING_'
def monkey_patch_getattr(self, name): def monkey_patch_getattr(self, name):
key = CACHE_KEY_PREFIX + name key = cache_key_prefix + name
cached = cache.get(key) cached = cache.get(key)
if cached is not None: if cached is not None:
return cached return cached
if self._wrapped is empty: if self._wrapped is empty:
self._setup(name) self._setup(name)
val = getattr(self._wrapped, name) val = getattr(self._wrapped, name)
# self.__dict__[name] = val # Never set it
return val return val
def monkey_patch_setattr(self, name, value): def monkey_patch_setattr(self, name, value):
key = CACHE_KEY_PREFIX + name key = cache_key_prefix + name
cache.set(key, value, None) cache.set(key, value, None)
if name == '_wrapped': if name == '_wrapped':
self.__dict__.clear() self.__dict__.clear()
...@@ -51,7 +50,7 @@ def refresh_all_settings_on_django_ready(sender, **kwargs): ...@@ -51,7 +50,7 @@ def refresh_all_settings_on_django_ready(sender, **kwargs):
def monkey_patch_delattr(self, name): def monkey_patch_delattr(self, name):
super(LazySettings, self).__delattr__(name) super(LazySettings, self).__delattr__(name)
self.__dict__.pop(name, None) self.__dict__.pop(name, None)
key = CACHE_KEY_PREFIX + name key = cache_key_prefix + name
cache.delete(key) cache.delete(key)
try: try:
......
...@@ -47,16 +47,13 @@ class TreeNode: ...@@ -47,16 +47,13 @@ class TreeNode:
def __gt__(self, other): def __gt__(self, other):
if self.isParent and not other.isParent: if self.isParent and not other.isParent:
return False return False
elif not self.isParent and other.isParent:
return True
return self.id > other.id return self.id > other.id
def __eq__(self, other): def __eq__(self, other):
return self.id == other.id return self.id == other.id
def __lt__(self, other):
if self.isParent and not other.isParent:
return True
return self.id < other.id
class Tree: class Tree:
def __init__(self): def __init__(self):
......
...@@ -304,7 +304,7 @@ defaults = { ...@@ -304,7 +304,7 @@ defaults = {
'REDIS_DB_CELERY': 3, 'REDIS_DB_CELERY': 3,
'REDIS_DB_CACHE': 4, 'REDIS_DB_CACHE': 4,
'CAPTCHA_TEST_MODE': None, 'CAPTCHA_TEST_MODE': None,
'TOKEN_EXPIRATION': 3600, 'TOKEN_EXPIRATION': 3600 * 24,
'DISPLAY_PER_PAGE': 25, 'DISPLAY_PER_PAGE': 25,
'DEFAULT_EXPIRED_YEARS': 70, 'DEFAULT_EXPIRED_YEARS': 70,
'SESSION_COOKIE_DOMAIN': None, 'SESSION_COOKIE_DOMAIN': None,
...@@ -312,7 +312,14 @@ defaults = { ...@@ -312,7 +312,14 @@ defaults = {
'SESSION_COOKIE_AGE': 3600 * 24, 'SESSION_COOKIE_AGE': 3600 * 24,
'SESSION_EXPIRE_AT_BROWSER_CLOSE': False, 'SESSION_EXPIRE_AT_BROWSER_CLOSE': False,
'AUTH_OPENID': False, 'AUTH_OPENID': False,
'EMAIL_SUFFIX': 'jumpserver.org' 'OTP_ISSUER_NAME': 'Jumpserver',
'EMAIL_SUFFIX': 'jumpserver.org',
'TERMINAL_PASSWORD_AUTH': True,
'TERMINAL_PUBLIC_KEY_AUTH': True,
'TERMINAL_HEARTBEAT_INTERVAL': 5,
'TERMINAL_ASSET_LIST_SORT_BY': 'hostname',
'TERMINAL_ASSET_LIST_PAGE_SIZE': 'auto',
'TERMINAL_SESSION_KEEP_DURATION': 9999,
} }
......
...@@ -356,6 +356,7 @@ FILE_UPLOAD_DIRECTORY_PERMISSIONS = 0o755 ...@@ -356,6 +356,7 @@ FILE_UPLOAD_DIRECTORY_PERMISSIONS = 0o755
# OTP settings # OTP settings
OTP_ISSUER_NAME = CONFIG.OTP_ISSUER_NAME OTP_ISSUER_NAME = CONFIG.OTP_ISSUER_NAME
OTP_VALID_WINDOW = CONFIG.OTP_VALID_WINDOW
# Auth LDAP settings # Auth LDAP settings
AUTH_LDAP = False AUTH_LDAP = False
...@@ -466,6 +467,7 @@ DEFAULT_TERMINAL_REPLAY_STORAGE = { ...@@ -466,6 +467,7 @@ DEFAULT_TERMINAL_REPLAY_STORAGE = {
TERMINAL_REPLAY_STORAGE = { TERMINAL_REPLAY_STORAGE = {
} }
SECURITY_MFA_AUTH = False SECURITY_MFA_AUTH = False
SECURITY_LOGIN_LIMIT_COUNT = 7 SECURITY_LOGIN_LIMIT_COUNT = 7
SECURITY_LOGIN_LIMIT_TIME = 30 # Unit: minute SECURITY_LOGIN_LIMIT_TIME = 30 # Unit: minute
...@@ -484,6 +486,13 @@ SECURITY_PASSWORD_RULES = [ ...@@ -484,6 +486,13 @@ SECURITY_PASSWORD_RULES = [
'SECURITY_PASSWORD_SPECIAL_CHAR' 'SECURITY_PASSWORD_SPECIAL_CHAR'
] ]
TERMINAL_PASSWORD_AUTH = CONFIG.TERMINAL_PASSWORD_AUTH
TERMINAL_PUBLIC_KEY_AUTH = CONFIG.TERMINAL_PUBLIC_KEY_AUTH
TERMINAL_HEARTBEAT_INTERVAL = CONFIG.TERMINAL_HEARTBEAT_INTERVAL
TERMINAL_ASSET_LIST_SORT_BY = CONFIG.TERMINAL_ASSET_LIST_SORT_BY
TERMINAL_ASSET_LIST_PAGE_SIZE = CONFIG.TERMINAL_ASSET_LIST_PAGE_SIZE
TERMINAL_SESSION_KEEP_DURATION = CONFIG.TERMINAL_SESSION_KEEP_DURATION
# Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html # Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html
BOOTSTRAP3 = { BOOTSTRAP3 = {
'horizontal_label_class': 'col-md-2', 'horizontal_label_class': 'col-md-2',
......
...@@ -8,7 +8,7 @@ msgid "" ...@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Jumpserver 0.3.3\n" "Project-Id-Version: Jumpserver 0.3.3\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-07 18:11+0800\n" "POT-Creation-Date: 2018-12-18 10:13+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler <ibuler@qq.com>\n" "Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: Jumpserver team<ibuler@qq.com>\n" "Language-Team: Jumpserver team<ibuler@qq.com>\n"
...@@ -17,25 +17,17 @@ msgstr "" ...@@ -17,25 +17,17 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: assets/api/node.py:58 #: assets/api/node.py:261
msgid "You can't update the root node name"
msgstr "不能修改根节点名称"
#: assets/api/node.py:82
msgid "New node {}"
msgstr "新节点 {}"
#: assets/api/node.py:222
msgid "Update node asset hardware information: {}" msgid "Update node asset hardware information: {}"
msgstr "更新节点资产硬件信息: {}" msgstr "更新节点资产硬件信息: {}"
#: assets/api/node.py:236 #: assets/api/node.py:275
msgid "Test if the assets under the node are connectable: {}" msgid "Test if the assets under the node are connectable: {}"
msgstr "测试节点下资产是否可连接: {}" msgstr "测试节点下资产是否可连接: {}"
#: assets/forms/asset.py:27 assets/models/asset.py:83 assets/models/user.py:113 #: assets/forms/asset.py:27 assets/models/asset.py:83 assets/models/user.py:113
#: assets/templates/assets/asset_detail.html:187 #: assets/templates/assets/asset_detail.html:191
#: assets/templates/assets/asset_detail.html:195 #: assets/templates/assets/asset_detail.html:199
#: assets/templates/assets/system_user_asset.html:95 perms/models.py:32 #: assets/templates/assets/system_user_asset.html:95 perms/models.py:32
msgid "Nodes" msgid "Nodes"
msgstr "节点管理" msgstr "节点管理"
...@@ -62,19 +54,20 @@ msgid "Label" ...@@ -62,19 +54,20 @@ msgid "Label"
msgstr "标签" msgstr "标签"
#: assets/forms/asset.py:37 assets/forms/asset.py:76 assets/models/asset.py:79 #: assets/forms/asset.py:37 assets/forms/asset.py:76 assets/models/asset.py:79
#: assets/models/domain.py:24 assets/models/domain.py:50 #: assets/models/domain.py:26 assets/models/domain.py:52
#: assets/templates/assets/asset_detail.html:81
#: assets/templates/assets/user_asset_list.html:157 #: assets/templates/assets/user_asset_list.html:157
#: xpack/plugins/orgs/templates/orgs/org_list.html:17 #: xpack/plugins/orgs/templates/orgs/org_list.html:17
msgid "Domain" msgid "Domain"
msgstr "网域" msgstr "网域"
#: assets/forms/asset.py:41 assets/forms/asset.py:66 assets/forms/asset.py:80 #: assets/forms/asset.py:41 assets/forms/asset.py:66 assets/forms/asset.py:80
#: assets/forms/asset.py:131 assets/models/node.py:28 #: assets/forms/asset.py:131 assets/models/node.py:31
#: assets/templates/assets/asset_create.html:30 #: assets/templates/assets/asset_create.html:30
#: assets/templates/assets/asset_update.html:35 perms/forms.py:37 #: assets/templates/assets/asset_update.html:35 perms/forms.py:37
#: perms/forms.py:44 perms/models.py:79 #: perms/forms.py:44 perms/models.py:79
#: perms/templates/perms/asset_permission_list.html:57 #: perms/templates/perms/asset_permission_list.html:57
#: perms/templates/perms/asset_permission_list.html:151 #: perms/templates/perms/asset_permission_list.html:117
#: xpack/plugins/cloud/models.py:122 #: xpack/plugins/cloud/models.py:122
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:63 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:63
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:66 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:66
...@@ -105,7 +98,7 @@ msgid "Select assets" ...@@ -105,7 +98,7 @@ msgid "Select assets"
msgstr "选择资产" msgstr "选择资产"
#: assets/forms/asset.py:108 assets/models/asset.py:76 #: assets/forms/asset.py:108 assets/models/asset.py:76
#: assets/models/domain.py:48 assets/templates/assets/admin_user_assets.html:53 #: assets/models/domain.py:50 assets/templates/assets/admin_user_assets.html:53
#: assets/templates/assets/asset_detail.html:69 #: assets/templates/assets/asset_detail.html:69
#: assets/templates/assets/domain_gateway_list.html:58 #: assets/templates/assets/domain_gateway_list.html:58
#: assets/templates/assets/system_user_asset.html:52 #: assets/templates/assets/system_user_asset.html:52
...@@ -115,7 +108,7 @@ msgid "Port" ...@@ -115,7 +108,7 @@ msgid "Port"
msgstr "端口" msgstr "端口"
#: assets/forms/domain.py:15 assets/forms/label.py:13 #: assets/forms/domain.py:15 assets/forms/label.py:13
#: assets/models/asset.py:260 assets/templates/assets/admin_user_list.html:28 #: assets/models/asset.py:290 assets/templates/assets/admin_user_list.html:28
#: assets/templates/assets/domain_detail.html:60 #: assets/templates/assets/domain_detail.html:60
#: assets/templates/assets/domain_list.html:26 #: assets/templates/assets/domain_list.html:26
#: assets/templates/assets/label_list.html:16 #: assets/templates/assets/label_list.html:16
...@@ -125,7 +118,7 @@ msgstr "端口" ...@@ -125,7 +118,7 @@ msgstr "端口"
#: perms/models.py:31 #: perms/models.py:31
#: perms/templates/perms/asset_permission_create_update.html:40 #: perms/templates/perms/asset_permission_create_update.html:40
#: perms/templates/perms/asset_permission_list.html:56 #: perms/templates/perms/asset_permission_list.html:56
#: perms/templates/perms/asset_permission_list.html:148 #: perms/templates/perms/asset_permission_list.html:114
#: terminal/backends/command/models.py:13 terminal/models.py:138 #: terminal/backends/command/models.py:13 terminal/models.py:138
#: terminal/templates/terminal/command_list.html:40 #: terminal/templates/terminal/command_list.html:40
#: terminal/templates/terminal/command_list.html:73 #: terminal/templates/terminal/command_list.html:73
...@@ -137,13 +130,13 @@ msgstr "端口" ...@@ -137,13 +130,13 @@ msgstr "端口"
msgid "Asset" msgid "Asset"
msgstr "资产" msgstr "资产"
#: assets/forms/domain.py:42 #: assets/forms/domain.py:46
msgid "Password should not contain special characters" msgid "Password should not contain special characters"
msgstr "不能包含特殊字符" msgstr "不能包含特殊字符"
#: assets/forms/domain.py:59 assets/forms/user.py:80 assets/forms/user.py:143 #: assets/forms/domain.py:63 assets/forms/user.py:80 assets/forms/user.py:143
#: assets/models/base.py:22 assets/models/cluster.py:18 #: assets/models/base.py:22 assets/models/cluster.py:18
#: assets/models/cmd_filter.py:20 assets/models/domain.py:18 #: assets/models/cmd_filter.py:20 assets/models/domain.py:20
#: assets/models/group.py:20 assets/models/label.py:18 #: assets/models/group.py:20 assets/models/label.py:18
#: assets/templates/assets/admin_user_detail.html:56 #: assets/templates/assets/admin_user_detail.html:56
#: assets/templates/assets/admin_user_list.html:26 #: assets/templates/assets/admin_user_list.html:26
...@@ -183,7 +176,7 @@ msgstr "不能包含特殊字符" ...@@ -183,7 +176,7 @@ msgstr "不能包含特殊字符"
msgid "Name" msgid "Name"
msgstr "名称" msgstr "名称"
#: assets/forms/domain.py:60 assets/forms/user.py:81 assets/forms/user.py:144 #: assets/forms/domain.py:64 assets/forms/user.py:81 assets/forms/user.py:144
#: assets/models/base.py:23 assets/templates/assets/admin_user_detail.html:60 #: assets/models/base.py:23 assets/templates/assets/admin_user_detail.html:60
#: assets/templates/assets/admin_user_list.html:27 #: assets/templates/assets/admin_user_list.html:27
#: assets/templates/assets/domain_gateway_list.html:60 #: assets/templates/assets/domain_gateway_list.html:60
...@@ -256,7 +249,7 @@ msgid "" ...@@ -256,7 +249,7 @@ msgid ""
"password." "password."
msgstr "如果选择手动登录模式,用户名和密码可以不填写" msgstr "如果选择手动登录模式,用户名和密码可以不填写"
#: assets/models/asset.py:73 assets/models/domain.py:47 #: assets/models/asset.py:73 assets/models/domain.py:49
#: assets/templates/assets/_asset_list_modal.html:46 #: assets/templates/assets/_asset_list_modal.html:46
#: assets/templates/assets/admin_user_assets.html:52 #: assets/templates/assets/admin_user_assets.html:52
#: assets/templates/assets/asset_detail.html:61 #: assets/templates/assets/asset_detail.html:61
...@@ -285,7 +278,7 @@ msgstr "IP" ...@@ -285,7 +278,7 @@ msgstr "IP"
msgid "Hostname" msgid "Hostname"
msgstr "主机名" msgstr "主机名"
#: assets/models/asset.py:75 assets/models/domain.py:49 #: assets/models/asset.py:75 assets/models/domain.py:51
#: assets/models/user.py:117 assets/templates/assets/asset_detail.html:73 #: assets/models/user.py:117 assets/templates/assets/asset_detail.html:73
#: assets/templates/assets/domain_gateway_list.html:59 #: assets/templates/assets/domain_gateway_list.html:59
#: assets/templates/assets/system_user_detail.html:70 #: assets/templates/assets/system_user_detail.html:70
...@@ -295,14 +288,14 @@ msgstr "主机名" ...@@ -295,14 +288,14 @@ msgstr "主机名"
msgid "Protocol" msgid "Protocol"
msgstr "协议" msgstr "协议"
#: assets/models/asset.py:77 assets/templates/assets/asset_detail.html:101 #: assets/models/asset.py:77 assets/templates/assets/asset_detail.html:105
#: assets/templates/assets/user_asset_list.html:154 #: assets/templates/assets/user_asset_list.html:154
msgid "Platform" msgid "Platform"
msgstr "系统平台" msgstr "系统平台"
#: assets/models/asset.py:84 assets/models/cmd_filter.py:21 #: assets/models/asset.py:84 assets/models/cmd_filter.py:21
#: assets/models/domain.py:52 assets/models/label.py:21 #: assets/models/domain.py:54 assets/models/label.py:22
#: assets/templates/assets/asset_detail.html:109 #: assets/templates/assets/asset_detail.html:113
#: assets/templates/assets/user_asset_list.html:158 #: assets/templates/assets/user_asset_list.html:158
msgid "Is active" msgid "Is active"
msgstr "激活" msgstr "激活"
...@@ -311,19 +304,19 @@ msgstr "激活" ...@@ -311,19 +304,19 @@ msgstr "激活"
msgid "Public IP" msgid "Public IP"
msgstr "公网IP" msgstr "公网IP"
#: assets/models/asset.py:92 assets/templates/assets/asset_detail.html:117 #: assets/models/asset.py:92 assets/templates/assets/asset_detail.html:121
msgid "Asset number" msgid "Asset number"
msgstr "资产编号" msgstr "资产编号"
#: assets/models/asset.py:96 assets/templates/assets/asset_detail.html:81 #: assets/models/asset.py:96 assets/templates/assets/asset_detail.html:85
msgid "Vendor" msgid "Vendor"
msgstr "制造商" msgstr "制造商"
#: assets/models/asset.py:98 assets/templates/assets/asset_detail.html:85 #: assets/models/asset.py:98 assets/templates/assets/asset_detail.html:89
msgid "Model" msgid "Model"
msgstr "型号" msgstr "型号"
#: assets/models/asset.py:100 assets/templates/assets/asset_detail.html:113 #: assets/models/asset.py:100 assets/templates/assets/asset_detail.html:117
msgid "Serial number" msgid "Serial number"
msgstr "序列号" msgstr "序列号"
...@@ -343,7 +336,7 @@ msgstr "CPU核数" ...@@ -343,7 +336,7 @@ msgstr "CPU核数"
msgid "CPU vcpus" msgid "CPU vcpus"
msgstr "CPU总数" msgstr "CPU总数"
#: assets/models/asset.py:108 assets/templates/assets/asset_detail.html:93 #: assets/models/asset.py:108 assets/templates/assets/asset_detail.html:97
msgid "Memory" msgid "Memory"
msgstr "内存" msgstr "内存"
...@@ -355,7 +348,7 @@ msgstr "硬盘大小" ...@@ -355,7 +348,7 @@ msgstr "硬盘大小"
msgid "Disk info" msgid "Disk info"
msgstr "硬盘信息" msgstr "硬盘信息"
#: assets/models/asset.py:115 assets/templates/assets/asset_detail.html:105 #: assets/models/asset.py:115 assets/templates/assets/asset_detail.html:109
#: assets/templates/assets/user_asset_list.html:155 #: assets/templates/assets/user_asset_list.html:155
msgid "OS" msgid "OS"
msgstr "操作系统" msgstr "操作系统"
...@@ -373,7 +366,7 @@ msgid "Hostname raw" ...@@ -373,7 +366,7 @@ msgid "Hostname raw"
msgstr "主机名原始" msgstr "主机名原始"
#: assets/models/asset.py:125 assets/templates/assets/asset_create.html:34 #: assets/models/asset.py:125 assets/templates/assets/asset_create.html:34
#: assets/templates/assets/asset_detail.html:224 #: assets/templates/assets/asset_detail.html:228
#: assets/templates/assets/asset_update.html:39 templates/_nav.html:26 #: assets/templates/assets/asset_update.html:39 templates/_nav.html:26
msgid "Labels" msgid "Labels"
msgstr "标签管理" msgstr "标签管理"
...@@ -382,7 +375,7 @@ msgstr "标签管理" ...@@ -382,7 +375,7 @@ msgstr "标签管理"
#: assets/models/cluster.py:28 assets/models/cmd_filter.py:25 #: assets/models/cluster.py:28 assets/models/cmd_filter.py:25
#: assets/models/cmd_filter.py:55 assets/models/group.py:21 #: assets/models/cmd_filter.py:55 assets/models/group.py:21
#: assets/templates/assets/admin_user_detail.html:68 #: assets/templates/assets/admin_user_detail.html:68
#: assets/templates/assets/asset_detail.html:121 #: assets/templates/assets/asset_detail.html:125
#: assets/templates/assets/cmd_filter_detail.html:77 #: assets/templates/assets/cmd_filter_detail.html:77
#: assets/templates/assets/domain_detail.html:72 #: assets/templates/assets/domain_detail.html:72
#: assets/templates/assets/system_user_detail.html:100 #: assets/templates/assets/system_user_detail.html:100
...@@ -394,8 +387,8 @@ msgid "Created by" ...@@ -394,8 +387,8 @@ msgid "Created by"
msgstr "创建者" msgstr "创建者"
#: assets/models/asset.py:130 assets/models/cluster.py:26 #: assets/models/asset.py:130 assets/models/cluster.py:26
#: assets/models/domain.py:21 assets/models/group.py:22 #: assets/models/domain.py:23 assets/models/group.py:22
#: assets/models/label.py:24 assets/templates/assets/admin_user_detail.html:64 #: assets/models/label.py:25 assets/templates/assets/admin_user_detail.html:64
#: assets/templates/assets/cmd_filter_detail.html:69 #: assets/templates/assets/cmd_filter_detail.html:69
#: assets/templates/assets/domain_detail.html:68 #: assets/templates/assets/domain_detail.html:68
#: assets/templates/assets/system_user_detail.html:96 #: assets/templates/assets/system_user_detail.html:96
...@@ -413,11 +406,11 @@ msgstr "创建日期" ...@@ -413,11 +406,11 @@ msgstr "创建日期"
#: assets/models/asset.py:132 assets/models/base.py:27 #: assets/models/asset.py:132 assets/models/base.py:27
#: assets/models/cluster.py:29 assets/models/cmd_filter.py:22 #: assets/models/cluster.py:29 assets/models/cmd_filter.py:22
#: assets/models/cmd_filter.py:52 assets/models/domain.py:19 #: assets/models/cmd_filter.py:52 assets/models/domain.py:21
#: assets/models/domain.py:51 assets/models/group.py:23 #: assets/models/domain.py:53 assets/models/group.py:23
#: assets/models/label.py:22 assets/templates/assets/admin_user_detail.html:72 #: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:72
#: assets/templates/assets/admin_user_list.html:32 #: assets/templates/assets/admin_user_list.html:32
#: assets/templates/assets/asset_detail.html:129 #: assets/templates/assets/asset_detail.html:133
#: assets/templates/assets/cmd_filter_detail.html:65 #: assets/templates/assets/cmd_filter_detail.html:65
#: assets/templates/assets/cmd_filter_list.html:27 #: assets/templates/assets/cmd_filter_list.html:27
#: assets/templates/assets/cmd_filter_rule_list.html:62 #: assets/templates/assets/cmd_filter_rule_list.html:62
...@@ -487,7 +480,7 @@ msgid "Default" ...@@ -487,7 +480,7 @@ msgid "Default"
msgstr "默认" msgstr "默认"
#: assets/models/cluster.py:36 assets/models/label.py:14 #: assets/models/cluster.py:36 assets/models/label.py:14
#: users/models/user.py:422 #: users/models/user.py:432
msgid "System" msgid "System"
msgstr "系统" msgstr "系统"
...@@ -516,7 +509,7 @@ msgid "Regex" ...@@ -516,7 +509,7 @@ msgid "Regex"
msgstr "正则表达式" msgstr "正则表达式"
#: assets/models/cmd_filter.py:36 ops/models/command.py:19 #: assets/models/cmd_filter.py:36 ops/models/command.py:19
#: ops/templates/ops/command_execution_list.html:46 terminal/models.py:144 #: ops/templates/ops/command_execution_list.html:60 terminal/models.py:144
#: terminal/templates/terminal/command_list.html:55 #: terminal/templates/terminal/command_list.html:55
#: terminal/templates/terminal/command_list.html:71 #: terminal/templates/terminal/command_list.html:71
#: terminal/templates/terminal/session_detail.html:48 #: terminal/templates/terminal/session_detail.html:48
...@@ -591,7 +584,7 @@ msgstr "每行一个命令" ...@@ -591,7 +584,7 @@ msgstr "每行一个命令"
msgid "Action" msgid "Action"
msgstr "动作" msgstr "动作"
#: assets/models/domain.py:59 assets/templates/assets/domain_detail.html:21 #: assets/models/domain.py:61 assets/templates/assets/domain_detail.html:21
#: assets/templates/assets/domain_detail.html:64 #: assets/templates/assets/domain_detail.html:64
#: assets/templates/assets/domain_gateway_list.html:21 #: assets/templates/assets/domain_gateway_list.html:21
#: assets/templates/assets/domain_list.html:27 #: assets/templates/assets/domain_list.html:27
...@@ -613,38 +606,42 @@ msgstr "默认资产组" ...@@ -613,38 +606,42 @@ msgstr "默认资产组"
#: audits/templates/audits/operate_log_list.html:66 #: audits/templates/audits/operate_log_list.html:66
#: audits/templates/audits/password_change_log_list.html:33 #: audits/templates/audits/password_change_log_list.html:33
#: audits/templates/audits/password_change_log_list.html:50 #: audits/templates/audits/password_change_log_list.html:50
#: ops/templates/ops/command_execution_list.html:22 #: ops/templates/ops/command_execution_list.html:34
#: ops/templates/ops/command_execution_list.html:45 perms/forms.py:28 #: ops/templates/ops/command_execution_list.html:59 perms/forms.py:28
#: perms/models.py:29 #: perms/models.py:29
#: perms/templates/perms/asset_permission_create_update.html:36 #: perms/templates/perms/asset_permission_create_update.html:36
#: perms/templates/perms/asset_permission_list.html:54 #: perms/templates/perms/asset_permission_list.html:54
#: perms/templates/perms/asset_permission_list.html:142 templates/index.html:87 #: perms/templates/perms/asset_permission_list.html:108 templates/index.html:87
#: terminal/backends/command/models.py:12 terminal/models.py:137 #: terminal/backends/command/models.py:12 terminal/models.py:137
#: terminal/templates/terminal/command_list.html:32 #: terminal/templates/terminal/command_list.html:32
#: terminal/templates/terminal/command_list.html:72 #: terminal/templates/terminal/command_list.html:72
#: terminal/templates/terminal/session_list.html:33 #: terminal/templates/terminal/session_list.html:33
#: terminal/templates/terminal/session_list.html:71 users/forms.py:310 #: terminal/templates/terminal/session_list.html:71 users/forms.py:314
#: users/models/user.py:33 users/models/user.py:410 #: users/models/user.py:33 users/models/user.py:420
#: users/templates/users/user_group_detail.html:78 #: users/templates/users/user_group_detail.html:78
#: users/templates/users/user_group_list.html:13 users/views/user.py:384 #: users/templates/users/user_group_list.html:13 users/views/user.py:386
#: xpack/plugins/orgs/forms.py:26 #: xpack/plugins/orgs/forms.py:26
#: xpack/plugins/orgs/templates/orgs/org_detail.html:113 #: xpack/plugins/orgs/templates/orgs/org_detail.html:113
#: xpack/plugins/orgs/templates/orgs/org_list.html:14 #: xpack/plugins/orgs/templates/orgs/org_list.html:14
msgid "User" msgid "User"
msgstr "用户" msgstr "用户"
#: assets/models/label.py:19 assets/models/node.py:20 #: assets/models/label.py:19 assets/models/node.py:21
#: assets/templates/assets/label_list.html:15 common/models.py:30 #: assets/templates/assets/label_list.html:15 common/models.py:30
msgid "Value" msgid "Value"
msgstr "值" msgstr "值"
#: assets/models/label.py:20 #: assets/models/label.py:21
msgid "Category" msgid "Category"
msgstr "分类" msgstr "分类"
#: assets/models/node.py:19 #: assets/models/node.py:20
msgid "Key" msgid "Key"
msgstr "" msgstr "键"
#: assets/models/node.py:127
msgid "New node"
msgstr "新节点"
#: assets/models/user.py:109 #: assets/models/user.py:109
msgid "Automatic login" msgid "Automatic login"
...@@ -701,7 +698,7 @@ msgstr "登录模式" ...@@ -701,7 +698,7 @@ msgstr "登录模式"
#: perms/models.py:33 perms/models.py:81 #: perms/models.py:33 perms/models.py:81
#: perms/templates/perms/asset_permission_detail.html:140 #: perms/templates/perms/asset_permission_detail.html:140
#: perms/templates/perms/asset_permission_list.html:58 #: perms/templates/perms/asset_permission_list.html:58
#: perms/templates/perms/asset_permission_list.html:154 templates/_nav.html:25 #: perms/templates/perms/asset_permission_list.html:120 templates/_nav.html:25
#: terminal/backends/command/models.py:14 terminal/models.py:139 #: terminal/backends/command/models.py:14 terminal/models.py:139
#: terminal/templates/terminal/command_list.html:48 #: terminal/templates/terminal/command_list.html:48
#: terminal/templates/terminal/command_list.html:74 #: terminal/templates/terminal/command_list.html:74
...@@ -733,7 +730,7 @@ msgid "Asset may not be support ansible, skipped: {}" ...@@ -733,7 +730,7 @@ msgid "Asset may not be support ansible, skipped: {}"
msgstr "资产或许不支持ansible, 跳过: {}" msgstr "资产或许不支持ansible, 跳过: {}"
#: assets/tasks.py:111 assets/tasks.py:210 assets/tasks.py:325 #: assets/tasks.py:111 assets/tasks.py:210 assets/tasks.py:325
#: assets/tasks.py:431 #: assets/tasks.py:432
msgid "No assets matched, stop task" msgid "No assets matched, stop task"
msgstr "没有匹配到资产,结束任务" msgstr "没有匹配到资产,结束任务"
...@@ -741,27 +738,23 @@ msgstr "没有匹配到资产,结束任务" ...@@ -741,27 +738,23 @@ msgstr "没有匹配到资产,结束任务"
msgid "Update asset hardware info: {}" msgid "Update asset hardware info: {}"
msgstr "更新资产硬件信息: {}" msgstr "更新资产硬件信息: {}"
#: assets/tasks.py:148
msgid "Update assets hardware info period"
msgstr "定期更新资产硬件信息"
#: assets/tasks.py:230 #: assets/tasks.py:230
msgid "Test admin user connectability period: {}" msgid "Test admin user connectivity period: {}"
msgstr "定期测试管理账号可连接性: {}" msgstr "定期测试管理账号可连接性: {}"
#: assets/tasks.py:236 #: assets/tasks.py:236
msgid "Test admin user connectability: {}" msgid "Test admin user connectivity: {}"
msgstr "测试管理行号可连接性: {}" msgstr "测试管理行号可连接性: {}"
#: assets/tasks.py:246 #: assets/tasks.py:246
msgid "Test assets connectability" msgid "Test assets connectivity"
msgstr "测试资产可连接性" msgstr "测试资产可连接性"
#: assets/tasks.py:251 assets/tasks.py:316 assets/tasks.py:422 #: assets/tasks.py:251 assets/tasks.py:316 assets/tasks.py:423
msgid "Asset has been disabled, skip: {}" msgid "Asset has been disabled, skip: {}"
msgstr "资产被禁用,跳过:{}" msgstr "资产被禁用,跳过:{}"
#: assets/tasks.py:255 assets/tasks.py:320 assets/tasks.py:426 #: assets/tasks.py:255 assets/tasks.py:320 assets/tasks.py:427
msgid "Asset may not be support ansible, skip: {}" msgid "Asset may not be support ansible, skip: {}"
msgstr "资产或许不支持ansible, 跳过: {}" msgstr "资产或许不支持ansible, 跳过: {}"
...@@ -770,31 +763,27 @@ msgid "No assets, task stop" ...@@ -770,31 +763,27 @@ msgid "No assets, task stop"
msgstr "没有匹配到资产,结束任务" msgstr "没有匹配到资产,结束任务"
#: assets/tasks.py:280 #: assets/tasks.py:280
msgid "Test assets connectability: {}" msgid "Test assets connectivity: {}"
msgstr "测试资产可连接性: {}" msgstr "测试资产可连接性: {}"
#: assets/tasks.py:339 #: assets/tasks.py:339
msgid "Test system user connectability: {}" msgid "Test system user connectivity: {}"
msgstr "测试系统用户可连接性: {}" msgstr "测试系统用户可连接性: {}"
#: assets/tasks.py:346 #: assets/tasks.py:346
msgid "Test system user connectability: {} => {}" msgid "Test system user connectivity: {} => {}"
msgstr "测试系统用户可连接性: {} => {}" msgstr "测试系统用户可连接性: {} => {}"
#: assets/tasks.py:359 #: assets/tasks.py:414
msgid "Test system user connectability period: {}"
msgstr "定期测试系统用户可连接性: {}"
#: assets/tasks.py:413
msgid "" msgid ""
"Push system user task skip, auto push not enable or protocol is not ssh: {}" "Push system user task skip, auto push not enable or protocol is not ssh: {}"
msgstr "推送系统用户任务跳过,自动推送没有打开,或协议不是ssh: {}" msgstr "推送系统用户任务跳过,自动推送没有打开,或协议不是ssh: {}"
#: assets/tasks.py:444 assets/tasks.py:458 #: assets/tasks.py:445 assets/tasks.py:459
msgid "Push system users to assets: {}" msgid "Push system users to assets: {}"
msgstr "推送系统用户到入资产: {}" msgstr "推送系统用户到入资产: {}"
#: assets/tasks.py:450 #: assets/tasks.py:451
msgid "Push system users to asset: {} => {}" msgid "Push system users to asset: {} => {}"
msgstr "推送系统用户到入资产: {} => {}" msgstr "推送系统用户到入资产: {} => {}"
...@@ -899,9 +888,9 @@ msgstr "其它" ...@@ -899,9 +888,9 @@ msgstr "其它"
#: common/templates/common/replay_storage_create.html:139 #: common/templates/common/replay_storage_create.html:139
#: common/templates/common/security_setting.html:70 #: common/templates/common/security_setting.html:70
#: common/templates/common/terminal_setting.html:68 #: common/templates/common/terminal_setting.html:68
#: perms/templates/perms/asset_permission_create_update.html:69 #: perms/templates/perms/asset_permission_create_update.html:75
#: terminal/templates/terminal/terminal_update.html:47 #: terminal/templates/terminal/terminal_update.html:47
#: users/templates/users/_user.html:46 #: users/templates/users/_user.html:50
#: users/templates/users/user_bulk_update.html:23 #: users/templates/users/user_bulk_update.html:23
#: users/templates/users/user_detail.html:176 #: users/templates/users/user_detail.html:176
#: users/templates/users/user_password_update.html:71 #: users/templates/users/user_password_update.html:71
...@@ -932,11 +921,11 @@ msgstr "重置" ...@@ -932,11 +921,11 @@ msgstr "重置"
#: common/templates/common/replay_storage_create.html:140 #: common/templates/common/replay_storage_create.html:140
#: common/templates/common/security_setting.html:71 #: common/templates/common/security_setting.html:71
#: common/templates/common/terminal_setting.html:70 #: common/templates/common/terminal_setting.html:70
#: perms/templates/perms/asset_permission_create_update.html:70 #: perms/templates/perms/asset_permission_create_update.html:76
#: terminal/templates/terminal/command_list.html:103 #: terminal/templates/terminal/command_list.html:103
#: terminal/templates/terminal/session_list.html:127 #: terminal/templates/terminal/session_list.html:127
#: terminal/templates/terminal/terminal_update.html:48 #: terminal/templates/terminal/terminal_update.html:48
#: users/templates/users/_user.html:47 #: users/templates/users/_user.html:51
#: users/templates/users/forgot_password.html:45 #: users/templates/users/forgot_password.html:45
#: users/templates/users/user_bulk_update.html:24 #: users/templates/users/user_bulk_update.html:24
#: users/templates/users/user_list.html:45 #: users/templates/users/user_list.html:45
...@@ -999,12 +988,12 @@ msgid "Quick update" ...@@ -999,12 +988,12 @@ msgid "Quick update"
msgstr "快速更新" msgstr "快速更新"
#: assets/templates/assets/admin_user_assets.html:72 #: assets/templates/assets/admin_user_assets.html:72
#: assets/templates/assets/asset_detail.html:172 #: assets/templates/assets/asset_detail.html:176
msgid "Test connective" msgid "Test connective"
msgstr "测试可连接性" msgstr "测试可连接性"
#: assets/templates/assets/admin_user_assets.html:75 #: assets/templates/assets/admin_user_assets.html:75
#: assets/templates/assets/asset_detail.html:175 #: assets/templates/assets/asset_detail.html:179
#: assets/templates/assets/system_user_asset.html:75 #: assets/templates/assets/system_user_asset.html:75
#: assets/templates/assets/system_user_asset.html:161 #: assets/templates/assets/system_user_asset.html:161
#: assets/templates/assets/system_user_detail.html:151 #: assets/templates/assets/system_user_detail.html:151
...@@ -1014,7 +1003,7 @@ msgstr "测试" ...@@ -1014,7 +1003,7 @@ msgstr "测试"
#: assets/templates/assets/admin_user_detail.html:24 #: assets/templates/assets/admin_user_detail.html:24
#: assets/templates/assets/admin_user_list.html:88 #: assets/templates/assets/admin_user_list.html:88
#: assets/templates/assets/asset_detail.html:24 #: assets/templates/assets/asset_detail.html:24
#: assets/templates/assets/asset_list.html:171 #: assets/templates/assets/asset_list.html:174
#: assets/templates/assets/cmd_filter_detail.html:29 #: assets/templates/assets/cmd_filter_detail.html:29
#: assets/templates/assets/cmd_filter_list.html:57 #: assets/templates/assets/cmd_filter_list.html:57
#: assets/templates/assets/cmd_filter_rule_list.html:86 #: assets/templates/assets/cmd_filter_rule_list.html:86
...@@ -1026,13 +1015,13 @@ msgstr "测试" ...@@ -1026,13 +1015,13 @@ msgstr "测试"
#: assets/templates/assets/system_user_detail.html:26 #: assets/templates/assets/system_user_detail.html:26
#: assets/templates/assets/system_user_list.html:92 audits/models.py:32 #: assets/templates/assets/system_user_list.html:92 audits/models.py:32
#: perms/templates/perms/asset_permission_detail.html:30 #: perms/templates/perms/asset_permission_detail.html:30
#: perms/templates/perms/asset_permission_list.html:200 #: perms/templates/perms/asset_permission_list.html:166
#: terminal/templates/terminal/terminal_detail.html:16 #: terminal/templates/terminal/terminal_detail.html:16
#: terminal/templates/terminal/terminal_list.html:71 #: terminal/templates/terminal/terminal_list.html:71
#: users/templates/users/user_detail.html:25 #: users/templates/users/user_detail.html:25
#: users/templates/users/user_group_detail.html:28 #: users/templates/users/user_group_detail.html:28
#: users/templates/users/user_group_list.html:43 #: users/templates/users/user_group_list.html:43
#: users/templates/users/user_list.html:77 #: users/templates/users/user_list.html:80
#: users/templates/users/user_profile.html:155 #: users/templates/users/user_profile.html:155
#: users/templates/users/user_profile.html:185 #: users/templates/users/user_profile.html:185
#: users/templates/users/user_profile.html:194 #: users/templates/users/user_profile.html:194
...@@ -1046,7 +1035,7 @@ msgstr "更新" ...@@ -1046,7 +1035,7 @@ msgstr "更新"
#: assets/templates/assets/admin_user_detail.html:28 #: assets/templates/assets/admin_user_detail.html:28
#: assets/templates/assets/admin_user_list.html:89 #: assets/templates/assets/admin_user_list.html:89
#: assets/templates/assets/asset_detail.html:28 #: assets/templates/assets/asset_detail.html:28
#: assets/templates/assets/asset_list.html:172 #: assets/templates/assets/asset_list.html:175
#: assets/templates/assets/cmd_filter_detail.html:33 #: assets/templates/assets/cmd_filter_detail.html:33
#: assets/templates/assets/cmd_filter_list.html:58 #: assets/templates/assets/cmd_filter_list.html:58
#: assets/templates/assets/cmd_filter_rule_list.html:87 #: assets/templates/assets/cmd_filter_rule_list.html:87
...@@ -1061,13 +1050,13 @@ msgstr "更新" ...@@ -1061,13 +1050,13 @@ msgstr "更新"
#: common/templates/common/terminal_setting.html:112 #: common/templates/common/terminal_setting.html:112
#: ops/templates/ops/task_list.html:72 #: ops/templates/ops/task_list.html:72
#: perms/templates/perms/asset_permission_detail.html:34 #: perms/templates/perms/asset_permission_detail.html:34
#: perms/templates/perms/asset_permission_list.html:201 #: perms/templates/perms/asset_permission_list.html:167
#: terminal/templates/terminal/terminal_list.html:73 #: terminal/templates/terminal/terminal_list.html:73
#: users/templates/users/user_detail.html:30 #: users/templates/users/user_detail.html:30
#: users/templates/users/user_group_detail.html:32 #: users/templates/users/user_group_detail.html:32
#: users/templates/users/user_group_list.html:45 #: users/templates/users/user_group_list.html:45
#: users/templates/users/user_list.html:81 #: users/templates/users/user_list.html:84
#: users/templates/users/user_list.html:85 #: users/templates/users/user_list.html:88
#: xpack/plugins/cloud/templates/cloud/account_detail.html:29 #: xpack/plugins/cloud/templates/cloud/account_detail.html:29
#: xpack/plugins/cloud/templates/cloud/account_list.html:40 #: xpack/plugins/cloud/templates/cloud/account_list.html:40
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:32 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:32
...@@ -1087,8 +1076,8 @@ msgid "Select nodes" ...@@ -1087,8 +1076,8 @@ msgid "Select nodes"
msgstr "选择节点" msgstr "选择节点"
#: assets/templates/assets/admin_user_detail.html:100 #: assets/templates/assets/admin_user_detail.html:100
#: assets/templates/assets/asset_detail.html:204 #: assets/templates/assets/asset_detail.html:208
#: assets/templates/assets/asset_list.html:633 #: assets/templates/assets/asset_list.html:623
#: assets/templates/assets/cmd_filter_detail.html:106 #: assets/templates/assets/cmd_filter_detail.html:106
#: assets/templates/assets/system_user_asset.html:112 #: assets/templates/assets/system_user_asset.html:112
#: assets/templates/assets/system_user_detail.html:182 #: assets/templates/assets/system_user_detail.html:182
...@@ -1101,7 +1090,7 @@ msgstr "选择节点" ...@@ -1101,7 +1090,7 @@ msgstr "选择节点"
#: users/templates/users/user_detail.html:480 #: users/templates/users/user_detail.html:480
#: users/templates/users/user_group_create_update.html:32 #: users/templates/users/user_group_create_update.html:32
#: users/templates/users/user_group_list.html:88 #: users/templates/users/user_group_list.html:88
#: users/templates/users/user_list.html:205 #: users/templates/users/user_list.html:208
#: users/templates/users/user_profile.html:236 #: users/templates/users/user_profile.html:236
#: xpack/plugins/cloud/templates/cloud/account_create_update.html:34 #: xpack/plugins/cloud/templates/cloud/account_create_update.html:34
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:36 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:36
...@@ -1153,28 +1142,28 @@ msgstr "选择需要修改属性" ...@@ -1153,28 +1142,28 @@ msgstr "选择需要修改属性"
msgid "Select all" msgid "Select all"
msgstr "全选" msgstr "全选"
#: assets/templates/assets/asset_detail.html:89 #: assets/templates/assets/asset_detail.html:93
msgid "CPU" msgid "CPU"
msgstr "CPU" msgstr "CPU"
#: assets/templates/assets/asset_detail.html:97 #: assets/templates/assets/asset_detail.html:101
msgid "Disk" msgid "Disk"
msgstr "硬盘" msgstr "硬盘"
#: assets/templates/assets/asset_detail.html:125 #: assets/templates/assets/asset_detail.html:129
#: users/templates/users/user_detail.html:115 #: users/templates/users/user_detail.html:115
#: users/templates/users/user_profile.html:104 #: users/templates/users/user_profile.html:104
msgid "Date joined" msgid "Date joined"
msgstr "创建日期" msgstr "创建日期"
#: assets/templates/assets/asset_detail.html:141 #: assets/templates/assets/asset_detail.html:145
#: terminal/templates/terminal/session_detail.html:81 #: terminal/templates/terminal/session_detail.html:81
#: users/templates/users/user_detail.html:138 #: users/templates/users/user_detail.html:138
#: users/templates/users/user_profile.html:146 #: users/templates/users/user_profile.html:146
msgid "Quick modify" msgid "Quick modify"
msgstr "快速修改" msgstr "快速修改"
#: assets/templates/assets/asset_detail.html:147 #: assets/templates/assets/asset_detail.html:151
#: assets/templates/assets/asset_list.html:95 #: assets/templates/assets/asset_list.html:95
#: assets/templates/assets/user_asset_list.html:47 perms/models.py:34 #: assets/templates/assets/user_asset_list.html:47 perms/models.py:34
#: perms/models.py:82 #: perms/models.py:82
...@@ -1186,20 +1175,20 @@ msgstr "快速修改" ...@@ -1186,20 +1175,20 @@ msgstr "快速修改"
#: users/templates/users/user_detail.html:144 #: users/templates/users/user_detail.html:144
#: users/templates/users/user_granted_asset.html:46 #: users/templates/users/user_granted_asset.html:46
#: users/templates/users/user_group_granted_asset.html:46 #: users/templates/users/user_group_granted_asset.html:46
#: users/templates/users/user_list.html:28
#: users/templates/users/user_profile.html:63 #: users/templates/users/user_profile.html:63
msgid "Active" msgid "Active"
msgstr "激活中" msgstr "激活中"
#: assets/templates/assets/asset_detail.html:164 #: assets/templates/assets/asset_detail.html:168
msgid "Refresh hardware" msgid "Refresh hardware"
msgstr "更新硬件信息" msgstr "更新硬件信息"
#: assets/templates/assets/asset_detail.html:167 #: assets/templates/assets/asset_detail.html:171
#: assets/templates/assets/asset_list.html:139
msgid "Refresh" msgid "Refresh"
msgstr "刷新" msgstr "刷新"
#: assets/templates/assets/asset_detail.html:304 #: assets/templates/assets/asset_detail.html:308
#: users/templates/users/user_detail.html:305 #: users/templates/users/user_detail.html:305
#: users/templates/users/user_detail.html:332 #: users/templates/users/user_detail.html:332
msgid "Update successfully!" msgid "Update successfully!"
...@@ -1292,41 +1281,41 @@ msgstr "仅显示当前节点资产" ...@@ -1292,41 +1281,41 @@ msgstr "仅显示当前节点资产"
msgid "Displays all child node assets" msgid "Displays all child node assets"
msgstr "显示所有子节点资产" msgstr "显示所有子节点资产"
#: assets/templates/assets/asset_list.html:208 #: assets/templates/assets/asset_list.html:213
msgid "Create node failed" msgid "Create node failed"
msgstr "创建节点失败" msgstr "创建节点失败"
#: assets/templates/assets/asset_list.html:220 #: assets/templates/assets/asset_list.html:225
msgid "Have child node, cancel" msgid "Have child node, cancel"
msgstr "存在子节点,不能删除" msgstr "存在子节点,不能删除"
#: assets/templates/assets/asset_list.html:222 #: assets/templates/assets/asset_list.html:227
msgid "Have assets, cancel" msgid "Have assets, cancel"
msgstr "存在资产,不能删除" msgstr "存在资产,不能删除"
#: assets/templates/assets/asset_list.html:293 #: assets/templates/assets/asset_list.html:298
msgid "Rename success" msgid "Rename success"
msgstr "重命名成功" msgstr "重命名成功"
#: assets/templates/assets/asset_list.html:294 #: assets/templates/assets/asset_list.html:299
msgid "Rename failed, do not change the root node name" msgid "Rename failed, do not change the root node name"
msgstr "重命名失败,不能更改root节点的名称" msgstr "重命名失败,不能更改root节点的名称"
#: assets/templates/assets/asset_list.html:627 #: assets/templates/assets/asset_list.html:617
#: assets/templates/assets/system_user_list.html:137 #: assets/templates/assets/system_user_list.html:137
#: users/templates/users/user_detail.html:380 #: users/templates/users/user_detail.html:380
#: users/templates/users/user_detail.html:406 #: users/templates/users/user_detail.html:406
#: users/templates/users/user_detail.html:474 #: users/templates/users/user_detail.html:474
#: users/templates/users/user_group_list.html:82 #: users/templates/users/user_group_list.html:82
#: users/templates/users/user_list.html:199 #: users/templates/users/user_list.html:202
msgid "Are you sure?" msgid "Are you sure?"
msgstr "你确认吗?" msgstr "你确认吗?"
#: assets/templates/assets/asset_list.html:628 #: assets/templates/assets/asset_list.html:618
msgid "This will delete the selected assets !!!" msgid "This will delete the selected assets !!!"
msgstr "删除选择资产" msgstr "删除选择资产"
#: assets/templates/assets/asset_list.html:631 #: assets/templates/assets/asset_list.html:621
#: assets/templates/assets/system_user_list.html:141 #: assets/templates/assets/system_user_list.html:141
#: common/templates/common/terminal_setting.html:163 #: common/templates/common/terminal_setting.html:163
#: users/templates/users/user_detail.html:384 #: users/templates/users/user_detail.html:384
...@@ -1334,21 +1323,21 @@ msgstr "删除选择资产" ...@@ -1334,21 +1323,21 @@ msgstr "删除选择资产"
#: users/templates/users/user_detail.html:478 #: users/templates/users/user_detail.html:478
#: users/templates/users/user_group_create_update.html:31 #: users/templates/users/user_group_create_update.html:31
#: users/templates/users/user_group_list.html:86 #: users/templates/users/user_group_list.html:86
#: users/templates/users/user_list.html:203 #: users/templates/users/user_list.html:206
#: xpack/plugins/orgs/templates/orgs/org_create_update.html:32 #: xpack/plugins/orgs/templates/orgs/org_create_update.html:32
msgid "Cancel" msgid "Cancel"
msgstr "取消" msgstr "取消"
#: assets/templates/assets/asset_list.html:637 #: assets/templates/assets/asset_list.html:627
msgid "Asset Deleted." msgid "Asset Deleted."
msgstr "已被删除" msgstr "已被删除"
#: assets/templates/assets/asset_list.html:638 #: assets/templates/assets/asset_list.html:628
#: assets/templates/assets/asset_list.html:643 #: assets/templates/assets/asset_list.html:633
msgid "Asset Delete" msgid "Asset Delete"
msgstr "删除" msgstr "删除"
#: assets/templates/assets/asset_list.html:642 #: assets/templates/assets/asset_list.html:632
msgid "Asset Deleting failed." msgid "Asset Deleting failed."
msgstr "删除失败" msgstr "删除失败"
...@@ -1585,7 +1574,7 @@ msgstr "批量更新资产" ...@@ -1585,7 +1574,7 @@ msgstr "批量更新资产"
msgid "Update asset" msgid "Update asset"
msgstr "更新资产" msgstr "更新资产"
#: assets/views/asset.py:293 #: assets/views/asset.py:292
msgid "already exists" msgid "already exists"
msgstr "已经存在" msgstr "已经存在"
...@@ -1680,7 +1669,7 @@ msgid "Filename" ...@@ -1680,7 +1669,7 @@ msgid "Filename"
msgstr "文件名" msgstr "文件名"
#: audits/models.py:22 audits/templates/audits/ftp_log_list.html:76 #: audits/models.py:22 audits/templates/audits/ftp_log_list.html:76
#: ops/templates/ops/command_execution_list.html:49 #: ops/templates/ops/command_execution_list.html:64
#: ops/templates/ops/task_list.html:39 users/models/authentication.py:73 #: ops/templates/ops/task_list.html:39 users/models/authentication.py:73
#: users/templates/users/user_detail.html:456 xpack/plugins/cloud/api.py:61 #: users/templates/users/user_detail.html:456 xpack/plugins/cloud/api.py:61
msgid "Success" msgid "Success"
...@@ -1706,7 +1695,7 @@ msgstr "修改者" ...@@ -1706,7 +1695,7 @@ msgstr "修改者"
#: audits/templates/audits/ftp_log_list.html:77 #: audits/templates/audits/ftp_log_list.html:77
#: ops/templates/ops/adhoc_history.html:52 #: ops/templates/ops/adhoc_history.html:52
#: ops/templates/ops/adhoc_history_detail.html:61 #: ops/templates/ops/adhoc_history_detail.html:61
#: ops/templates/ops/command_execution_list.html:50 #: ops/templates/ops/command_execution_list.html:65
#: ops/templates/ops/task_history.html:58 perms/models.py:35 #: ops/templates/ops/task_history.html:58 perms/models.py:35
#: perms/templates/perms/asset_permission_detail.html:86 terminal/models.py:148 #: perms/templates/perms/asset_permission_detail.html:86 terminal/models.py:148
#: terminal/templates/terminal/session_list.html:78 #: terminal/templates/terminal/session_list.html:78
...@@ -1722,8 +1711,8 @@ msgstr "选择用户" ...@@ -1722,8 +1711,8 @@ msgstr "选择用户"
#: audits/templates/audits/login_log_list.html:40 #: audits/templates/audits/login_log_list.html:40
#: audits/templates/audits/operate_log_list.html:58 #: audits/templates/audits/operate_log_list.html:58
#: audits/templates/audits/password_change_log_list.html:42 #: audits/templates/audits/password_change_log_list.html:42
#: ops/templates/ops/command_execution_list.html:30 #: ops/templates/ops/command_execution_list.html:42
#: ops/templates/ops/command_execution_list.html:35 #: ops/templates/ops/command_execution_list.html:47
#: ops/templates/ops/task_list.html:21 ops/templates/ops/task_list.html:26 #: ops/templates/ops/task_list.html:21 ops/templates/ops/task_list.html:26
#: templates/_base_list.html:43 templates/_header_bar.html:8 #: templates/_base_list.html:43 templates/_header_bar.html:8
#: terminal/templates/terminal/command_list.html:60 #: terminal/templates/terminal/command_list.html:60
...@@ -1751,7 +1740,7 @@ msgstr "Agent" ...@@ -1751,7 +1740,7 @@ msgstr "Agent"
msgid "City" msgid "City"
msgstr "城市" msgstr "城市"
#: audits/templates/audits/login_log_list.html:54 users/forms.py:168 #: audits/templates/audits/login_log_list.html:54 users/forms.py:172
#: users/models/authentication.py:82 users/models/user.py:75 #: users/models/authentication.py:82 users/models/user.py:75
#: users/templates/users/first_login.html:45 #: users/templates/users/first_login.html:45
msgid "MFA" msgid "MFA"
...@@ -1785,23 +1774,23 @@ msgid "Datetime" ...@@ -1785,23 +1774,23 @@ msgid "Datetime"
msgstr "日期" msgstr "日期"
#: audits/views.py:68 audits/views.py:112 audits/views.py:148 #: audits/views.py:68 audits/views.py:112 audits/views.py:148
#: audits/views.py:192 audits/views.py:223 templates/_nav.html:71 #: audits/views.py:192 audits/views.py:223 templates/_nav.html:72
msgid "Audits" msgid "Audits"
msgstr "日志审计" msgstr "日志审计"
#: audits/views.py:69 templates/_nav.html:75 #: audits/views.py:69 templates/_nav.html:76
msgid "FTP log" msgid "FTP log"
msgstr "FTP日志" msgstr "FTP日志"
#: audits/views.py:113 templates/_nav.html:76 #: audits/views.py:113 templates/_nav.html:77
msgid "Operate log" msgid "Operate log"
msgstr "操作日志" msgstr "操作日志"
#: audits/views.py:149 templates/_nav.html:77 #: audits/views.py:149 templates/_nav.html:78
msgid "Password change log" msgid "Password change log"
msgstr "改密日志" msgstr "改密日志"
#: audits/views.py:193 templates/_nav.html:74 #: audits/views.py:193 templates/_nav.html:75
msgid "Login log" msgid "Login log"
msgstr "登录日志" msgstr "登录日志"
...@@ -1958,64 +1947,86 @@ msgid "Enable LDAP auth" ...@@ -1958,64 +1947,86 @@ msgid "Enable LDAP auth"
msgstr "启用LDAP认证" msgstr "启用LDAP认证"
#: common/forms.py:138 #: common/forms.py:138
msgid "All"
msgstr "全部"
#: common/forms.py:139
msgid "Auto"
msgstr "自动"
#: common/forms.py:146
msgid "Password auth" msgid "Password auth"
msgstr "密码认证" msgstr "密码认证"
#: common/forms.py:141 #: common/forms.py:149
msgid "Public key auth" msgid "Public key auth"
msgstr "密钥认证" msgstr "密钥认证"
#: common/forms.py:144 #: common/forms.py:152
msgid "Heartbeat interval" msgid "Heartbeat interval"
msgstr "心跳间隔" msgstr "心跳间隔"
#: common/forms.py:144 ops/models/adhoc.py:38 #: common/forms.py:152 ops/models/adhoc.py:38
msgid "Units: seconds" msgid "Units: seconds"
msgstr "单位: 秒" msgstr "单位: 秒"
#: common/forms.py:147 #: common/forms.py:155
msgid "List sort by" msgid "List sort by"
msgstr "资产列表排序" msgstr "资产列表排序"
#: common/forms.py:159 #: common/forms.py:158
msgid "List page size"
msgstr "资产分页每页数量"
#: common/forms.py:161
msgid "Session keep duration"
msgstr "会话保留时长"
#: common/forms.py:162
msgid ""
"Units: days, Session, record, command will be delete if more than duration, "
"only in database"
msgstr "单位:天。 会话、录像、命令记录超过该时长将会被删除(仅影响数据库存储, oss等不受影响)"
#: common/forms.py:175
msgid "MFA Secondary certification" msgid "MFA Secondary certification"
msgstr "MFA 二次认证" msgstr "MFA 二次认证"
#: common/forms.py:161 #: common/forms.py:177
msgid "" msgid ""
"After opening, the user login must use MFA secondary authentication (valid " "After opening, the user login must use MFA secondary authentication (valid "
"for all users, including administrators)" "for all users, including administrators)"
msgstr "开启后,用户登录必须使用MFA二次认证(对所有用户有效,包括管理员)" msgstr "开启后,用户登录必须使用MFA二次认证(对所有用户有效,包括管理员)"
#: common/forms.py:168 #: common/forms.py:184
msgid "Limit the number of login failures" msgid "Limit the number of login failures"
msgstr "限制登录失败次数" msgstr "限制登录失败次数"
#: common/forms.py:173 #: common/forms.py:189
msgid "No logon interval" msgid "No logon interval"
msgstr "禁止登录时间间隔" msgstr "禁止登录时间间隔"
#: common/forms.py:175 #: common/forms.py:191
msgid "" msgid ""
"Tip: (unit/minute) if the user has failed to log in for a limited number of " "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." "times, no login is allowed during this time interval."
msgstr "" msgstr ""
"提示:(单位:分)当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录" "提示:(单位:分)当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录"
#: common/forms.py:182 #: common/forms.py:198
msgid "Connection max idle time" msgid "Connection max idle time"
msgstr "SSH最大空闲时间" msgstr "SSH最大空闲时间"
#: common/forms.py:184 #: common/forms.py:200
msgid "" msgid ""
"If idle time more than it, disconnect connection(only ssh now) Unit: minute" "If idle time more than it, disconnect connection(only ssh now) Unit: minute"
msgstr "提示:(单位:分)如果超过该配置没有操作,连接会被断开(仅ssh)" msgstr "提示:(单位:分)如果超过该配置没有操作,连接会被断开(仅ssh)"
#: common/forms.py:190 #: common/forms.py:206
msgid "Password expiration time" msgid "Password expiration time"
msgstr "密码过期时间" msgstr "密码过期时间"
#: common/forms.py:193 #: common/forms.py:209
msgid "" msgid ""
"Tip: (unit: day) If the user does not update the password during the time, " "Tip: (unit: day) If the user does not update the password during the time, "
"the user password will expire failure;The password expiration reminder mail " "the user password will expire failure;The password expiration reminder mail "
...@@ -2025,45 +2036,45 @@ msgstr "" ...@@ -2025,45 +2036,45 @@ msgstr ""
"提示:(单位:天)如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期" "提示:(单位:天)如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期"
"提醒邮件将在密码过期前5天内由系统(每天)自动发送给用户" "提醒邮件将在密码过期前5天内由系统(每天)自动发送给用户"
#: common/forms.py:202 #: common/forms.py:218
msgid "Password minimum length" msgid "Password minimum length"
msgstr "密码最小长度 " msgstr "密码最小长度 "
#: common/forms.py:208 #: common/forms.py:224
msgid "Must contain capital letters" msgid "Must contain capital letters"
msgstr "必须包含大写字母" msgstr "必须包含大写字母"
#: common/forms.py:210 #: common/forms.py:226
msgid "" msgid ""
"After opening, the user password changes and resets must contain uppercase " "After opening, the user password changes and resets must contain uppercase "
"letters" "letters"
msgstr "开启后,用户密码修改、重置必须包含大写字母" msgstr "开启后,用户密码修改、重置必须包含大写字母"
#: common/forms.py:216 #: common/forms.py:232
msgid "Must contain lowercase letters" msgid "Must contain lowercase letters"
msgstr "必须包含小写字母" msgstr "必须包含小写字母"
#: common/forms.py:217 #: common/forms.py:233
msgid "" msgid ""
"After opening, the user password changes and resets must contain lowercase " "After opening, the user password changes and resets must contain lowercase "
"letters" "letters"
msgstr "开启后,用户密码修改、重置必须包含小写字母" msgstr "开启后,用户密码修改、重置必须包含小写字母"
#: common/forms.py:223 #: common/forms.py:239
msgid "Must contain numeric characters" msgid "Must contain numeric characters"
msgstr "必须包含数字字符" msgstr "必须包含数字字符"
#: common/forms.py:224 #: common/forms.py:240
msgid "" msgid ""
"After opening, the user password changes and resets must contain numeric " "After opening, the user password changes and resets must contain numeric "
"characters" "characters"
msgstr "开启后,用户密码修改、重置必须包含数字字符" msgstr "开启后,用户密码修改、重置必须包含数字字符"
#: common/forms.py:230 #: common/forms.py:246
msgid "Must contain special characters" msgid "Must contain special characters"
msgstr "必须包含特殊字符" msgstr "必须包含特殊字符"
#: common/forms.py:231 #: common/forms.py:247
msgid "" msgid ""
"After opening, the user password changes and resets must contain special " "After opening, the user password changes and resets must contain special "
"characters" "characters"
...@@ -2126,7 +2137,7 @@ msgstr "安全设置" ...@@ -2126,7 +2137,7 @@ msgstr "安全设置"
#: common/templates/common/command_storage_create.html:50 #: common/templates/common/command_storage_create.html:50
#: ops/models/adhoc.py:161 ops/templates/ops/adhoc_detail.html:53 #: ops/models/adhoc.py:161 ops/templates/ops/adhoc_detail.html:53
#: ops/templates/ops/command_execution_list.html:44 #: ops/templates/ops/command_execution_list.html:58
#: ops/templates/ops/task_adhoc.html:59 ops/templates/ops/task_list.html:38 #: ops/templates/ops/task_adhoc.html:59 ops/templates/ops/task_list.html:38
msgid "Hosts" msgid "Hosts"
msgstr "主机" msgstr "主机"
...@@ -2227,7 +2238,7 @@ msgstr "不能包含特殊字符" ...@@ -2227,7 +2238,7 @@ msgstr "不能包含特殊字符"
#: common/views.py:18 common/views.py:44 common/views.py:70 common/views.py:99 #: common/views.py:18 common/views.py:44 common/views.py:70 common/views.py:99
#: common/views.py:126 common/views.py:138 common/views.py:151 #: common/views.py:126 common/views.py:138 common/views.py:151
#: templates/_nav.html:106 #: templates/_nav.html:107
msgid "Settings" msgid "Settings"
msgstr "系统设置" msgstr "系统设置"
...@@ -2339,15 +2350,15 @@ msgstr "汇总" ...@@ -2339,15 +2350,15 @@ msgstr "汇总"
msgid "Result" msgid "Result"
msgstr "结果" msgstr "结果"
#: ops/models/command.py:52 #: ops/models/command.py:55
msgid "Task start" msgid "Task start"
msgstr "任务开始" msgstr "任务开始"
#: ops/models/command.py:64 #: ops/models/command.py:67
msgid "Command `{}` is forbidden ........" msgid "Command `{}` is forbidden ........"
msgstr "命令 `{}` 不允许被执行 ......." msgstr "命令 `{}` 不允许被执行 ......."
#: ops/models/command.py:70 #: ops/models/command.py:73
msgid "Task end" msgid "Task end"
msgstr "任务结束" msgstr "任务结束"
...@@ -2357,12 +2368,14 @@ msgid "Version detail" ...@@ -2357,12 +2368,14 @@ msgid "Version detail"
msgstr "版本详情" msgstr "版本详情"
#: ops/templates/ops/adhoc_detail.html:22 #: ops/templates/ops/adhoc_detail.html:22
#: ops/templates/ops/adhoc_history.html:22 ops/views/adhoc.py:124 #: ops/templates/ops/adhoc_history.html:22 ops/views/adhoc.py:127
msgid "Version run history" msgid "Version run history"
msgstr "执行历史" msgstr "执行历史"
#: ops/templates/ops/adhoc_detail.html:72 #: ops/templates/ops/adhoc_detail.html:72
#: ops/templates/ops/adhoc_detail.html:77 ops/templates/ops/task_adhoc.html:61 #: ops/templates/ops/adhoc_detail.html:77
#: ops/templates/ops/command_execution_list.html:61
#: ops/templates/ops/task_adhoc.html:61
msgid "Run as" msgid "Run as"
msgstr "运行用户" msgstr "运行用户"
...@@ -2419,12 +2432,12 @@ msgstr "失败/成功/总" ...@@ -2419,12 +2432,12 @@ msgstr "失败/成功/总"
msgid "Version" msgid "Version"
msgstr "版本" msgstr "版本"
#: ops/templates/ops/adhoc_history_detail.html:19 ops/views/adhoc.py:137 #: ops/templates/ops/adhoc_history_detail.html:19 ops/views/adhoc.py:140
msgid "Run history detail" msgid "Run history detail"
msgstr "执行历史详情" msgstr "执行历史详情"
#: ops/templates/ops/adhoc_history_detail.html:22 #: ops/templates/ops/adhoc_history_detail.html:22
#: ops/templates/ops/command_execution_list.html:47 #: ops/templates/ops/command_execution_list.html:62
#: terminal/backends/command/models.py:16 #: terminal/backends/command/models.py:16
msgid "Output" msgid "Output"
msgstr "输出" msgstr "输出"
...@@ -2450,31 +2463,27 @@ msgstr "没有资产" ...@@ -2450,31 +2463,27 @@ msgstr "没有资产"
msgid "Success assets" msgid "Success assets"
msgstr "成功资产" msgstr "成功资产"
#: ops/templates/ops/command_execution_create.html:67 #: ops/templates/ops/command_execution_create.html:71
#: terminal/templates/terminal/session_detail.html:91 #: terminal/templates/terminal/session_detail.html:91
#: terminal/templates/terminal/session_detail.html:100 #: terminal/templates/terminal/session_detail.html:100
msgid "Go" msgid "Go"
msgstr "" msgstr ""
#: ops/templates/ops/command_execution_create.html:244 #: ops/templates/ops/command_execution_create.html:253
msgid "Pending" msgid "Pending"
msgstr "" msgstr ""
#: ops/templates/ops/command_execution_list.html:48 #: ops/templates/ops/command_execution_list.html:63
msgid "Finished" msgid "Finished"
msgstr "结束" msgstr "结束"
#: ops/templates/ops/command_execution_list.html:51
msgid "Date finished"
msgstr "结束日期"
#: ops/templates/ops/task_adhoc.html:19 ops/templates/ops/task_detail.html:20 #: ops/templates/ops/task_adhoc.html:19 ops/templates/ops/task_detail.html:20
#: ops/templates/ops/task_history.html:19 ops/views/adhoc.py:72 #: ops/templates/ops/task_history.html:19 ops/views/adhoc.py:75
msgid "Task detail" msgid "Task detail"
msgstr "任务详情" msgstr "任务详情"
#: ops/templates/ops/task_adhoc.html:22 ops/templates/ops/task_detail.html:23 #: ops/templates/ops/task_adhoc.html:22 ops/templates/ops/task_detail.html:23
#: ops/templates/ops/task_history.html:22 ops/views/adhoc.py:85 #: ops/templates/ops/task_history.html:22 ops/views/adhoc.py:88
msgid "Task versions" msgid "Task versions"
msgstr "任务各版本" msgstr "任务各版本"
...@@ -2521,9 +2530,9 @@ msgstr "任务开始: " ...@@ -2521,9 +2530,9 @@ msgstr "任务开始: "
msgid "Update task content: {}" msgid "Update task content: {}"
msgstr "更新任务内容: {}" msgstr "更新任务内容: {}"
#: ops/views/adhoc.py:49 ops/views/adhoc.py:71 ops/views/adhoc.py:84 #: ops/views/adhoc.py:49 ops/views/adhoc.py:74 ops/views/adhoc.py:87
#: ops/views/adhoc.py:97 ops/views/adhoc.py:110 ops/views/adhoc.py:123 #: ops/views/adhoc.py:100 ops/views/adhoc.py:113 ops/views/adhoc.py:126
#: ops/views/adhoc.py:136 ops/views/command.py:43 ops/views/command.py:67 #: ops/views/adhoc.py:139 ops/views/command.py:43 ops/views/command.py:67
msgid "Ops" msgid "Ops"
msgstr "作业中心" msgstr "作业中心"
...@@ -2531,11 +2540,12 @@ msgstr "作业中心" ...@@ -2531,11 +2540,12 @@ msgstr "作业中心"
msgid "Task list" msgid "Task list"
msgstr "任务列表" msgstr "任务列表"
#: ops/views/adhoc.py:98 #: ops/views/adhoc.py:101
msgid "Task run history" msgid "Task run history"
msgstr "执行历史" msgstr "执行历史"
#: ops/views/command.py:68 templates/_nav.html:78 templates/_nav_user.html:9 #: ops/views/command.py:68 templates/_nav.html:67 templates/_nav.html:79
#: templates/_nav_user.html:9
msgid "Command execution" msgid "Command execution"
msgstr "命令执行" msgstr "命令执行"
...@@ -2545,8 +2555,8 @@ msgstr "组织管理" ...@@ -2545,8 +2555,8 @@ msgstr "组织管理"
#: perms/forms.py:31 perms/models.py:30 perms/models.py:80 #: perms/forms.py:31 perms/models.py:30 perms/models.py:80
#: perms/templates/perms/asset_permission_list.html:55 #: perms/templates/perms/asset_permission_list.html:55
#: perms/templates/perms/asset_permission_list.html:145 templates/_nav.html:14 #: perms/templates/perms/asset_permission_list.html:111 templates/_nav.html:14
#: users/forms.py:280 users/models/group.py:26 users/models/user.py:59 #: users/forms.py:284 users/models/group.py:26 users/models/user.py:59
#: users/templates/users/_select_user_modal.html:16 #: users/templates/users/_select_user_modal.html:16
#: users/templates/users/user_detail.html:211 #: users/templates/users/user_detail.html:211
#: users/templates/users/user_list.html:26 #: users/templates/users/user_list.html:26
...@@ -2688,14 +2698,14 @@ msgstr "文档" ...@@ -2688,14 +2698,14 @@ msgstr "文档"
msgid "Commercial support" msgid "Commercial support"
msgstr "商业支持" msgstr "商业支持"
#: templates/_header_bar.html:89 templates/_nav_user.html:14 users/forms.py:147 #: templates/_header_bar.html:89 templates/_nav_user.html:14 users/forms.py:151
#: users/templates/users/_user.html:39 #: users/templates/users/_user.html:43
#: users/templates/users/first_login.html:39 #: users/templates/users/first_login.html:39
#: users/templates/users/user_password_update.html:40 #: users/templates/users/user_password_update.html:40
#: users/templates/users/user_profile.html:17 #: users/templates/users/user_profile.html:17
#: users/templates/users/user_profile_update.html:37 #: users/templates/users/user_profile_update.html:37
#: users/templates/users/user_profile_update.html:57 #: users/templates/users/user_profile_update.html:57
#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:366 #: users/templates/users/user_pubkey_update.html:37 users/views/user.py:368
msgid "Profile" msgid "Profile"
msgstr "个人信息" msgstr "个人信息"
...@@ -2783,8 +2793,8 @@ msgstr "" ...@@ -2783,8 +2793,8 @@ msgstr ""
#: templates/_nav.html:10 users/views/group.py:28 users/views/group.py:44 #: templates/_nav.html:10 users/views/group.py:28 users/views/group.py:44
#: users/views/group.py:60 users/views/group.py:76 users/views/group.py:92 #: users/views/group.py:60 users/views/group.py:76 users/views/group.py:92
#: users/views/login.py:346 users/views/user.py:68 users/views/user.py:83 #: users/views/login.py:346 users/views/user.py:68 users/views/user.py:83
#: users/views/user.py:111 users/views/user.py:192 users/views/user.py:353 #: users/views/user.py:113 users/views/user.py:194 users/views/user.py:355
#: users/views/user.py:403 users/views/user.py:437 #: users/views/user.py:405 users/views/user.py:439
msgid "Users" msgid "Users"
msgstr "用户管理" msgstr "用户管理"
...@@ -2831,15 +2841,15 @@ msgstr "终端管理" ...@@ -2831,15 +2841,15 @@ msgstr "终端管理"
msgid "Job Center" msgid "Job Center"
msgstr "作业中心" msgstr "作业中心"
#: templates/_nav.html:84 #: templates/_nav.html:85
msgid "XPack" msgid "XPack"
msgstr "" msgstr ""
#: templates/_nav.html:92 xpack/plugins/cloud/views.py:26 #: templates/_nav.html:93 xpack/plugins/cloud/views.py:26
msgid "Account list" msgid "Account list"
msgstr "账户列表" msgstr "账户列表"
#: templates/_nav.html:93 #: templates/_nav.html:94
msgid "Sync instance" msgid "Sync instance"
msgstr "同步实例" msgstr "同步实例"
...@@ -3309,11 +3319,11 @@ msgstr "MFA 验证码" ...@@ -3309,11 +3319,11 @@ msgstr "MFA 验证码"
msgid "Role" msgid "Role"
msgstr "角色" msgstr "角色"
#: users/forms.py:55 users/forms.py:226 #: users/forms.py:55 users/forms.py:230
msgid "ssh public key" msgid "ssh public key"
msgstr "ssh公钥" msgstr "ssh公钥"
#: users/forms.py:56 users/forms.py:227 #: users/forms.py:56 users/forms.py:231
msgid "ssh-rsa AAAA..." msgid "ssh-rsa AAAA..."
msgstr "" msgstr ""
...@@ -3325,15 +3335,15 @@ msgstr "复制用户公钥到这里" ...@@ -3325,15 +3335,15 @@ msgstr "复制用户公钥到这里"
msgid "Join user groups" msgid "Join user groups"
msgstr "添加到用户组" msgstr "添加到用户组"
#: users/forms.py:110 users/forms.py:241 #: users/forms.py:110 users/forms.py:245
msgid "Public key should not be the same as your old one." msgid "Public key should not be the same as your old one."
msgstr "不能和原来的密钥相同" msgstr "不能和原来的密钥相同"
#: users/forms.py:114 users/forms.py:245 users/serializers/v1.py:51 #: users/forms.py:114 users/forms.py:249 users/serializers/v1.py:38
msgid "Not a valid ssh public key" msgid "Not a valid ssh public key"
msgstr "ssh密钥不合法" msgstr "ssh密钥不合法"
#: users/forms.py:153 #: users/forms.py:157
msgid "" msgid ""
"Tip: when enabled, you will enter the MFA binding process the next time you " "Tip: when enabled, you will enter the MFA binding process the next time you "
"log in. you can also directly bind in \"personal information -> quick " "log in. you can also directly bind in \"personal information -> quick "
...@@ -3342,11 +3352,11 @@ msgstr "" ...@@ -3342,11 +3352,11 @@ msgstr ""
"提示:启用之后您将会在下次登录时进入MFA绑定流程;您也可以在(个人信息->快速修" "提示:启用之后您将会在下次登录时进入MFA绑定流程;您也可以在(个人信息->快速修"
"改->更改MFA设置)中直接绑定!" "改->更改MFA设置)中直接绑定!"
#: users/forms.py:163 #: users/forms.py:167
msgid "* Enable MFA authentication to make the account more secure." msgid "* Enable MFA authentication to make the account more secure."
msgstr "* 启用MFA认证,使账号更加安全." msgstr "* 启用MFA认证,使账号更加安全."
#: users/forms.py:173 #: users/forms.py:177
msgid "" msgid ""
"In order to protect you and your company, please keep your account, password " "In order to protect you and your company, please keep your account, password "
"and key sensitive information properly. (for example: setting complex " "and key sensitive information properly. (for example: setting complex "
...@@ -3355,41 +3365,41 @@ msgstr "" ...@@ -3355,41 +3365,41 @@ msgstr ""
"为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:" "为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:"
"设置复杂密码,启用MFA认证)" "设置复杂密码,启用MFA认证)"
#: users/forms.py:180 users/templates/users/first_login.html:48 #: users/forms.py:184 users/templates/users/first_login.html:48
#: users/templates/users/first_login.html:107 #: users/templates/users/first_login.html:107
#: users/templates/users/first_login.html:130 #: users/templates/users/first_login.html:130
msgid "Finish" msgid "Finish"
msgstr "完成" msgstr "完成"
#: users/forms.py:186 #: users/forms.py:190
msgid "Old password" msgid "Old password"
msgstr "原来密码" msgstr "原来密码"
#: users/forms.py:191 #: users/forms.py:195
msgid "New password" msgid "New password"
msgstr "新密码" msgstr "新密码"
#: users/forms.py:196 #: users/forms.py:200
msgid "Confirm password" msgid "Confirm password"
msgstr "确认密码" msgstr "确认密码"
#: users/forms.py:206 #: users/forms.py:210
msgid "Old password error" msgid "Old password error"
msgstr "原来密码错误" msgstr "原来密码错误"
#: users/forms.py:214 #: users/forms.py:218
msgid "Password does not match" msgid "Password does not match"
msgstr "密码不一致" msgstr "密码不一致"
#: users/forms.py:224 #: users/forms.py:228
msgid "Automatically configure and download the SSH key" msgid "Automatically configure and download the SSH key"
msgstr "自动配置并下载SSH密钥" msgstr "自动配置并下载SSH密钥"
#: users/forms.py:228 #: users/forms.py:232
msgid "Paste your id_rsa.pub here." msgid "Paste your id_rsa.pub here."
msgstr "复制你的公钥到这里" msgstr "复制你的公钥到这里"
#: users/forms.py:256 users/models/user.py:83 #: users/forms.py:260 users/models/user.py:83
#: users/templates/users/first_login.html:42 #: users/templates/users/first_login.html:42
#: users/templates/users/user_password_update.html:46 #: users/templates/users/user_password_update.html:46
#: users/templates/users/user_profile.html:68 #: users/templates/users/user_profile.html:68
...@@ -3398,7 +3408,7 @@ msgstr "复制你的公钥到这里" ...@@ -3398,7 +3408,7 @@ msgstr "复制你的公钥到这里"
msgid "Public key" msgid "Public key"
msgstr "ssh公钥" msgstr "ssh公钥"
#: users/forms.py:263 users/forms.py:268 users/forms.py:314 #: users/forms.py:267 users/forms.py:272 users/forms.py:318
#: xpack/plugins/orgs/forms.py:30 #: xpack/plugins/orgs/forms.py:30
msgid "Select users" msgid "Select users"
msgstr "选择用户" msgstr "选择用户"
...@@ -3456,7 +3466,7 @@ msgstr "Agent" ...@@ -3456,7 +3466,7 @@ msgstr "Agent"
msgid "Date login" msgid "Date login"
msgstr "登录日期" msgstr "登录日期"
#: users/models/user.py:32 users/models/user.py:418 #: users/models/user.py:32 users/models/user.py:428
msgid "Administrator" msgid "Administrator"
msgstr "管理员" msgstr "管理员"
...@@ -3502,7 +3512,7 @@ msgstr "用户来源" ...@@ -3502,7 +3512,7 @@ msgstr "用户来源"
msgid "Date password last updated" msgid "Date password last updated"
msgstr "最后更新密码日期" msgstr "最后更新密码日期"
#: users/models/user.py:421 #: users/models/user.py:431
msgid "Administrator is the super user of system" msgid "Administrator is the super user of system"
msgstr "Administrator是初始的超级管理员" msgstr "Administrator是初始的超级管理员"
...@@ -3790,7 +3800,7 @@ msgid "Reset link will be generated and sent to the user. " ...@@ -3790,7 +3800,7 @@ msgid "Reset link will be generated and sent to the user. "
msgstr "生成重置密码连接,通过邮件发送给用户" msgstr "生成重置密码连接,通过邮件发送给用户"
#: users/templates/users/user_detail.html:19 #: users/templates/users/user_detail.html:19
#: users/templates/users/user_granted_asset.html:18 users/views/user.py:193 #: users/templates/users/user_granted_asset.html:18 users/views/user.py:195
msgid "User detail" msgid "User detail"
msgstr "用户详情" msgstr "用户详情"
...@@ -3923,23 +3933,37 @@ msgstr "用户组删除" ...@@ -3923,23 +3933,37 @@ msgstr "用户组删除"
msgid "UserGroup Deleting failed." msgid "UserGroup Deleting failed."
msgstr "用户组删除失败" msgstr "用户组删除失败"
#: users/templates/users/user_list.html:200 #: users/templates/users/user_list.html:28 xpack/plugins/cloud/models.py:52
#: xpack/plugins/cloud/templates/cloud/account_detail.html:60
#: xpack/plugins/cloud/templates/cloud/account_list.html:14
msgid "Validity"
msgstr "账户状态"
#: users/templates/users/user_list.html:203
msgid "This will delete the selected users !!!" msgid "This will delete the selected users !!!"
msgstr "删除选中用户 !!!" msgstr "删除选中用户 !!!"
#: users/templates/users/user_list.html:209 #: users/templates/users/user_list.html:212
msgid "User Deleted." msgid "User Deleted."
msgstr "已被删除" msgstr "已被删除"
#: users/templates/users/user_list.html:210 #: users/templates/users/user_list.html:213
#: users/templates/users/user_list.html:215 #: users/templates/users/user_list.html:218
msgid "User Delete" msgid "User Delete"
msgstr "删除" msgstr "删除"
#: users/templates/users/user_list.html:214 #: users/templates/users/user_list.html:217
msgid "User Deleting failed." msgid "User Deleting failed."
msgstr "用户删除失败" msgstr "用户删除失败"
#: users/templates/users/user_list.html:253
msgid "User is expired"
msgstr "用户已失效"
#: users/templates/users/user_list.html:256
msgid "User is inactive"
msgstr "用户已禁用"
#: users/templates/users/user_otp_authentication.html:6 #: users/templates/users/user_otp_authentication.html:6
#: users/templates/users/user_password_authentication.html:6 #: users/templates/users/user_password_authentication.html:6
msgid "Authenticate" msgid "Authenticate"
...@@ -3985,8 +4009,8 @@ msgstr "安装完成后点击下一步进入绑定页面(如已安装,直接 ...@@ -3985,8 +4009,8 @@ msgstr "安装完成后点击下一步进入绑定页面(如已安装,直接
msgid "Administrator Settings force MFA login" msgid "Administrator Settings force MFA login"
msgstr "管理员设置强制使用MFA登录" msgstr "管理员设置强制使用MFA登录"
#: users/templates/users/user_profile.html:120 users/views/user.py:229 #: users/templates/users/user_profile.html:120 users/views/user.py:231
#: users/views/user.py:283 #: users/views/user.py:285
msgid "User groups" msgid "User groups"
msgstr "用户组" msgstr "用户组"
...@@ -4036,7 +4060,7 @@ msgid "" ...@@ -4036,7 +4060,7 @@ msgid ""
"corresponding private key." "corresponding private key."
msgstr "新的公钥已设置成功,请下载对应的私钥" msgstr "新的公钥已设置成功,请下载对应的私钥"
#: users/templates/users/user_update.html:4 users/views/user.py:112 #: users/templates/users/user_update.html:4 users/views/user.py:114
msgid "Update user" msgid "Update user"
msgstr "更新用户" msgstr "更新用户"
...@@ -4243,7 +4267,7 @@ msgstr "用户组授权资产" ...@@ -4243,7 +4267,7 @@ msgstr "用户组授权资产"
msgid "Please enable cookies and try again." msgid "Please enable cookies and try again."
msgstr "设置你的浏览器支持cookie" msgstr "设置你的浏览器支持cookie"
#: users/views/login.py:191 users/views/user.py:524 users/views/user.py:549 #: users/views/login.py:191 users/views/user.py:526 users/views/user.py:551
msgid "MFA code invalid, or ntp sync server time" msgid "MFA code invalid, or ntp sync server time"
msgstr "MFA验证码不正确,或者服务器端时间不对" msgstr "MFA验证码不正确,或者服务器端时间不对"
...@@ -4288,7 +4312,7 @@ msgstr "Token错误或失效" ...@@ -4288,7 +4312,7 @@ msgstr "Token错误或失效"
msgid "Password not same" msgid "Password not same"
msgstr "密码不一致" msgstr "密码不一致"
#: users/views/login.py:308 users/views/user.py:126 users/views/user.py:420 #: users/views/login.py:308 users/views/user.py:128 users/views/user.py:422
msgid "* Your password does not meet the requirements" msgid "* Your password does not meet the requirements"
msgstr "* 您的密码不符合要求" msgstr "* 您的密码不符合要求"
...@@ -4296,51 +4320,51 @@ msgstr "* 您的密码不符合要求" ...@@ -4296,51 +4320,51 @@ msgstr "* 您的密码不符合要求"
msgid "First login" msgid "First login"
msgstr "首次登陆" msgstr "首次登陆"
#: users/views/user.py:143 #: users/views/user.py:145
msgid "Bulk update user success" msgid "Bulk update user success"
msgstr "批量更新用户成功" msgstr "批量更新用户成功"
#: users/views/user.py:173 #: users/views/user.py:175
msgid "Bulk update user" msgid "Bulk update user"
msgstr "批量更新用户" msgstr "批量更新用户"
#: users/views/user.py:258 #: users/views/user.py:260
msgid "Invalid file." msgid "Invalid file."
msgstr "文件不合法" msgstr "文件不合法"
#: users/views/user.py:354 #: users/views/user.py:356
msgid "User granted assets" msgid "User granted assets"
msgstr "用户授权资产" msgstr "用户授权资产"
#: users/views/user.py:385 #: users/views/user.py:387
msgid "Profile setting" msgid "Profile setting"
msgstr "个人信息设置" msgstr "个人信息设置"
#: users/views/user.py:404 #: users/views/user.py:406
msgid "Password update" msgid "Password update"
msgstr "密码更新" msgstr "密码更新"
#: users/views/user.py:438 #: users/views/user.py:440
msgid "Public key update" msgid "Public key update"
msgstr "密钥更新" msgstr "密钥更新"
#: users/views/user.py:479 #: users/views/user.py:481
msgid "Password invalid" msgid "Password invalid"
msgstr "用户名或密码无效" msgstr "用户名或密码无效"
#: users/views/user.py:579 #: users/views/user.py:581
msgid "MFA enable success" msgid "MFA enable success"
msgstr "MFA 绑定成功" msgstr "MFA 绑定成功"
#: users/views/user.py:580 #: users/views/user.py:582
msgid "MFA enable success, return login page" msgid "MFA enable success, return login page"
msgstr "MFA 绑定成功,返回到登录页面" msgstr "MFA 绑定成功,返回到登录页面"
#: users/views/user.py:582 #: users/views/user.py:584
msgid "MFA disable success" msgid "MFA disable success"
msgstr "MFA 解绑成功" msgstr "MFA 解绑成功"
#: users/views/user.py:583 #: users/views/user.py:585
msgid "MFA disable success, return login page" msgid "MFA disable success, return login page"
msgstr "MFA 解绑成功,返回登录页面" msgstr "MFA 解绑成功,返回登录页面"
...@@ -4406,12 +4430,6 @@ msgstr "" ...@@ -4406,12 +4430,6 @@ msgstr ""
msgid "Access key secret" msgid "Access key secret"
msgstr "" msgstr ""
#: xpack/plugins/cloud/models.py:52
#: xpack/plugins/cloud/templates/cloud/account_detail.html:60
#: xpack/plugins/cloud/templates/cloud/account_list.html:14
msgid "Validity"
msgstr "账户状态"
#: xpack/plugins/cloud/models.py:120 #: xpack/plugins/cloud/models.py:120
msgid "Regions" msgid "Regions"
msgstr "地域" msgstr "地域"
...@@ -4490,7 +4508,7 @@ msgstr "创建账户" ...@@ -4490,7 +4508,7 @@ msgstr "创建账户"
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:91 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:91
msgid "Loading..." msgid "Loading..."
msgstr "" msgstr "加载中..."
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:106 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:106
msgid "Load failed" msgid "Load failed"
...@@ -4605,6 +4623,23 @@ msgstr "创建组织" ...@@ -4605,6 +4623,23 @@ msgstr "创建组织"
msgid "Update org" msgid "Update org"
msgstr "更新组织" msgstr "更新组织"
#, fuzzy
#~| msgid "Validity"
#~ msgid "Valid"
#~ msgstr "账户状态"
#~ msgid "You can't update the root node name"
#~ msgstr "不能修改根节点名称"
#~ msgid "Update assets hardware info period"
#~ msgstr "定期更新资产硬件信息"
#~ msgid "Test system user connectivity period: {}"
#~ msgstr "定期测试系统用户可连接性: {}"
#~ msgid "Date finished"
#~ msgstr "结束日期"
#, fuzzy #, fuzzy
#~| msgid "Audits" #~| msgid "Audits"
#~ msgid "Audit" #~ msgid "Audit"
......
...@@ -24,8 +24,10 @@ class TaskViewSet(viewsets.ModelViewSet): ...@@ -24,8 +24,10 @@ class TaskViewSet(viewsets.ModelViewSet):
def get_queryset(self): def get_queryset(self):
queryset = super().get_queryset() queryset = super().get_queryset()
if current_org: if current_org.is_real():
queryset = queryset.filter(created_by=current_org.id) queryset = queryset.filter(created_by=current_org.id)
else:
queryset = queryset.filter(created_by='')
return queryset return queryset
......
...@@ -53,7 +53,7 @@ class AdHocRunHistorySerializer(serializers.ModelSerializer): ...@@ -53,7 +53,7 @@ class AdHocRunHistorySerializer(serializers.ModelSerializer):
@staticmethod @staticmethod
def get_stat(obj): def get_stat(obj):
return { return {
"total": len(obj.adhoc.hosts), "total": obj.adhoc.hosts.count(),
"success": len(obj.summary.get("contacted", [])), "success": len(obj.summary.get("contacted", [])),
"failed": len(obj.summary.get("dark", [])), "failed": len(obj.summary.get("dark", [])),
} }
......
...@@ -11,6 +11,7 @@ app_name = "ops" ...@@ -11,6 +11,7 @@ app_name = "ops"
router = DefaultRouter() router = DefaultRouter()
router.register(r'tasks', api.TaskViewSet, 'task') router.register(r'tasks', api.TaskViewSet, 'task')
router.register(r'adhoc', api.AdHocViewSet, 'adhoc') router.register(r'adhoc', api.AdHocViewSet, 'adhoc')
router.register(r'history', api.AdHocRunHistoryViewSet, 'history')
router.register(r'command-executions', api.CommandExecutionViewSet, 'command-execution') router.register(r'command-executions', api.CommandExecutionViewSet, 'command-execution')
urlpatterns = [ urlpatterns = [
......
...@@ -62,8 +62,11 @@ class TaskDetailView(AdminUserRequiredMixin, DetailView): ...@@ -62,8 +62,11 @@ class TaskDetailView(AdminUserRequiredMixin, DetailView):
def get_queryset(self): def get_queryset(self):
queryset = super().get_queryset() queryset = super().get_queryset()
if current_org: # Todo: 需要整理默认组织等东西
if current_org.is_real():
queryset = queryset.filter(created_by=current_org.id) queryset = queryset.filter(created_by=current_org.id)
else:
queryset = queryset.filter(created_by='')
return queryset return queryset
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
......
...@@ -74,7 +74,7 @@ class OrgManager(models.Manager): ...@@ -74,7 +74,7 @@ class OrgManager(models.Manager):
class OrgModelMixin(models.Model): class OrgModelMixin(models.Model):
org_id = models.CharField(max_length=36, blank=True, default='', verbose_name=_("Organization")) org_id = models.CharField(max_length=36, blank=True, default='', verbose_name=_("Organization"), db_index=True)
objects = OrgManager() objects = OrgManager()
sep = '@' sep = '@'
......
...@@ -16,7 +16,7 @@ from orgs.utils import set_to_root_org ...@@ -16,7 +16,7 @@ from orgs.utils import set_to_root_org
from .utils import AssetPermissionUtil from .utils import AssetPermissionUtil
from .models import AssetPermission from .models import AssetPermission
from .hands import AssetGrantedSerializer, User, UserGroup, Asset, Node, \ from .hands import AssetGrantedSerializer, User, UserGroup, Asset, Node, \
NodeGrantedSerializer, SystemUser, NodeSerializer SystemUser, NodeSerializer
from . import serializers from . import serializers
from .mixins import AssetsFilterMixin from .mixins import AssetsFilterMixin
...@@ -150,7 +150,7 @@ class UserGrantedNodesWithAssetsApi(AssetsFilterMixin, ListAPIView): ...@@ -150,7 +150,7 @@ class UserGrantedNodesWithAssetsApi(AssetsFilterMixin, ListAPIView):
用户授权的节点并带着节点下资产的api 用户授权的节点并带着节点下资产的api
""" """
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = NodeGrantedSerializer serializer_class = serializers.NodeGrantedSerializer
def change_org_if_need(self): def change_org_if_need(self):
if self.request.user.is_superuser or \ if self.request.user.is_superuser or \
...@@ -228,15 +228,22 @@ class UserGrantedNodesWithAssetsAsTreeApi(ListAPIView): ...@@ -228,15 +228,22 @@ class UserGrantedNodesWithAssetsAsTreeApi(ListAPIView):
@staticmethod @staticmethod
def parse_asset_to_tree_node(node, asset, system_users): def parse_asset_to_tree_node(node, asset, system_users):
system_users_protocol_matched = [s for s in system_users if s.protocol == asset.protocol] system_users_protocol_matched = [s for s in system_users if s.protocol == asset.protocol]
system_user_serializer = serializers.GrantedSystemUserSerializer(
system_users_protocol_matched, many=True
)
asset_serializer = serializers.GrantedAssetSerializer(asset)
icon_skin = 'file' icon_skin = 'file'
if asset.platform.lower() == 'windows': if asset.platform.lower() == 'windows':
icon_skin = 'windows' icon_skin = 'windows'
elif asset.platform.lower() == 'linux': elif asset.platform.lower() == 'linux':
icon_skin = 'linux' icon_skin = 'linux'
system_users = []
for system_user in system_users_protocol_matched:
system_users.append({
'id': system_user.id,
'name': system_user.name,
'username': system_user.username,
'protocol': system_user.protocol,
'priority': system_user.priority,
'login_mode': system_user.login_mode,
'comment': system_user.comment,
})
data = { data = {
'id': str(asset.id), 'id': str(asset.id),
'name': asset.hostname, 'name': asset.hostname,
...@@ -246,9 +253,19 @@ class UserGrantedNodesWithAssetsAsTreeApi(ListAPIView): ...@@ -246,9 +253,19 @@ class UserGrantedNodesWithAssetsAsTreeApi(ListAPIView):
'open': False, 'open': False,
'iconSkin': icon_skin, 'iconSkin': icon_skin,
'meta': { 'meta': {
'system_users': system_user_serializer.data, 'system_users': system_users,
'type': 'asset', 'type': 'asset',
'asset': asset_serializer.data 'asset': {
'id': asset.id,
'hostname': asset.hostname,
'ip': asset.ip,
'port': asset.port,
'protocol': asset.protocol,
'platform': asset.platform,
'domain': None if not asset.domain else asset.domain.id,
'is_active': asset.is_active,
'comment': asset.comment
},
} }
} }
tree_node = TreeNode(**data) tree_node = TreeNode(**data)
...@@ -360,7 +377,7 @@ class UserGroupGrantedNodesApi(ListAPIView): ...@@ -360,7 +377,7 @@ class UserGroupGrantedNodesApi(ListAPIView):
class UserGroupGrantedNodesWithAssetsApi(ListAPIView): class UserGroupGrantedNodesWithAssetsApi(ListAPIView):
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = NodeGrantedSerializer serializer_class = serializers.NodeGrantedSerializer
def get_queryset(self): def get_queryset(self):
user_group_id = self.kwargs.get('pk', '') user_group_id = self.kwargs.get('pk', '')
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
from common.permissions import AdminUserRequiredMixin from common.permissions import AdminUserRequiredMixin
from users.models import User, UserGroup from users.models import User, UserGroup
from assets.models import Asset, SystemUser, Node from assets.models import Asset, SystemUser, Node
from assets.serializers import AssetGrantedSerializer, NodeGrantedSerializer, NodeSerializer from assets.serializers import AssetGrantedSerializer, NodeSerializer
...@@ -13,11 +13,11 @@ class Migration(migrations.Migration): ...@@ -13,11 +13,11 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='assetpermission', model_name='assetpermission',
name='org_id', name='org_id',
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'), field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='nodepermission', model_name='nodepermission',
name='org_id', name='org_id',
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'), field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
), ),
] ]
...@@ -83,6 +83,35 @@ class AssetPermissionNodeSerializer(serializers.ModelSerializer): ...@@ -83,6 +83,35 @@ class AssetPermissionNodeSerializer(serializers.ModelSerializer):
return obj.parent_key return obj.parent_key
class NodeGrantedSerializer(serializers.ModelSerializer):
"""
授权资产组
"""
assets_granted = AssetGrantedSerializer(many=True, read_only=True)
assets_amount = serializers.SerializerMethodField()
parent = serializers.SerializerMethodField()
name = serializers.SerializerMethodField()
class Meta:
model = Node
fields = [
'id', 'key', 'name', 'value', 'parent',
'assets_granted', 'assets_amount', 'org_id',
]
@staticmethod
def get_assets_amount(obj):
return len(obj.assets_granted)
@staticmethod
def get_name(obj):
return obj.name
@staticmethod
def get_parent(obj):
return obj.parent.id
class GrantedNodeSerializer(serializers.ModelSerializer): class GrantedNodeSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Node model = Node
......
...@@ -113,6 +113,7 @@ $(document).ready(function () { ...@@ -113,6 +113,7 @@ $(document).ready(function () {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
$("#asset_list_modal").modal(); $("#asset_list_modal").modal();
initSelectedAssets2Table();
} }
}) })
}) })
......
...@@ -78,58 +78,24 @@ var zTree, table, show = 0; ...@@ -78,58 +78,24 @@ var zTree, table, show = 0;
function onSelected(event, treeNode) { function onSelected(event, treeNode) {
setCookie('node_selected', treeNode.id); setCookie('node_selected', treeNode.id);
var url = table.ajax.url(); var url = table.ajax.url();
if (treeNode.is_node) { if (treeNode.meta.type === 'node') {
url = setUrlParam(url, 'asset', ""); url = setUrlParam(url, 'asset', "");
url = setUrlParam(url, 'node', treeNode.node_id) url = setUrlParam(url, 'node', treeNode.meta.node.id)
} else { } else {
url = setUrlParam(url, 'node', ""); url = setUrlParam(url, 'node', "");
url = setUrlParam(url, 'asset', treeNode.node_id) url = setUrlParam(url, 'asset', treeNode.meta.asset.id)
} }
setCookie('node_selected', treeNode.node_id); setCookie('node_selected', treeNode.node_id);
table.ajax.url(url); table.ajax.url(url);
table.ajax.reload(); table.ajax.reload();
} }
function selectQueryNode() {
var query_node_id = $.getUrlParam("node");
var cookie_node_id = getCookie('node_selected');
var node;
var node_id;
if (query_node_id !== null) {
node_id = query_node_id
} else if (cookie_node_id !== null) {
node_id = cookie_node_id;
}
node = zTree.getNodesByParam("id", node_id, null);
if (node){
zTree.selectNode(node[0]);
node.open = true;
}
}
function filter(treeId, parentNode, childNodes) {
$.each(childNodes, function (index, value) {
value["node_id"] = value["id"];
value["id"] = value["tree_id"];
if (value["tree_id"] !== value["tree_parent"]) {
value["pId"] = value["tree_parent"];
} else {
value["isParent"] = true;
}
value['name'] = value['value'];
value["iconSkin"] = value["is_node"] ? null : 'file';
{#value["pId"] = value["parent"];#}
{#value["name"] = value["value"];#}
value["isParent"] = value["is_node"];
});
return childNodes;
}
function beforeAsync(treeId, treeNode) { function beforeAsync(treeId, treeNode) {
return treeNode.is_node if (treeNode) {
return treeNode.meta.type === 'node'
}
return true
} }
function makeLabel(data) { function makeLabel(data) {
...@@ -235,9 +201,8 @@ function initTree() { ...@@ -235,9 +201,8 @@ function initTree() {
}, },
async: { async: {
enable: true, enable: true,
url: "{% url 'api-assets:node-children-2' %}?assets=1&all=", url: "{% url 'api-assets:node-children-tree' %}?assets=1",
autoParam:["node_id=id", "name=n", "level=lv"], autoParam:["id=key", "name=n", "level=lv"],
dataFilter: filter,
type: 'get' type: 'get'
}, },
callback: { callback: {
...@@ -245,25 +210,7 @@ function initTree() { ...@@ -245,25 +210,7 @@ function initTree() {
beforeAsync: beforeAsync beforeAsync: beforeAsync
} }
}; };
zTree = $.fn.zTree.init($("#assetTree"), setting);
var zNodes = [];
$.get("{% url 'api-assets:node-children-2' %}?assets=1", function(data, status){
$.each(data, function (index, value) {
value["node_id"] = value["id"];
value["id"] = value["tree_id"];
if (value["tree_id"] !== value["tree_parent"]) {
value["pId"] = value["tree_parent"];
}
value["isParent"] = value["is_node"];
value['name'] = value['value'];
value["iconSkin"] = value["is_node"] ? null : 'file';
});
zNodes = data;
$.fn.zTree.init($("#assetTree"), setting, zNodes);
zTree = $.fn.zTree.getZTreeObj("assetTree");
var root = zTree.getNodes()[0];
zTree.expandNode(root);
});
} }
function toggle() { function toggle() {
...@@ -299,10 +246,10 @@ $(document).ready(function(){ ...@@ -299,10 +246,10 @@ $(document).ready(function(){
var _nodes = []; var _nodes = [];
var _assets = []; var _assets = [];
$.each(nodes, function (id, node) { $.each(nodes, function (id, node) {
if (node.is_node) { if (node.meta.type === 'node') {
_nodes.push(node.node_id) _nodes.push(node.meta.node.id)
} else { } else {
_assets.push(node.node_id) _assets.push(node.meta.asset.id)
} }
}); });
url += "?assets=" + _assets.join(",") + "&nodes=" + _nodes.join(","); url += "?assets=" + _assets.join(",") + "&nodes=" + _nodes.join(",");
......
...@@ -812,3 +812,32 @@ function initPopover($container, $progress, $idPassword, $el, password_check_rul ...@@ -812,3 +812,32 @@ function initPopover($container, $progress, $idPassword, $el, password_check_rul
$idPassword.pwstrength(options); $idPassword.pwstrength(options);
popoverPasswordRules(password_check_rules, $el); popoverPasswordRules(password_check_rules, $el);
} }
// 解决input框中的资产和弹出表格中资产的显示不一致
function initSelectedAssets2Table(){
var inputAssets = $('#id_assets').val();
var selectedAssets = asset_table2.selected.concat();
// input assets无,table assets选中,则取消勾选(再次click)
if (selectedAssets.length !== 0){
$.each(selectedAssets, function (index, assetId){
if ($.inArray(assetId, inputAssets) === -1){
$('#'+assetId).trigger('click'); // 取消勾选
}
});
}
// input assets有,table assets没选,则选中(click)
if (inputAssets !== null){
asset_table2.selected = inputAssets;
$.each(inputAssets, function(index, assetId){
var dom = document.getElementById(assetId);
if (dom !== null){
var selected = dom.parentElement.parentElement.className.indexOf('selected')
}
if (selected === -1){
$('#'+assetId).trigger('click');
}
});
}
}
...@@ -94,44 +94,15 @@ class SessionReplayViewSet(viewsets.ViewSet): ...@@ -94,44 +94,15 @@ class SessionReplayViewSet(viewsets.ViewSet):
serializer_class = serializers.ReplaySerializer serializer_class = serializers.ReplaySerializer
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
session = None session = None
upload_to = 'replay' # 仅添加到本地存储中
def get_session_path(self, version=2):
"""
获取session日志的文件路径
:param version: 原来后缀是 .gz,为了统一新版本改为 .replay.gz
:return:
"""
suffix = '.replay.gz'
if version == 1:
suffix = '.gz'
date = self.session.date_start.strftime('%Y-%m-%d')
return os.path.join(date, str(self.session.id) + suffix)
def get_local_path(self, version=2):
session_path = self.get_session_path(version=version)
if version == 2:
local_path = os.path.join(self.upload_to, session_path)
else:
local_path = session_path
return local_path
def save_to_storage(self, f):
local_path = self.get_local_path()
try:
name = default_storage.save(local_path, f)
return name, None
except OSError as e:
return None, e
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
session_id = kwargs.get('pk') session_id = kwargs.get('pk')
self.session = get_object_or_404(Session, id=session_id) session = get_object_or_404(Session, id=session_id)
serializer = self.serializer_class(data=request.data) serializer = self.serializer_class(data=request.data)
if serializer.is_valid(): if serializer.is_valid():
file = serializer.validated_data['file'] file = serializer.validated_data['file']
name, err = self.save_to_storage(file) name, err = session.save_to_storage(file)
if not name: if not name:
msg = "Failed save replay `{}`: {}".format(session_id, err) msg = "Failed save replay `{}`: {}".format(session_id, err)
logger.error(msg) logger.error(msg)
...@@ -145,7 +116,7 @@ class SessionReplayViewSet(viewsets.ViewSet): ...@@ -145,7 +116,7 @@ class SessionReplayViewSet(viewsets.ViewSet):
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
session_id = kwargs.get('pk') session_id = kwargs.get('pk')
self.session = get_object_or_404(Session, id=session_id) session = get_object_or_404(Session, id=session_id)
data = { data = {
'type': 'guacamole' if self.session.protocol == 'rdp' else 'json', 'type': 'guacamole' if self.session.protocol == 'rdp' else 'json',
...@@ -153,9 +124,9 @@ class SessionReplayViewSet(viewsets.ViewSet): ...@@ -153,9 +124,9 @@ class SessionReplayViewSet(viewsets.ViewSet):
} }
# 新版本和老版本的文件后缀不同 # 新版本和老版本的文件后缀不同
session_path = self.get_session_path() # 存在外部存储上的路径 session_path = session.get_rel_replay_path() # 存在外部存储上的路径
local_path = self.get_local_path() local_path = session.get_local_path()
local_path_v1 = self.get_local_path(version=1) local_path_v1 = session.get_local_path(version=1)
# 去default storage中查找 # 去default storage中查找
for _local_path in (local_path, local_path_v1, session_path): for _local_path in (local_path, local_path_v1, session_path):
......
...@@ -13,11 +13,11 @@ class Migration(migrations.Migration): ...@@ -13,11 +13,11 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='command', model_name='command',
name='org_id', name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True), field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
), ),
migrations.AddField( migrations.AddField(
model_name='session', model_name='session',
name='org_id', name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True), field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
), ),
] ]
...@@ -10,14 +10,4 @@ class Migration(migrations.Migration): ...@@ -10,14 +10,4 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
migrations.AlterField(
model_name='command',
name='org_id',
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'),
),
migrations.AlterField(
model_name='session',
name='org_id',
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'),
),
] ]
from __future__ import unicode_literals from __future__ import unicode_literals
import os
import uuid import uuid
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils import timezone from django.utils import timezone
from django.conf import settings from django.conf import settings
from django.core.files.storage import default_storage
from users.models import User from users.models import User
from orgs.mixins import OrgModelMixin from orgs.mixins import OrgModelMixin
...@@ -148,6 +150,36 @@ class Session(OrgModelMixin): ...@@ -148,6 +150,36 @@ class Session(OrgModelMixin):
date_start = models.DateTimeField(verbose_name=_("Date start"), db_index=True, default=timezone.now) date_start = models.DateTimeField(verbose_name=_("Date start"), db_index=True, default=timezone.now)
date_end = models.DateTimeField(verbose_name=_("Date end"), null=True) date_end = models.DateTimeField(verbose_name=_("Date end"), null=True)
upload_to = 'replay'
def get_rel_replay_path(self, version=2):
"""
获取session日志的文件路径
:param version: 原来后缀是 .gz,为了统一新版本改为 .replay.gz
:return:
"""
suffix = '.replay.gz'
if version == 1:
suffix = '.gz'
date = self.date_start.strftime('%Y-%m-%d')
return os.path.join(date, str(self.id) + suffix)
def get_local_path(self, version=2):
rel_path = self.get_rel_replay_path(version=version)
if version == 2:
local_path = os.path.join(self.upload_to, rel_path)
else:
local_path = rel_path
return local_path
def save_to_storage(self, f):
local_path = self.get_local_path()
try:
name = default_storage.save(local_path, f)
return name, None
except OSError as e:
return None, e
class Meta: class Meta:
db_table = "terminal_session" db_table = "terminal_session"
ordering = ["-date_start"] ordering = ["-date_start"]
......
...@@ -4,15 +4,20 @@ ...@@ -4,15 +4,20 @@
import datetime import datetime
from celery import shared_task from celery import shared_task
from celery.utils.log import get_task_logger
from django.utils import timezone from django.utils import timezone
from django.conf import settings
from django.core.files.storage import default_storage
from ops.celery.utils import register_as_period_task, after_app_ready_start, \ from ops.celery.utils import register_as_period_task, after_app_ready_start, \
after_app_shutdown_clean after_app_shutdown_clean
from .models import Status, Session from .models import Status, Session, Command
CACHE_REFRESH_INTERVAL = 10 CACHE_REFRESH_INTERVAL = 10
RUNNING = False RUNNING = False
logger = get_task_logger(__name__)
@shared_task @shared_task
...@@ -34,3 +39,28 @@ def clean_orphan_session(): ...@@ -34,3 +39,28 @@ def clean_orphan_session():
if not session.terminal or not session.terminal.is_active: if not session.terminal or not session.terminal.is_active:
session.is_finished = True session.is_finished = True
session.save() session.save()
@shared_task
@register_as_period_task(interval=3600*24)
@after_app_ready_start
@after_app_shutdown_clean
def clean_expired_session_period():
logger.info("Start clean expired session record, commands and replay")
days = settings.TERMINAL_SESSION_KEEP_DURATION
dt = timezone.now() - timezone.timedelta(days=days)
expired_sessions = Session.objects.filter(date_start__lt=dt)
for session in expired_sessions:
logger.info("Clean session: {}".format(session.id))
Command.objects.filter(session=str(session.id)).delete()
# 删除录像文件
session_path = session.get_rel_replay_path()
local_path = session.get_local_path()
local_path_v1 = session.get_local_path(version=1)
# 去default storage中查找
for _local_path in (local_path, local_path_v1, session_path):
if default_storage.exists(_local_path):
default_storage.delete(_local_path)
# 删除session记录
session.delete()
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django.core.cache import cache from django.core.cache import cache
from .models import Session
from assets.models import Asset, SystemUser
from users.models import User
from .const import USERS_CACHE_KEY, ASSETS_CACHE_KEY, SYSTEM_USER_CACHE_KEY from .const import USERS_CACHE_KEY, ASSETS_CACHE_KEY, SYSTEM_USER_CACHE_KEY
def get_session_asset_list(): def get_session_asset_list():
return set(list(Session.objects.values_list('asset', flat=True))) return Asset.objects.values_list('hostname', flat=True)
def get_session_user_list(): def get_session_user_list():
return set(list(Session.objects.values_list('user', flat=True))) return User.objects.values_list('username', flat=True)
def get_session_system_user_list(): def get_session_system_user_list():
return set(list(Session.objects.values_list('system_user', flat=True))) return SystemUser.objects.values_list('username', flat=True)
def get_user_list_from_cache(): def get_user_list_from_cache():
......
...@@ -6,7 +6,7 @@ from rest_framework_bulk import BulkModelViewSet ...@@ -6,7 +6,7 @@ from rest_framework_bulk import BulkModelViewSet
from rest_framework.pagination import LimitOffsetPagination from rest_framework.pagination import LimitOffsetPagination
from ..serializers import UserGroupSerializer, \ from ..serializers import UserGroupSerializer, \
UserGroupUpdateMemeberSerializer UserGroupUpdateMemberSerializer
from ..models import UserGroup from ..models import UserGroup
from common.permissions import IsOrgAdmin from common.permissions import IsOrgAdmin
from common.mixins import IDInFilterMixin from common.mixins import IDInFilterMixin
...@@ -26,5 +26,5 @@ class UserGroupViewSet(IDInFilterMixin, BulkModelViewSet): ...@@ -26,5 +26,5 @@ class UserGroupViewSet(IDInFilterMixin, BulkModelViewSet):
class UserGroupUpdateUserApi(generics.RetrieveUpdateAPIView): class UserGroupUpdateUserApi(generics.RetrieveUpdateAPIView):
queryset = UserGroup.objects.all() queryset = UserGroup.objects.all()
serializer_class = UserGroupUpdateMemeberSerializer serializer_class = UserGroupUpdateMemberSerializer
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
...@@ -46,6 +46,9 @@ class UserViewSet(IDInFilterMixin, BulkModelViewSet): ...@@ -46,6 +46,9 @@ class UserViewSet(IDInFilterMixin, BulkModelViewSet):
self.permission_classes = (IsOrgAdminOrAppUser,) self.permission_classes = (IsOrgAdminOrAppUser,)
return super().get_permissions() return super().get_permissions()
def allow_bulk_destroy(self, qs, filtered):
return qs.count() == filtered.count()
class UserChangePasswordApi(generics.RetrieveUpdateAPIView): class UserChangePasswordApi(generics.RetrieveUpdateAPIView):
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
......
...@@ -13,6 +13,6 @@ class Migration(migrations.Migration): ...@@ -13,6 +13,6 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='usergroup', model_name='usergroup',
name='org_id', name='org_id',
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'), field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
), ),
] ]
...@@ -142,6 +142,18 @@ class User(AbstractUser): ...@@ -142,6 +142,18 @@ class User(AbstractUser):
return True return True
return False return False
@property
def groups_display(self):
return ' '.join(self.groups.all().values_list('name', flat=True))
@property
def role_display(self):
return self.get_role_display()
@property
def source_display(self):
return self.get_source_display()
@property @property
def is_expired(self): def is_expired(self):
if self.date_expired and self.date_expired < timezone.now(): if self.date_expired and self.date_expired < timezone.now():
......
...@@ -13,31 +13,18 @@ signer = get_signer() ...@@ -13,31 +13,18 @@ signer = get_signer()
class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
groups_display = serializers.SerializerMethodField()
groups = serializers.PrimaryKeyRelatedField(
many=True, queryset=UserGroup.objects.all(), required=False
)
class Meta: class Meta:
model = User model = User
list_serializer_class = BulkListSerializer list_serializer_class = BulkListSerializer
exclude = [ fields = [
'first_name', 'last_name', 'password', '_private_key', 'id', 'name', 'username', 'email', 'groups', 'groups_display',
'_public_key', '_otp_secret_key', 'user_permissions' 'role', 'role_display', 'avatar_url', 'wechat', 'phone',
'otp_level', 'comment', 'source', 'source_display',
'is_valid', 'is_expired', 'is_active',
'created_by', 'is_first_login',
'date_password_last_updated', 'date_expired',
] ]
# validators = []
def get_field_names(self, declared_fields, info):
fields = super(UserSerializer, self).get_field_names(declared_fields, info)
fields.extend([
'groups_display', 'get_role_display',
'get_source_display', 'is_valid'
])
return fields
@staticmethod
def get_groups_display(obj):
return " ".join([group.name for group in obj.groups.all()])
class UserPKUpdateSerializer(serializers.ModelSerializer): class UserPKUpdateSerializer(serializers.ModelSerializer):
...@@ -74,7 +61,7 @@ class UserGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer): ...@@ -74,7 +61,7 @@ class UserGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer):
return [user.name for user in obj.users.all()] return [user.name for user in obj.users.all()]
class UserGroupUpdateMemeberSerializer(serializers.ModelSerializer): class UserGroupUpdateMemberSerializer(serializers.ModelSerializer):
users = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects.all()) users = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects.all())
class Meta: class Meta:
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
<th class="text-center">{% trans 'Role' %}</th> <th class="text-center">{% trans 'Role' %}</th>
<th class="text-center">{% trans 'User group' %}</th> <th class="text-center">{% trans 'User group' %}</th>
<th class="text-center">{% trans 'Source' %}</th> <th class="text-center">{% trans 'Source' %}</th>
<th class="text-center">{% trans 'Active' %}</th> <th class="text-center">{% trans 'Validity' %}</th>
<th class="text-center">{% trans 'Action' %}</th> <th class="text-center">{% trans 'Action' %}</th>
</tr> </tr>
</thead> </thead>
...@@ -66,11 +66,14 @@ function initTable() { ...@@ -66,11 +66,14 @@ function initTable() {
var innerHtml = cellData.length > 20 ? cellData.substring(0, 20) + '...': cellData; var innerHtml = cellData.length > 20 ? cellData.substring(0, 20) + '...': cellData;
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>'); $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
}}, }},
{targets: 6, createdCell: function (td, cellData) { {targets: 6, createdCell: function (td, cellData, rowData) {
if (!cellData) { if (cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else {
$(td).html('<i class="fa fa-check text-navy"></i>') $(td).html('<i class="fa fa-check text-navy"></i>')
} else if (!rowData.is_active) {
$(td).html('<i class="fa fa-times text-danger inactive"></i>')
} else if (rowData.is_expired) {
$(td).html('<i class="fa fa-times text-danger expired"></i>')
} }
}}, }},
{targets: 7, createdCell: function (td, cellData, rowData) { {targets: 7, createdCell: function (td, cellData, rowData) {
...@@ -91,9 +94,9 @@ function initTable() { ...@@ -91,9 +94,9 @@ function initTable() {
ajax_url: '{% url "api-users:user-list" %}', ajax_url: '{% url "api-users:user-list" %}',
columns: [ columns: [
{data: "id"}, {data: "name" }, {data: "username" }, {data: "id"}, {data: "name" }, {data: "username" },
{data: "get_role_display", orderable: false}, {data: "role_display", orderable: false},
{data: "groups_display", orderable: false}, {data: "groups_display", orderable: false},
{data: "get_source_display", orderable: false}, {data: "source_display", orderable: false},
{data: "is_valid", orderable: false}, {data: "is_valid", orderable: false},
{data: "id", orderable: false} {data: "id", orderable: false}
], ],
...@@ -246,6 +249,13 @@ $(document).ready(function(){ ...@@ -246,6 +249,13 @@ $(document).ready(function(){
var uid = $this.data('uid'); var uid = $this.data('uid');
var the_url = '{% url "api-users:user-detail" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", uid); var the_url = '{% url "api-users:user-detail" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", uid);
objectDelete($this, name, the_url); objectDelete($this, name, the_url);
}).on('click', '.expired', function () {
var msg = '{% trans "User is expired" %}';
toastr.error(msg)
}).on('click', '.inactive', function () {
var msg = '{% trans 'User is inactive' %}';
toastr.error(msg)
}) })
</script> </script>
{% endblock %} {% endblock %}
......
...@@ -292,7 +292,8 @@ def check_otp_code(otp_secret_key, otp_code): ...@@ -292,7 +292,8 @@ def check_otp_code(otp_secret_key, otp_code):
if not otp_secret_key or not otp_code: if not otp_secret_key or not otp_code:
return False return False
totp = pyotp.TOTP(otp_secret_key) totp = pyotp.TOTP(otp_secret_key)
return totp.verify(otp_code) otp_valid_window = settings.OTP_VALID_WINDOW or 0
return totp.verify(otp=otp_code, valid_window=otp_valid_window)
def get_password_check_rules(): def get_password_check_rules():
......
...@@ -100,6 +100,9 @@ class Config: ...@@ -100,6 +100,9 @@ class Config:
} }
AUTH_LDAP_START_TLS = False AUTH_LDAP_START_TLS = False
#
# OTP_VALID_WINDOW = 0
def __init__(self): def __init__(self):
pass pass
...@@ -200,6 +203,10 @@ class DockerConfig(Config): ...@@ -200,6 +203,10 @@ class DockerConfig(Config):
AUTH_LDAP_START_TLS = False AUTH_LDAP_START_TLS = False
#
OTP_VALID_WINDOW = int(os.environ.get("OTP_VALID_WINDOW")) if os.environ.get("OTP_VALID_WINDOW") else 0
# Default using Config settings, you can write if/else for different env # Default using Config settings, you can write if/else for different env
config = DockerConfig() config = DockerConfig()
...@@ -90,6 +90,9 @@ class Config: ...@@ -90,6 +90,9 @@ class Config:
# AUTH_OPENID_CLIENT_ID = 'client-id' # AUTH_OPENID_CLIENT_ID = 'client-id'
# AUTH_OPENID_CLIENT_SECRET = 'client-secret' # AUTH_OPENID_CLIENT_SECRET = 'client-secret'
#
# OTP_VALID_WINDOW = 0
def __init__(self): def __init__(self):
pass pass
......
...@@ -26,8 +26,8 @@ LOG_DIR = os.path.join(BASE_DIR, 'logs') ...@@ -26,8 +26,8 @@ LOG_DIR = os.path.join(BASE_DIR, 'logs')
TMP_DIR = os.path.join(BASE_DIR, 'tmp') TMP_DIR = os.path.join(BASE_DIR, 'tmp')
HTTP_HOST = CONFIG.HTTP_BIND_HOST or '127.0.0.1' HTTP_HOST = CONFIG.HTTP_BIND_HOST or '127.0.0.1'
HTTP_PORT = CONFIG.HTTP_LISTEN_PORT or 8080 HTTP_PORT = CONFIG.HTTP_LISTEN_PORT or 8080
DEBUG = CONFIG.DEBUG DEBUG = CONFIG.DEBUG or False
LOG_LEVEL = CONFIG.LOG_LEVEL LOG_LEVEL = CONFIG.LOG_LEVEL or 'INFO'
START_TIMEOUT = 40 START_TIMEOUT = 40
WORKERS = 4 WORKERS = 4
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment