diff --git a/apps/applications/views/remote_app.py b/apps/applications/views/remote_app.py
index dbf0b51c0..dee6d50f2 100644
--- a/apps/applications/views/remote_app.py
+++ b/apps/applications/views/remote_app.py
@@ -6,11 +6,10 @@ from django.views.generic import TemplateView
from django.views.generic.edit import CreateView, UpdateView
from django.views.generic.detail import DetailView
from django.contrib.messages.views import SuccessMessageMixin
-from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse_lazy
-from common.permissions import PermissionsMixin, IsOrgAdmin
+from common.permissions import PermissionsMixin, IsOrgAdmin, IsValidUser
from common.const import create_success_msg, update_success_msg
from ..models import RemoteApp
@@ -92,8 +91,9 @@ class RemoteAppDetailView(PermissionsMixin, DetailView):
return super().get_context_data(**kwargs)
-class UserRemoteAppListView(LoginRequiredMixin, TemplateView):
+class UserRemoteAppListView(PermissionsMixin, TemplateView):
template_name = 'applications/user_remote_app_list.html'
+ permission_classes = [IsValidUser]
def get_context_data(self, **kwargs):
context = {
diff --git a/apps/assets/api/asset_user.py b/apps/assets/api/asset_user.py
index 7000a95a5..573928a76 100644
--- a/apps/assets/api/asset_user.py
+++ b/apps/assets/api/asset_user.py
@@ -1,58 +1,121 @@
# -*- coding: utf-8 -*-
#
+import time
from rest_framework.response import Response
from rest_framework import viewsets, status, generics
from rest_framework.pagination import LimitOffsetPagination
+from rest_framework import filters
+from rest_framework_bulk import BulkModelViewSet
+from django.shortcuts import get_object_or_404
from common.permissions import IsOrgAdminOrAppUser
from common.utils import get_object_or_none, get_logger
-
-from ..backends.multi import AssetUserManager
-from ..models import Asset
+from common.mixins import IDInCacheFilterMixin
+from ..backends import AssetUserManager
+from ..models import Asset, Node, SystemUser, AdminUser
from .. import serializers
from ..tasks import test_asset_users_connectivity_manual
__all__ = [
'AssetUserViewSet', 'AssetUserAuthInfoApi', 'AssetUserTestConnectiveApi',
+ 'AssetUserExportViewSet',
]
logger = get_logger(__name__)
-class AssetUserViewSet(viewsets.GenericViewSet):
+class AssetUserFilterBackend(filters.BaseFilterBackend):
+ def filter_queryset(self, request, queryset, view):
+ kwargs = {}
+ for field in view.filter_fields:
+ value = request.GET.get(field)
+ if not value:
+ continue
+ if field in ("node_id", "system_user_id", "admin_user_id"):
+ continue
+ kwargs[field] = value
+ return queryset.filter(**kwargs)
+
+
+class AssetUserSearchBackend(filters.BaseFilterBackend):
+ def filter_queryset(self, request, queryset, view):
+ value = request.GET.get('search')
+ if not value:
+ return queryset
+ _queryset = AssetUserManager.none()
+ for field in view.search_fields:
+ if field in ("node_id", "system_user_id", "admin_user_id"):
+ continue
+ _queryset |= queryset.filter(**{field: value})
+ return _queryset
+
+
+class AssetUserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
pagination_class = LimitOffsetPagination
serializer_class = serializers.AssetUserSerializer
permission_classes = (IsOrgAdminOrAppUser, )
http_method_names = ['get', 'post']
-
- def create(self, request, *args, **kwargs):
- serializer = self.get_serializer(data=request.data)
- serializer.is_valid(raise_exception=True)
- serializer.save()
- return Response(serializer.data, status=status.HTTP_201_CREATED)
-
- def list(self, request, *args, **kwargs):
- queryset = self.filter_queryset(self.get_queryset())
- serializer = self.get_serializer(queryset, many=True)
- return Response(serializer.data)
+ filter_fields = [
+ "id", "ip", "hostname", "username", "asset_id", "node_id",
+ "system_user_id", "admin_user_id"
+ ]
+ search_fields = filter_fields
+ filter_backends = (
+ filters.OrderingFilter,
+ AssetUserFilterBackend, AssetUserSearchBackend,
+ )
def get_queryset(self):
+ # 尽可能先返回更少的数据
username = self.request.GET.get('username')
asset_id = self.request.GET.get('asset_id')
- asset = get_object_or_none(Asset, pk=asset_id)
- queryset = AssetUserManager.filter(username=username, asset=asset)
+ node_id = self.request.GET.get('node_id')
+ admin_user_id = self.request.GET.get("admin_user_id")
+ system_user_id = self.request.GET.get("system_user_id")
+
+ kwargs = {}
+ assets = []
+
+ manager = AssetUserManager()
+ if system_user_id:
+ system_user = get_object_or_404(SystemUser, id=system_user_id)
+ assets = system_user.assets.all()
+ username = system_user.username
+ elif admin_user_id:
+ admin_user = get_object_or_404(AdminUser, id=admin_user_id)
+ assets = admin_user.assets.all()
+ username = admin_user.username
+ manager.prefer('admin_user')
+
+ if asset_id:
+ asset = get_object_or_none(Asset, pk=asset_id)
+ assets = [asset]
+ elif node_id:
+ node = get_object_or_404(Node, id=node_id)
+ assets = node.assets.all()
+
+ if username:
+ kwargs['username'] = username
+ if assets:
+ kwargs['assets'] = assets
+
+ queryset = manager.filter(**kwargs)
return queryset
- def filter_queryset(self, queryset):
- queryset = sorted(
- queryset,
- key=lambda q: (q.asset.hostname, q.connectivity, q.username)
- )
- return queryset
+
+class AssetUserExportViewSet(AssetUserViewSet):
+ serializer_class = serializers.AssetUserExportSerializer
+ http_method_names = ['get']
+
+ def list(self, request, *args, **kwargs):
+ otp_last_verify = request.session.get("OTP_LAST_VERIFY_TIME")
+ if not otp_last_verify or time.time() - int(otp_last_verify) > 600:
+ return Response({"error": "Need MFA confirm mfa auth"}, status=403)
+ return super().list(request, *args, **kwargs)
class AssetUserAuthInfoApi(generics.RetrieveAPIView):
@@ -60,6 +123,10 @@ class AssetUserAuthInfoApi(generics.RetrieveAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
def retrieve(self, request, *args, **kwargs):
+ otp_last_verify = request.session.get("OTP_LAST_VERIFY_TIME")
+ if not otp_last_verify or time.time() - int(otp_last_verify) > 600:
+ return Response({"error": "Need MFA confirm mfa auth"}, status=403)
+
instance = self.get_object()
serializer = self.get_serializer(instance)
status_code = status.HTTP_200_OK
@@ -70,9 +137,13 @@ class AssetUserAuthInfoApi(generics.RetrieveAPIView):
def get_object(self):
username = self.request.GET.get('username')
asset_id = self.request.GET.get('asset_id')
+ prefer = self.request.GET.get("prefer")
asset = get_object_or_none(Asset, pk=asset_id)
try:
- instance = AssetUserManager.get(username, asset)
+ manger = AssetUserManager()
+ if prefer:
+ manger.prefer(prefer)
+ instance = manger.get(username, asset)
except Exception as e:
logger.error(e, exc_info=True)
return None
@@ -84,18 +155,36 @@ class AssetUserTestConnectiveApi(generics.RetrieveAPIView):
"""
Test asset users connective
"""
+ permission_classes = (IsOrgAdminOrAppUser,)
def get_asset_users(self):
username = self.request.GET.get('username')
asset_id = self.request.GET.get('asset_id')
asset = get_object_or_none(Asset, pk=asset_id)
- asset_users = AssetUserManager.filter(username=username, asset=asset)
+ manager = AssetUserManager()
+ asset_users = manager.filter(username=username, assets=[asset])
return asset_users
def retrieve(self, request, *args, **kwargs):
asset_users = self.get_asset_users()
- task = test_asset_users_connectivity_manual.delay(asset_users)
+ prefer = self.request.GET.get("prefer")
+ kwargs = {}
+ if prefer == "admin_user":
+ kwargs["run_as_admin"] = True
+ task = test_asset_users_connectivity_manual.delay(asset_users, **kwargs)
return Response({"task": task.id})
+class AssetUserPushApi(generics.CreateAPIView):
+ """
+ Test asset users connective
+ """
+ serializer_class = serializers.AssetUserPushSerializer
+ permission_classes = (IsOrgAdminOrAppUser,)
+ def create(self, request, *args, **kwargs):
+ serializer = self.get_serializer(data=request.data)
+ serializer.is_valid(raise_exception=True)
+ asset = serializer.validated_data["asset"]
+ username = serializer.validated_data["username"]
+ pass
diff --git a/apps/assets/backends/__init__.py b/apps/assets/backends/__init__.py
index e69de29bb..9a22a23dd 100644
--- a/apps/assets/backends/__init__.py
+++ b/apps/assets/backends/__init__.py
@@ -0,0 +1 @@
+from .manager import AssetUserManager
diff --git a/apps/assets/backends/admin_user.py b/apps/assets/backends/admin_user.py
new file mode 100644
index 000000000..8f3644b1b
--- /dev/null
+++ b/apps/assets/backends/admin_user.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+#
+
+from ..models import AdminUser
+from .asset_user import AssetUserBackend
+
+
+class AdminUserBackend(AssetUserBackend):
+ model = AdminUser
+ backend = 'AdminUser'
diff --git a/apps/assets/backends/asset_user.py b/apps/assets/backends/asset_user.py
new file mode 100644
index 000000000..62ab24b4d
--- /dev/null
+++ b/apps/assets/backends/asset_user.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+#
+from .base import BaseBackend
+
+
+class AssetUserBackend(BaseBackend):
+ model = None
+ backend = "AssetUser"
+
+ @classmethod
+ def filter_queryset_more(cls, queryset):
+ return queryset
+
+ @classmethod
+ def filter(cls, username=None, assets=None, **kwargs):
+ queryset = cls.model.objects.all()
+ if username:
+ queryset = queryset.filter(username=username)
+ if assets:
+ queryset = queryset.filter(assets__in=assets).distinct()
+ queryset = cls.filter_queryset_more(queryset)
+ instances = cls.construct_authbook_objects(queryset, assets)
+ return instances
+
+ @classmethod
+ def construct_authbook_objects(cls, asset_users, assets):
+ instances = []
+ for asset_user in asset_users:
+ if not assets:
+ assets = asset_user.assets.all()
+ for asset in assets:
+ instance = asset_user.construct_to_authbook(asset)
+ instance.backend = cls.backend
+ instances.append(instance)
+ return instances
diff --git a/apps/assets/backends/base.py b/apps/assets/backends/base.py
index c93ea6a31..d6f30f940 100644
--- a/apps/assets/backends/base.py
+++ b/apps/assets/backends/base.py
@@ -1,60 +1,84 @@
# -*- coding: utf-8 -*-
#
-
-from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
+import uuid
from abc import abstractmethod
-class NotSupportError(Exception):
- pass
-
-
class BaseBackend:
- ObjectDoesNotExist = ObjectDoesNotExist
- MultipleObjectsReturned = MultipleObjectsReturned
- NotSupportError = NotSupportError
- MSG_NOT_EXIST = '{} Object matching query does not exist'
- MSG_MULTIPLE = '{} get() returned more than one object ' \
- '-- it returned {}!'
-
- @classmethod
- def get(cls, username, asset):
- instances = cls.filter(username, asset)
- if len(instances) == 1:
- return instances[0]
- elif len(instances) == 0:
- cls.raise_does_not_exist(cls.__name__)
- else:
- cls.raise_multiple_return(cls.__name__, len(instances))
-
@classmethod
@abstractmethod
- def filter(cls, username=None, asset=None, latest=True):
+ def filter(cls, username=None, assets=None, latest=True):
"""
:param username: 用户名
- :param asset: 对象
+ :param assets: 对象
:param latest: 是否是最新记录
:return: 元素为的可迭代对象( or )
"""
pass
- @classmethod
- @abstractmethod
- def create(cls, **kwargs):
- """
- :param kwargs:
- {
- name, username, asset, comment, password, public_key, private_key,
- (org_id)
- }
- :return: 对象
- """
- pass
- @classmethod
- def raise_does_not_exist(cls, name):
- raise cls.ObjectDoesNotExist(cls.MSG_NOT_EXIST.format(name))
+class AssetUserQuerySet(list):
+ def order_by(self, *ordering):
+ _ordering = []
+ reverse = False
+ for i in ordering:
+ if i[0] == '-':
+ reverse = True
+ i = i[1:]
+ _ordering.append(i)
+ self.sort(key=lambda obj: [getattr(obj, j) for j in _ordering], reverse=reverse)
+ return self
- @classmethod
- def raise_multiple_return(cls, name, length):
- raise cls.MultipleObjectsReturned(cls.MSG_MULTIPLE.format(name, length))
+ def filter_in(self, kwargs):
+ in_kwargs = {}
+ queryset = []
+ for k, v in kwargs.items():
+ if len(v) == 0:
+ return self
+ if k.find("__in") >= 0:
+ in_kwargs[k] = v
+ for k in in_kwargs:
+ kwargs.pop(k)
+
+ if len(in_kwargs) == 0:
+ return self
+ for i in self:
+ matched = True
+ for k, v in in_kwargs.items():
+ key = k.split('__')[0]
+ attr = getattr(i, key, None)
+ # 如果属性或者value中是uuid,则转换成string
+ if isinstance(v[0], uuid.UUID):
+ v = [str(i) for i in v]
+ if isinstance(attr, uuid.UUID):
+ attr = str(attr)
+ if attr not in v:
+ matched = False
+ if matched:
+ queryset.append(i)
+ return AssetUserQuerySet(queryset)
+
+ def filter_equal(self, kwargs):
+ def filter_it(obj):
+ wanted = []
+ real = []
+ for k, v in kwargs.items():
+ wanted.append(v)
+ value = getattr(obj, k)
+ if isinstance(value, uuid.UUID):
+ value = str(value)
+ real.append(value)
+ return wanted == real
+ if len(kwargs) > 0:
+ queryset = AssetUserQuerySet([i for i in self if filter_it(i)])
+ else:
+ queryset = self
+ return queryset
+
+ def filter(self, **kwargs):
+ queryset = self.filter_in(kwargs).filter_equal(kwargs)
+ return queryset
+
+ def __or__(self, other):
+ self.extend(other)
+ return self
diff --git a/apps/assets/backends/external/db.py b/apps/assets/backends/db.py
similarity index 76%
rename from apps/assets/backends/external/db.py
rename to apps/assets/backends/db.py
index eedafd336..f37569e51 100644
--- a/apps/assets/backends/external/db.py
+++ b/apps/assets/backends/db.py
@@ -1,20 +1,18 @@
# -*- coding: utf-8 -*-
#
-from assets.models import AuthBook
-
-from ..base import BaseBackend
+from ..models import AuthBook
+from .base import BaseBackend
class AuthBookBackend(BaseBackend):
-
@classmethod
- def filter(cls, username=None, asset=None, latest=True):
+ def filter(cls, username=None, assets=None, latest=True):
queryset = AuthBook.objects.all()
if username is not None:
queryset = queryset.filter(username=username)
- if asset:
- queryset = queryset.filter(asset=asset)
+ if assets:
+ queryset = queryset.filter(asset__in=assets)
if latest:
queryset = queryset.latest_version()
return queryset
diff --git a/apps/assets/backends/external/__init__.py b/apps/assets/backends/external/__init__.py
deleted file mode 100644
index ec51c5a2b..000000000
--- a/apps/assets/backends/external/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-# -*- coding: utf-8 -*-
-#
diff --git a/apps/assets/backends/internal/__init__.py b/apps/assets/backends/internal/__init__.py
deleted file mode 100644
index f19a64d9a..000000000
--- a/apps/assets/backends/internal/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-
-
diff --git a/apps/assets/backends/internal/admin_user.py b/apps/assets/backends/internal/admin_user.py
deleted file mode 100644
index abd32b5ae..000000000
--- a/apps/assets/backends/internal/admin_user.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-
-from assets.models import Asset
-
-from ..base import BaseBackend
-from .utils import construct_authbook_object
-
-
-class AdminUserBackend(BaseBackend):
-
- @classmethod
- def filter(cls, username=None, asset=None, **kwargs):
- instances = cls.construct_authbook_objects(username, asset)
- return instances
-
- @classmethod
- def _get_assets(cls, asset):
- if not asset:
- assets = Asset.objects.all().prefetch_related('admin_user')
- else:
- assets = [asset]
- return assets
-
- @classmethod
- def construct_authbook_objects(cls, username, asset):
- instances = []
- assets = cls._get_assets(asset)
- for asset in assets:
- if username is not None and asset.admin_user.username != username:
- continue
- instance = construct_authbook_object(asset.admin_user, asset)
- instances.append(instance)
- return instances
-
- @classmethod
- def create(cls, **kwargs):
- raise cls.NotSupportError("Not support create")
diff --git a/apps/assets/backends/internal/asset_user.py b/apps/assets/backends/internal/asset_user.py
deleted file mode 100644
index 8502cf5d9..000000000
--- a/apps/assets/backends/internal/asset_user.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-
-from ..base import BaseBackend
-from .admin_user import AdminUserBackend
-from .system_user import SystemUserBackend
-
-
-class AssetUserBackend(BaseBackend):
- @classmethod
- def filter(cls, username=None, asset=None, **kwargs):
- admin_user_instances = AdminUserBackend.filter(username, asset)
- system_user_instances = SystemUserBackend.filter(username, asset)
- instances = cls._merge_instances(admin_user_instances, system_user_instances)
- return instances
-
- @classmethod
- def _merge_instances(cls, admin_user_instances, system_user_instances):
- admin_user_instances_keyword_list = [
- {'username': instance.username, 'asset': instance.asset}
- for instance in admin_user_instances
- ]
- instances = [
- instance for instance in system_user_instances
- if instance.keyword not in admin_user_instances_keyword_list
- ]
- admin_user_instances.extend(instances)
- return admin_user_instances
-
- @classmethod
- def create(cls, **kwargs):
- raise cls.NotSupportError("Not support create")
diff --git a/apps/assets/backends/internal/system_user.py b/apps/assets/backends/internal/system_user.py
deleted file mode 100644
index b1413fedb..000000000
--- a/apps/assets/backends/internal/system_user.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-
-import itertools
-
-from assets.models import Asset
-
-from ..base import BaseBackend
-from .utils import construct_authbook_object
-
-
-class SystemUserBackend(BaseBackend):
-
- @classmethod
- def filter(cls, username=None, asset=None, **kwargs):
- instances = cls.construct_authbook_objects(username, asset)
- return instances
-
- @classmethod
- def _distinct_system_users_by_username(cls, system_users):
- system_users = sorted(
- system_users,
- key=lambda su: (su.username, su.priority, su.date_updated),
- reverse=True,
- )
- results = itertools.groupby(system_users, key=lambda su: su.username)
- system_users = [next(result[1]) for result in results]
- return system_users
-
- @classmethod
- def _filter_system_users_by_username(cls, system_users, username):
- _system_users = cls._distinct_system_users_by_username(system_users)
- if username is not None:
- _system_users = [su for su in _system_users if username == su.username]
- return _system_users
-
- @classmethod
- def _construct_authbook_objects(cls, system_users, asset):
- instances = []
- for system_user in system_users:
- instance = construct_authbook_object(system_user, asset)
- instances.append(instance)
- return instances
-
- @classmethod
- def _get_assets_with_system_users(cls, asset=None):
- """
- { 'asset': set(, , ...) }
- """
- if not asset:
- _assets = Asset.objects.all().prefetch_related('systemuser_set')
- else:
- _assets = [asset]
-
- assets = {asset: set(asset.systemuser_set.all()) for asset in _assets}
- return assets
-
- @classmethod
- def construct_authbook_objects(cls, username, asset):
- """
- :return: [, , ...]
- """
- instances = []
- assets = cls._get_assets_with_system_users(asset)
- for _asset, _system_users in assets.items():
- _system_users = cls._filter_system_users_by_username(_system_users, username)
- _instances = cls._construct_authbook_objects(_system_users, _asset)
- instances.extend(_instances)
- return instances
-
- @classmethod
- def create(cls, **kwargs):
- raise Exception("Not support create")
-
-
diff --git a/apps/assets/backends/internal/utils.py b/apps/assets/backends/internal/utils.py
deleted file mode 100644
index 65b4fa821..000000000
--- a/apps/assets/backends/internal/utils.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-
-from assets.models import AuthBook
-
-
-def construct_authbook_object(asset_user, asset):
- """
- 作用: 将对象构造成为对象并返回
-
- :param asset_user: 或对象
- :param asset: 对象
- :return: 对象
- """
- fields = [
- 'id', 'name', 'username', 'comment', 'org_id',
- '_password', '_private_key', '_public_key',
- 'date_created', 'date_updated', 'created_by'
- ]
-
- obj = AuthBook(asset=asset, version=0, is_latest=True)
- for field in fields:
- value = getattr(asset_user, field)
- setattr(obj, field, value)
- return obj
-
diff --git a/apps/assets/backends/manager.py b/apps/assets/backends/manager.py
new file mode 100644
index 000000000..180c2861c
--- /dev/null
+++ b/apps/assets/backends/manager.py
@@ -0,0 +1,113 @@
+# -*- coding: utf-8 -*-
+#
+from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
+
+from .base import AssetUserQuerySet
+from .db import AuthBookBackend
+from .system_user import SystemUserBackend
+from .admin_user import AdminUserBackend
+
+
+class NotSupportError(Exception):
+ pass
+
+
+class AssetUserManager:
+ """
+ 资产用户管理器
+ """
+ ObjectDoesNotExist = ObjectDoesNotExist
+ MultipleObjectsReturned = MultipleObjectsReturned
+ NotSupportError = NotSupportError
+ MSG_NOT_EXIST = '{} Object matching query does not exist'
+ MSG_MULTIPLE = '{} get() returned more than one object ' \
+ '-- it returned {}!'
+
+ backends = (
+ ('db', AuthBookBackend),
+ ('system_user', SystemUserBackend),
+ ('admin_user', AdminUserBackend),
+ )
+
+ _prefer = "system_user"
+ _using = None
+
+ def filter(self, username=None, assets=None, latest=True):
+ if self._using:
+ backend = dict(self.backends).get(self._using)
+ if not backend:
+ return self.none()
+ instances = backend.filter(username=username, assets=assets, latest=latest)
+ return AssetUserQuerySet(instances)
+
+ instances_map = {}
+ instances = []
+ for name, backend in self.backends:
+ _instances = backend.filter(
+ username=username, assets=assets, latest=latest
+ )
+ instances_map[name] = _instances
+
+ # 如果不是获取最新版本,就不再merge
+ if not latest:
+ for _instances in instances_map.values():
+ instances.extend(_instances)
+ return AssetUserQuerySet(instances)
+
+ # merge的顺序
+ ordering = ["db"]
+ if self._prefer == "system_user":
+ ordering.extend(["system_user", "admin_user"])
+ else:
+ ordering.extend(["admin_user", "system_user"])
+ # 根据prefer决定优先使用系统用户或管理用户谁的
+ ordering_instances = [instances_map.get(i) for i in ordering]
+ instances = self._merge_instances(*ordering_instances)
+ return AssetUserQuerySet(instances)
+
+ def get(self, username, asset):
+ instances = self.filter(username, assets=[asset])
+ if len(instances) == 1:
+ return instances[0]
+ elif len(instances) == 0:
+ self.raise_does_not_exist(self.__name__)
+ else:
+ self.raise_multiple_return(self.__name__, len(instances))
+
+ def raise_does_not_exist(self, name):
+ raise self.ObjectDoesNotExist(self.MSG_NOT_EXIST.format(name))
+
+ def raise_multiple_return(self, name, length):
+ raise self.MultipleObjectsReturned(self.MSG_MULTIPLE.format(name, length))
+
+ @staticmethod
+ def create(**kwargs):
+ instance = AuthBookBackend.create(**kwargs)
+ return instance
+
+ def all(self):
+ return self.filter()
+
+ def prefer(self, s):
+ self._prefer = s
+ return self
+
+ def using(self, s):
+ self._using = s
+ return self
+
+ @staticmethod
+ def none():
+ return AssetUserQuerySet()
+
+ @staticmethod
+ def _merge_instances(*args):
+ instances = list(args[0])
+ keywords = [obj.keyword for obj in instances]
+
+ for _instances in args[1:]:
+ need_merge_instances = [obj for obj in _instances if obj.keyword not in keywords]
+ need_merge_keywords = [obj.keyword for obj in need_merge_instances]
+ instances.extend(need_merge_instances)
+ keywords.extend(need_merge_keywords)
+ return instances
diff --git a/apps/assets/backends/multi.py b/apps/assets/backends/multi.py
deleted file mode 100644
index 785176885..000000000
--- a/apps/assets/backends/multi.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-
-from .base import BaseBackend
-
-from .external.utils import get_backend
-from .internal.asset_user import AssetUserBackend
-
-
-class AssetUserManager(BaseBackend):
- """
- 资产用户管理器
- """
- external_backend = get_backend()
- internal_backend = AssetUserBackend
-
- @classmethod
- def filter(cls, username=None, asset=None, **kwargs):
- external_instance = list(cls.external_backend.filter(username, asset))
- internal_instance = list(cls.internal_backend.filter(username, asset))
- instances = cls._merge_instances(external_instance, internal_instance)
- return instances
-
- @classmethod
- def create(cls, **kwargs):
- instance = cls.external_backend.create(**kwargs)
- return instance
-
- @classmethod
- def _merge_instances(cls, external_instances, internal_instances):
- external_instances_keyword_list = [
- {'username': instance.username, 'asset': instance.asset}
- for instance in external_instances
- ]
- instances = [
- instance for instance in internal_instances
- if instance.keyword not in external_instances_keyword_list
- ]
- external_instances.extend(instances)
- return external_instances
diff --git a/apps/assets/backends/system_user.py b/apps/assets/backends/system_user.py
new file mode 100644
index 000000000..8dda67d2a
--- /dev/null
+++ b/apps/assets/backends/system_user.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+#
+
+import itertools
+
+from assets.models import SystemUser
+from .asset_user import AssetUserBackend
+
+
+class SystemUserBackend(AssetUserBackend):
+ model = SystemUser
+ backend = 'SystemUser'
+
+ @classmethod
+ def filter_queryset_more(cls, queryset):
+ queryset = cls._distinct_system_users_by_username(queryset)
+ return queryset
+
+ @classmethod
+ def _distinct_system_users_by_username(cls, system_users):
+ system_users = sorted(
+ system_users,
+ key=lambda su: (su.username, su.priority, su.date_updated),
+ reverse=True,
+ )
+ results = itertools.groupby(system_users, key=lambda su: su.username)
+ system_users = [next(result[1]) for result in results]
+ return system_users
+
+
diff --git a/apps/assets/backends/external/utils.py b/apps/assets/backends/utils.py
similarity index 100%
rename from apps/assets/backends/external/utils.py
rename to apps/assets/backends/utils.py
diff --git a/apps/assets/backends/external/vault.py b/apps/assets/backends/vault.py
similarity index 100%
rename from apps/assets/backends/external/vault.py
rename to apps/assets/backends/vault.py
diff --git a/apps/assets/const.py b/apps/assets/const.py
index 1f93482b6..eebb5ecca 100644
--- a/apps/assets/const.py
+++ b/apps/assets/const.py
@@ -49,7 +49,7 @@ TEST_WINDOWS_SYSTEM_USER_CONN_TASKS = [
}
]
-ASSET_USER_CONN_CACHE_KEY = 'ASSET_USER_CONN_{}_{}'
+ASSET_USER_CONN_CACHE_KEY = 'ASSET_USER_CONN_{}'
TEST_ASSET_USER_CONN_TASKS = [
{
"name": "ping",
diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py
index 9512d26ba..4450751dd 100644
--- a/apps/assets/models/asset.py
+++ b/apps/assets/models/asset.py
@@ -104,7 +104,7 @@ class Asset(OrgModelMixin):
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
# Auth
- admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, null=True, verbose_name=_("Admin user"))
+ admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, null=True, verbose_name=_("Admin user"), related_name='assets')
# Some information
public_ip = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Public IP'))
@@ -250,16 +250,11 @@ class Asset(OrgModelMixin):
@property
def connectivity(self):
- if not self.is_unixlike():
- return self.REACHABLE
- key = self.CONNECTIVITY_CACHE_KEY.format(str(self.id))
- cached = cache.get(key, None)
- return cached if cached is not None else self.UNKNOWN
+ return self.admin_user.get_connectivity_of(self)
@connectivity.setter
def connectivity(self, value):
- key = self.CONNECTIVITY_CACHE_KEY.format(str(self.id))
- cache.set(key, value, 3600*2)
+ self.admin_user.set_connectivity_of(self, value)
def get_auth_info(self):
if not self.admin_user:
diff --git a/apps/assets/models/authbook.py b/apps/assets/models/authbook.py
index 94e4f4cf4..e61c3423d 100644
--- a/apps/assets/models/authbook.py
+++ b/apps/assets/models/authbook.py
@@ -29,6 +29,9 @@ class AuthBook(AssetUser):
version = models.IntegerField(default=1, verbose_name=_('Version'))
objects = AuthBookManager.from_queryset(AuthBookQuerySet)()
+ backend = "db"
+ # 用于system user和admin_user的动态设置
+ _connectivity = None
class Meta:
verbose_name = _('AuthBook')
@@ -40,7 +43,8 @@ class AuthBook(AssetUser):
def _get_pre_obj(self):
pre_obj = self.__class__.objects.filter(
- username=self.username, asset=self.asset).latest_version().first()
+ username=self.username, asset=self.asset
+ ).latest_version().first()
return pre_obj
def _remove_pre_obj_latest(self):
@@ -63,30 +67,30 @@ class AuthBook(AssetUser):
@property
def _conn_cache_key(self):
- return ASSET_USER_CONN_CACHE_KEY.format(self.id, self.asset.id)
+ return ASSET_USER_CONN_CACHE_KEY.format(self.id)
@property
def connectivity(self):
+ if self._connectivity:
+ return self._connectivity
value = cache.get(self._conn_cache_key, self.UNKNOWN)
return value
@connectivity.setter
def connectivity(self, value):
- _connectivity = self.UNKNOWN
-
- for host in value.get('dark', {}).keys():
- if host == self.asset.hostname:
- _connectivity = self.UNREACHABLE
-
- for host in value.get('contacted', []):
- if host == self.asset.hostname:
- _connectivity = self.REACHABLE
-
- cache.set(self._conn_cache_key, _connectivity, 3600)
+ cache.set(self._conn_cache_key, value, 3600)
@property
def keyword(self):
- return {'username': self.username, 'asset': self.asset}
+ return '{}_#_{}'.format(self.username, str(self.asset.id))
+
+ @property
+ def hostname(self):
+ return self.asset.hostname
+
+ @property
+ def ip(self):
+ return self.asset.ip
def __str__(self):
return '{}@{}'.format(self.username, self.asset)
diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py
index 40336fd00..42fef8d04 100644
--- a/apps/assets/models/base.py
+++ b/apps/assets/models/base.py
@@ -6,6 +6,7 @@ from hashlib import md5
import sshpubkeys
from django.db import models
+from django.core.cache import cache
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
@@ -39,6 +40,8 @@ class AssetUser(OrgModelMixin):
(REACHABLE, _('Reachable')),
(UNKNOWN, _("Unknown")),
)
+ CONNECTIVITY_CACHE_KEY = "CONNECTIVITY_{}"
+ _prefer = "system_user"
@property
def password(self):
@@ -124,10 +127,21 @@ class AssetUser(OrgModelMixin):
def get_auth(self, asset=None):
pass
+ def get_connectivity_of(self, asset):
+ i = self.generate_id_with_asset(asset)
+ key = self.CONNECTIVITY_CACHE_KEY.format(i)
+ return cache.get(key)
+
+ def set_connectivity_of(self, asset, c):
+ i = self.generate_id_with_asset(asset)
+ key = self.CONNECTIVITY_CACHE_KEY.format(i)
+ cache.set(key, c, 3600)
+
def load_specific_asset_auth(self, asset):
- from ..backends.multi import AssetUserManager
+ from ..backends import AssetUserManager
try:
- other = AssetUserManager.get(username=self.username, asset=asset)
+ manager = AssetUserManager().prefer(self._prefer)
+ other = manager.get(username=self.username, asset=asset)
except Exception as e:
logger.error(e, exc_info=True)
else:
@@ -172,5 +186,25 @@ class AssetUser(OrgModelMixin):
'private_key': self.private_key_file,
}
+ def generate_id_with_asset(self, asset):
+ id_ = '{}_{}'.format(asset.id, self.id)
+ id_ = uuid.UUID(md5(id_.encode()).hexdigest())
+ return id_
+
+ def construct_to_authbook(self, asset):
+ from . import AuthBook
+ fields = [
+ 'name', 'username', 'comment', 'org_id',
+ '_password', '_private_key', '_public_key',
+ 'date_created', 'date_updated', 'created_by'
+ ]
+ id_ = self.generate_id_with_asset(asset)
+ obj = AuthBook(id=id_, asset=asset, version=0, is_latest=True)
+ obj._connectivity = self.get_connectivity_of(asset)
+ for field in fields:
+ value = getattr(self, field)
+ setattr(obj, field, value)
+ return obj
+
class Meta:
abstract = True
diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py
index ecd9ae429..2cded41a1 100644
--- a/apps/assets/models/user.py
+++ b/apps/assets/models/user.py
@@ -32,6 +32,7 @@ class AdminUser(AssetUser):
become_user = models.CharField(default='root', max_length=64)
_become_pass = models.CharField(default='', max_length=128)
CONNECTIVE_CACHE_KEY = '_JMS_ADMIN_USER_CONNECTIVE_{}'
+ _prefer = "admin_user"
def __str__(self):
return self.name
@@ -61,7 +62,7 @@ class AdminUser(AssetUser):
return info
def get_related_assets(self):
- assets = self.asset_set.all()
+ assets = self.assets.all()
return assets
@property
@@ -174,17 +175,20 @@ class SystemUser(AssetUser):
data = self.connectivity
unreachable = data['unreachable']
reachable = data['reachable']
+ assets = {asset.hostname: asset for asset in self.assets.all()}
for host in value.get('dark', {}).keys():
if host not in unreachable:
unreachable.append(host)
if host in reachable:
reachable.remove(host)
+ self.set_connectivity_of(assets.get(host), self.UNREACHABLE)
for host in value.get('contacted'):
if host not in reachable:
reachable.append(host)
if host in unreachable:
unreachable.remove(host)
+ self.set_connectivity_of(assets.get(host), self.REACHABLE)
cache_key = self.CONNECTIVE_CACHE_KEY.format(str(self.id))
cache.set(cache_key, data, 3600)
diff --git a/apps/assets/serializers/asset_user.py b/apps/assets/serializers/asset_user.py
index f7345437e..517a1e9c3 100644
--- a/apps/assets/serializers/asset_user.py
+++ b/apps/assets/serializers/asset_user.py
@@ -4,49 +4,70 @@
from django.utils.translation import ugettext as _
from rest_framework import serializers
-from ..models import AuthBook
-from ..backends.multi import AssetUserManager
+from ..models import AuthBook, Asset
+from ..backends import AssetUserManager
+from common.utils import validate_ssh_private_key
+from common.mixins import BulkSerializerMixin
+from common.serializers import AdaptedBulkListSerializer
+
__all__ = [
'AssetUserSerializer', 'AssetUserAuthInfoSerializer',
+ 'AssetUserExportSerializer', 'AssetUserPushSerializer',
]
-class AssetUserSerializer(serializers.ModelSerializer):
+class BasicAssetSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = Asset
+ fields = ['hostname', 'ip']
+
+
+class AssetUserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
+ hostname = serializers.CharField(read_only=True, label=_("Hostname"))
+ ip = serializers.CharField(read_only=True, label=_("IP"))
+ connectivity = serializers.CharField(read_only=True, label=_("Connectivity"))
password = serializers.CharField(
max_length=256, allow_blank=True, allow_null=True, write_only=True,
- required=False, help_text=_('Password')
+ required=False, label=_('Password')
)
public_key = serializers.CharField(
max_length=4096, allow_blank=True, allow_null=True, write_only=True,
- required=False, help_text=_('Public key')
+ required=False, label=_('Public key')
)
private_key = serializers.CharField(
max_length=4096, allow_blank=True, allow_null=True, write_only=True,
- required=False, help_text=_('Private key')
+ required=False, label=_('Private key')
)
+ backend = serializers.CharField(read_only=True, label=_("Backend"))
class Meta:
model = AuthBook
+ list_serializer_class = AdaptedBulkListSerializer
read_only_fields = (
'date_created', 'date_updated', 'created_by',
'is_latest', 'version', 'connectivity',
)
- fields = '__all__'
+ fields = [
+ "id", "hostname", "ip", "username", "password", "asset", "version",
+ "is_latest", "connectivity", "backend", "org_id",
+ "date_created", "date_updated", "private_key", "public_key",
+ ]
extra_kwargs = {
- 'username': {'required': True}
+ 'username': {'required': True},
}
- def get_field_names(self, declared_fields, info):
- fields = super().get_field_names(declared_fields, info)
- fields = [f for f in fields if not f.startswith('_') and f != 'id']
- fields.extend(['connectivity'])
- return fields
+ def validate_private_key(self, key):
+ password = self.initial_data.get("password")
+ valid = validate_ssh_private_key(key, password)
+ if not valid:
+ raise serializers.ValidationError(_("private key invalid"))
+ return key
def create(self, validated_data):
kwargs = {
- 'name': validated_data.get('name'),
+ 'name': validated_data.get('username'),
'username': validated_data.get('username'),
'asset': validated_data.get('asset'),
'comment': validated_data.get('comment', ''),
@@ -59,7 +80,33 @@ class AssetUserSerializer(serializers.ModelSerializer):
return instance
+class AssetUserExportSerializer(AssetUserSerializer):
+ password = serializers.CharField(
+ max_length=256, allow_blank=True, allow_null=True,
+ required=False, label=_('Password')
+ )
+ public_key = serializers.CharField(
+ max_length=4096, allow_blank=True, allow_null=True,
+ required=False, label=_('Public key')
+ )
+ private_key = serializers.CharField(
+ max_length=4096, allow_blank=True, allow_null=True,
+ required=False, label=_('Private key')
+ )
+
+
class AssetUserAuthInfoSerializer(serializers.ModelSerializer):
class Meta:
model = AuthBook
fields = ['password', 'private_key', 'public_key']
+
+
+class AssetUserPushSerializer(serializers.Serializer):
+ asset = serializers.PrimaryKeyRelatedField(queryset=Asset.objects.all(), label=_("Asset"))
+ username = serializers.CharField(max_length=1024)
+
+ def create(self, validated_data):
+ pass
+
+ def update(self, instance, validated_data):
+ pass
diff --git a/apps/assets/tasks.py b/apps/assets/tasks.py
index 76ab6bc2e..fff623413 100644
--- a/apps/assets/tasks.py
+++ b/apps/assets/tasks.py
@@ -563,11 +563,17 @@ def get_test_asset_user_connectivity_tasks(asset):
@shared_task
def set_asset_user_connectivity_info(asset_user, result):
summary = result[1]
- asset_user.connectivity = summary
+ if summary.get('contacted'):
+ connectivity = 1
+ elif summary.get("dark"):
+ connectivity = 0
+ else:
+ connectivity = 3
+ asset_user.connectivity = connectivity
@shared_task
-def test_asset_user_connectivity_util(asset_user, task_name):
+def test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=False):
"""
:param asset_user: 对象
:param task_name:
@@ -582,23 +588,29 @@ def test_asset_user_connectivity_util(asset_user, task_name):
if not tasks:
return
- task, created = update_or_create_ansible_task(
- task_name, hosts=[asset_user.asset], tasks=tasks, pattern='all',
- options=const.TASK_OPTIONS,
- run_as=asset_user.username, created_by=asset_user.org_id
- )
+ args = (task_name,)
+ kwargs = {
+ 'hosts': [asset_user.asset], 'tasks': tasks,
+ 'pattern': 'all', 'options': const.TASK_OPTIONS,
+ 'created_by': asset_user.org_id,
+ }
+ if run_as_admin:
+ kwargs["run_as_admin"] = True
+ else:
+ kwargs["run_as"] = asset_user.username
+ task, created = update_or_create_ansible_task(*args, **kwargs)
result = task.run()
set_asset_user_connectivity_info(asset_user, result)
@shared_task
-def test_asset_users_connectivity_manual(asset_users):
+def test_asset_users_connectivity_manual(asset_users, run_as_admin=False):
"""
:param asset_users: 对象
"""
for asset_user in asset_users:
task_name = _("Test asset user connectivity: {}").format(asset_user)
- test_asset_user_connectivity_util(asset_user, task_name)
+ test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=run_as_admin)
# @shared_task
diff --git a/apps/assets/templates/assets/_asset_user_auth_modal.html b/apps/assets/templates/assets/_asset_user_auth_modal.html
deleted file mode 100644
index be615ce52..000000000
--- a/apps/assets/templates/assets/_asset_user_auth_modal.html
+++ /dev/null
@@ -1,28 +0,0 @@
-{% extends '_modal.html' %}
-{% load i18n %}
-{% block modal_id %}asset_user_auth_modal{% endblock %}
-{% block modal_title%}{% trans "Update asset user auth" %}{% endblock %}
-{% block modal_body %}
-
-{% endblock %}
-{% block modal_confirm_id %}btn_asset_user_auth_modal_confirm{% endblock %}
diff --git a/apps/assets/templates/assets/_asset_user_auth_update_modal.html b/apps/assets/templates/assets/_asset_user_auth_update_modal.html
new file mode 100644
index 000000000..28a1a956d
--- /dev/null
+++ b/apps/assets/templates/assets/_asset_user_auth_update_modal.html
@@ -0,0 +1,87 @@
+{% extends '_modal.html' %}
+{% load i18n %}
+{% block modal_id %}asset_user_auth_update_modal{% endblock %}
+{% block modal_title%}{% trans "Update asset user auth" %}{% endblock %}
+{% block modal_body %}
+
+
+{% endblock %}
+{% block modal_confirm_id %}btn_asset_user_auth_update_modal_confirm{% endblock %}
diff --git a/apps/assets/templates/assets/_asset_user_view_auth_modal.html b/apps/assets/templates/assets/_asset_user_auth_view_modal.html
similarity index 68%
rename from apps/assets/templates/assets/_asset_user_view_auth_modal.html
rename to apps/assets/templates/assets/_asset_user_auth_view_modal.html
index 05f3bf619..4164f2a2d 100644
--- a/apps/assets/templates/assets/_asset_user_view_auth_modal.html
+++ b/apps/assets/templates/assets/_asset_user_auth_view_modal.html
@@ -10,17 +10,7 @@
}
+
+{% endblock %}
+{% block modal_button %}
+
+{% endblock %}
diff --git a/apps/common/api.py b/apps/common/api.py
index 4f5f8da30..bf41312d7 100644
--- a/apps/common/api.py
+++ b/apps/common/api.py
@@ -48,7 +48,7 @@ class LogTailApi(generics.RetrieveAPIView):
return line
def read_from_file(self):
- with open(self.log_path, 'r') as f:
+ with open(self.log_path, 'rt', encoding='utf8') as f:
offset = cache.get(self.mark, 0)
f.seek(offset)
data = f.read(self.buff_size).replace('\n', '\r\n')
@@ -79,7 +79,6 @@ class LogTailApi(generics.RetrieveAPIView):
class ResourcesIDCacheApi(APIView):
-
def post(self, request, *args, **kwargs):
spm = str(uuid.uuid4())
resources_id = request.data.get('resources')
diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py
index 7f88e980a..453b1b9e7 100644
--- a/apps/jumpserver/settings.py
+++ b/apps/jumpserver/settings.py
@@ -358,7 +358,7 @@ EMAIL_USE_SSL = False
EMAIL_USE_TLS = False
EMAIL_SUBJECT_PREFIX = '[JMS] '
-#Email custom content
+# Email custom content
EMAIL_CUSTOM_USER_CREATED_SUBJECT = ''
EMAIL_CUSTOM_USER_CREATED_HONORIFIC = ''
EMAIL_CUSTOM_USER_CREATED_BODY = ''
diff --git a/apps/jumpserver/views.py b/apps/jumpserver/views.py
index 8f4954f69..f3272fb17 100644
--- a/apps/jumpserver/views.py
+++ b/apps/jumpserver/views.py
@@ -8,7 +8,6 @@ from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.db.models import Count
from django.shortcuts import redirect
-from django.contrib.auth.mixins import LoginRequiredMixin
from rest_framework.response import Response
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse
@@ -18,10 +17,12 @@ from users.models import User
from assets.models import Asset
from terminal.models import Session
from orgs.utils import current_org
+from common.permissions import PermissionsMixin, IsValidUser
-class IndexView(LoginRequiredMixin, TemplateView):
+class IndexView(PermissionsMixin, TemplateView):
template_name = 'index.html'
+ permission_classes = [IsValidUser]
session_week = None
session_month = None
diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo
index b31355b1c..8845aafc9 100644
Binary files a/apps/locale/zh/LC_MESSAGES/django.mo and b/apps/locale/zh/LC_MESSAGES/django.mo differ
diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po
index 06d988973..1c6088541 100644
--- a/apps/locale/zh/LC_MESSAGES/django.po
+++ b/apps/locale/zh/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Jumpserver 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-06-14 17:01+0800\n"
+"POT-Creation-Date: 2019-06-19 10:59+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler \n"
"Language-Team: Jumpserver team\n"
@@ -76,8 +76,9 @@ msgstr "运行参数"
#: applications/templates/applications/remote_app_list.html:22
#: applications/templates/applications/user_remote_app_list.html:18
#: assets/forms/domain.py:15 assets/forms/label.py:13
-#: assets/models/asset.py:320 assets/models/authbook.py:27
-#: assets/serializers/admin_user.py:23 assets/serializers/system_user.py:28
+#: assets/models/asset.py:315 assets/models/authbook.py:27
+#: assets/serializers/admin_user.py:23 assets/serializers/asset_user.py:105
+#: assets/serializers/system_user.py:28
#: assets/templates/assets/admin_user_list.html:49
#: assets/templates/assets/domain_detail.html:60
#: assets/templates/assets/domain_list.html:26
@@ -95,7 +96,7 @@ msgstr "运行参数"
#: terminal/templates/terminal/session_list.html:41
#: terminal/templates/terminal/session_list.html:72
#: xpack/plugins/change_auth_plan/forms.py:114
-#: xpack/plugins/change_auth_plan/models.py:409
+#: xpack/plugins/change_auth_plan/models.py:413
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:46
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:54
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:13
@@ -103,6 +104,7 @@ msgstr "运行参数"
#: xpack/plugins/cloud/models.py:187
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:63
#: xpack/plugins/orgs/templates/orgs/org_list.html:16
+#: xpack/plugins/vault/forms.py:13 xpack/plugins/vault/forms.py:15
msgid "Asset"
msgstr "资产"
@@ -110,7 +112,7 @@ msgstr "资产"
#: applications/templates/applications/remote_app_detail.html:61
#: applications/templates/applications/remote_app_list.html:23
#: applications/templates/applications/user_remote_app_list.html:19
-#: assets/models/user.py:247 assets/templates/assets/user_asset_list.html:172
+#: assets/models/user.py:251 assets/templates/assets/user_asset_list.html:172
#: audits/models.py:20 audits/templates/audits/ftp_log_list.html:49
#: audits/templates/audits/ftp_log_list.html:72
#: perms/forms/asset_permission.py:52 perms/models/asset_permission.py:39
@@ -133,7 +135,7 @@ msgstr "系统用户"
#: applications/templates/applications/remote_app_list.html:20
#: applications/templates/applications/user_remote_app_list.html:16
#: assets/forms/domain.py:73 assets/forms/user.py:84 assets/forms/user.py:148
-#: assets/models/asset.py:72 assets/models/base.py:26
+#: assets/models/asset.py:72 assets/models/base.py:27
#: assets/models/cluster.py:18 assets/models/cmd_filter.py:20
#: assets/models/domain.py:20 assets/models/group.py:20
#: assets/models/label.py:18 assets/templates/assets/admin_user_detail.html:56
@@ -173,7 +175,7 @@ msgstr "系统用户"
#: users/templates/users/user_profile.html:51
#: users/templates/users/user_pubkey_update.html:53
#: xpack/plugins/change_auth_plan/forms.py:97
-#: xpack/plugins/change_auth_plan/models.py:58
+#: xpack/plugins/change_auth_plan/models.py:61
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:61
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:12
#: xpack/plugins/cloud/models.py:49 xpack/plugins/cloud/models.py:119
@@ -204,7 +206,7 @@ msgstr "参数"
#: applications/models/remote_app.py:43
#: applications/templates/applications/remote_app_detail.html:77
-#: assets/models/asset.py:132 assets/models/base.py:34
+#: assets/models/asset.py:132 assets/models/base.py:35
#: assets/models/cluster.py:28 assets/models/cmd_filter.py:25
#: assets/models/cmd_filter.py:58 assets/models/group.py:21
#: assets/templates/assets/admin_user_detail.html:68
@@ -218,7 +220,7 @@ msgstr "参数"
#: perms/templates/perms/remote_app_permission_detail.html:90
#: users/models/user.py:104 users/serializers/v1.py:72
#: users/templates/users/user_detail.html:111
-#: xpack/plugins/change_auth_plan/models.py:103
+#: xpack/plugins/change_auth_plan/models.py:106
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113
#: xpack/plugins/cloud/models.py:55 xpack/plugins/cloud/models.py:127
msgid "Created by"
@@ -256,7 +258,7 @@ msgstr "创建日期"
#: applications/templates/applications/remote_app_detail.html:81
#: applications/templates/applications/remote_app_list.html:24
#: applications/templates/applications/user_remote_app_list.html:20
-#: assets/models/asset.py:134 assets/models/base.py:31
+#: assets/models/asset.py:134 assets/models/base.py:32
#: assets/models/cluster.py:29 assets/models/cmd_filter.py:22
#: assets/models/cmd_filter.py:55 assets/models/domain.py:21
#: assets/models/domain.py:53 assets/models/group.py:23
@@ -282,7 +284,7 @@ msgstr "创建日期"
#: users/templates/users/user_group_detail.html:67
#: users/templates/users/user_group_list.html:37
#: users/templates/users/user_profile.html:134
-#: xpack/plugins/change_auth_plan/models.py:99
+#: xpack/plugins/change_auth_plan/models.py:102
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:117
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:19
#: xpack/plugins/cloud/models.py:54 xpack/plugins/cloud/models.py:125
@@ -339,6 +341,7 @@ msgstr "远程应用"
#: xpack/plugins/cloud/templates/cloud/account_create_update.html:33
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:35
#: xpack/plugins/interface/templates/interface/interface.html:72
+#: xpack/plugins/vault/templates/vault/vault_create.html:45
msgid "Reset"
msgstr "重置"
@@ -365,7 +368,7 @@ msgstr "重置"
#: settings/templates/settings/security_setting.html:74
#: settings/templates/settings/terminal_setting.html:73
#: terminal/templates/terminal/command_list.html:103
-#: terminal/templates/terminal/session_list.html:126
+#: terminal/templates/terminal/session_list.html:127
#: terminal/templates/terminal/terminal_update.html:46
#: users/templates/users/_user.html:51
#: users/templates/users/forgot_password.html:42
@@ -376,6 +379,7 @@ msgstr "重置"
#: users/templates/users/user_pubkey_update.html:77
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:72
#: xpack/plugins/interface/templates/interface/interface.html:74
+#: xpack/plugins/vault/templates/vault/vault_create.html:46
msgid "Submit"
msgstr "提交"
@@ -407,6 +411,7 @@ msgstr "详情"
#: applications/templates/applications/remote_app_detail.html:21
#: applications/templates/applications/remote_app_list.html:56
+#: assets/templates/assets/_asset_user_list.html:60
#: assets/templates/assets/admin_user_detail.html:24
#: assets/templates/assets/admin_user_list.html:29
#: assets/templates/assets/admin_user_list.html:112
@@ -503,23 +508,21 @@ msgid "Download application loader"
msgstr "下载应用加载器"
#: applications/templates/applications/remote_app_list.html:12
-#: applications/views/remote_app.py:47
+#: applications/views/remote_app.py:49
msgid "Create RemoteApp"
msgstr "创建远程应用"
#: applications/templates/applications/remote_app_list.html:25
#: applications/templates/applications/user_remote_app_list.html:21
#: assets/models/cmd_filter.py:54
-#: assets/templates/assets/admin_user_assets.html:52
+#: assets/templates/assets/_asset_user_list.html:20
#: assets/templates/assets/admin_user_list.html:54
-#: assets/templates/assets/asset_asset_user_list.html:48
#: assets/templates/assets/asset_list.html:108
#: assets/templates/assets/cmd_filter_list.html:28
#: assets/templates/assets/cmd_filter_rule_list.html:63
#: assets/templates/assets/domain_gateway_list.html:73
#: assets/templates/assets/domain_list.html:29
#: assets/templates/assets/label_list.html:17
-#: assets/templates/assets/system_user_asset.html:54
#: assets/templates/assets/system_user_list.html:60
#: assets/templates/assets/user_asset_list.html:48 audits/models.py:38
#: audits/templates/audits/operate_log_list.html:41
@@ -552,44 +555,44 @@ msgstr "动作"
msgid "Connect"
msgstr "连接"
-#: applications/views/remote_app.py:31 applications/views/remote_app.py:46
-#: applications/views/remote_app.py:67 applications/views/remote_app.py:84
-#: assets/models/user.py:134
+#: applications/views/remote_app.py:32 applications/views/remote_app.py:48
+#: applications/views/remote_app.py:70 applications/views/remote_app.py:88
+#: assets/models/user.py:135
#: assets/templates/assets/_asset_group_bulk_update_modal.html:11
#: assets/templates/assets/system_user_asset.html:22
#: assets/templates/assets/system_user_detail.html:22
-#: assets/views/admin_user.py:29 assets/views/admin_user.py:47
-#: assets/views/admin_user.py:63 assets/views/admin_user.py:78
-#: assets/views/admin_user.py:102 assets/views/asset.py:52
-#: assets/views/asset.py:68 assets/views/asset.py:125 assets/views/asset.py:167
-#: assets/views/asset.py:194 assets/views/asset.py:219
-#: assets/views/cmd_filter.py:30 assets/views/cmd_filter.py:46
-#: assets/views/cmd_filter.py:62 assets/views/cmd_filter.py:78
-#: assets/views/cmd_filter.py:97 assets/views/cmd_filter.py:130
-#: assets/views/cmd_filter.py:163 assets/views/domain.py:29
-#: assets/views/domain.py:45 assets/views/domain.py:61
-#: assets/views/domain.py:74 assets/views/domain.py:98
-#: assets/views/domain.py:126 assets/views/domain.py:145
-#: assets/views/label.py:26 assets/views/label.py:43 assets/views/label.py:69
-#: assets/views/system_user.py:28 assets/views/system_user.py:44
-#: assets/views/system_user.py:60 assets/views/system_user.py:74
-#: templates/_nav.html:19 xpack/plugins/change_auth_plan/models.py:65
+#: assets/views/admin_user.py:30 assets/views/admin_user.py:49
+#: assets/views/admin_user.py:66 assets/views/admin_user.py:82
+#: assets/views/admin_user.py:107 assets/views/asset.py:53
+#: assets/views/asset.py:70 assets/views/asset.py:128 assets/views/asset.py:171
+#: assets/views/asset.py:199 assets/views/asset.py:225
+#: assets/views/cmd_filter.py:31 assets/views/cmd_filter.py:48
+#: assets/views/cmd_filter.py:65 assets/views/cmd_filter.py:82
+#: assets/views/cmd_filter.py:102 assets/views/cmd_filter.py:136
+#: assets/views/cmd_filter.py:170 assets/views/domain.py:30
+#: assets/views/domain.py:47 assets/views/domain.py:64
+#: assets/views/domain.py:78 assets/views/domain.py:104
+#: assets/views/domain.py:133 assets/views/domain.py:153
+#: assets/views/label.py:27 assets/views/label.py:45 assets/views/label.py:72
+#: assets/views/system_user.py:29 assets/views/system_user.py:46
+#: assets/views/system_user.py:63 assets/views/system_user.py:78
+#: templates/_nav.html:19 xpack/plugins/change_auth_plan/models.py:68
msgid "Assets"
msgstr "资产管理"
-#: applications/views/remote_app.py:32
+#: applications/views/remote_app.py:33
msgid "RemoteApp list"
msgstr "远程应用列表"
-#: applications/views/remote_app.py:68
+#: applications/views/remote_app.py:71
msgid "Update RemoteApp"
msgstr "更新远程应用"
-#: applications/views/remote_app.py:85
+#: applications/views/remote_app.py:89
msgid "RemoteApp detail"
msgstr "远程应用详情"
-#: applications/views/remote_app.py:96
+#: applications/views/remote_app.py:100
msgid "My RemoteApp"
msgstr "我的远程应用"
@@ -615,16 +618,16 @@ msgid "Test if the assets under the node are connectable: {}"
msgstr "测试节点下资产是否可连接: {}"
#: assets/forms/asset.py:45 assets/models/asset.py:103
-#: assets/models/user.py:133 assets/templates/assets/asset_detail.html:195
+#: assets/models/user.py:134 assets/templates/assets/asset_detail.html:195
#: assets/templates/assets/asset_detail.html:203
-#: assets/templates/assets/system_user_asset.html:95
+#: assets/templates/assets/system_user_asset.html:83
#: perms/models/asset_permission.py:38
-#: xpack/plugins/change_auth_plan/models.py:69
+#: xpack/plugins/change_auth_plan/models.py:72
msgid "Nodes"
msgstr "节点管理"
#: assets/forms/asset.py:48 assets/forms/asset.py:83 assets/models/asset.py:107
-#: assets/models/cluster.py:19 assets/models/user.py:91
+#: assets/models/cluster.py:19 assets/models/user.py:92
#: assets/templates/assets/asset_detail.html:81 templates/_nav.html:24
#: xpack/plugins/cloud/models.py:124
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:65
@@ -700,12 +703,12 @@ msgid "SSH gateway support proxy SSH,RDP,VNC"
msgstr "SSH网关,支持代理SSH,RDP和VNC"
#: assets/forms/domain.py:74 assets/forms/user.py:85 assets/forms/user.py:149
-#: assets/models/base.py:27
-#: assets/templates/assets/_asset_user_auth_modal.html:15
-#: assets/templates/assets/_asset_user_view_auth_modal.html:31
+#: assets/models/base.py:28
+#: assets/templates/assets/_asset_user_auth_update_modal.html:15
+#: assets/templates/assets/_asset_user_auth_view_modal.html:21
+#: assets/templates/assets/_asset_user_list.html:16
#: assets/templates/assets/admin_user_detail.html:60
#: assets/templates/assets/admin_user_list.html:48
-#: assets/templates/assets/asset_asset_user_list.html:44
#: assets/templates/assets/domain_gateway_list.html:71
#: assets/templates/assets/system_user_detail.html:62
#: assets/templates/assets/system_user_list.html:52 audits/models.py:94
@@ -721,8 +724,8 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC"
#: users/templates/users/user_list.html:36
#: users/templates/users/user_profile.html:47
#: xpack/plugins/change_auth_plan/forms.py:99
-#: xpack/plugins/change_auth_plan/models.py:60
-#: xpack/plugins/change_auth_plan/models.py:405
+#: xpack/plugins/change_auth_plan/models.py:63
+#: xpack/plugins/change_auth_plan/models.py:409
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:65
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:53
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:12
@@ -734,11 +737,11 @@ msgstr "用户名"
msgid "Password or private key passphrase"
msgstr "密码或密钥密码"
-#: assets/forms/user.py:26 assets/models/base.py:28
-#: assets/serializers/admin_user.py:20 assets/serializers/asset_user.py:19
-#: assets/serializers/system_user.py:16
-#: assets/templates/assets/_asset_user_auth_modal.html:21
-#: assets/templates/assets/_asset_user_view_auth_modal.html:37
+#: assets/forms/user.py:26 assets/models/base.py:29
+#: assets/serializers/admin_user.py:20 assets/serializers/asset_user.py:33
+#: assets/serializers/asset_user.py:86 assets/serializers/system_user.py:16
+#: assets/templates/assets/_asset_user_auth_update_modal.html:21
+#: assets/templates/assets/_asset_user_auth_view_modal.html:27
#: authentication/forms.py:13
#: authentication/templates/authentication/login.html:67
#: authentication/templates/authentication/new_login.html:93
@@ -749,12 +752,14 @@ msgstr "密码或密钥密码"
#: users/templates/users/user_profile_update.html:40
#: users/templates/users/user_pubkey_update.html:40
#: users/templates/users/user_update.html:20
-#: xpack/plugins/change_auth_plan/models.py:90
-#: xpack/plugins/change_auth_plan/models.py:260
+#: xpack/plugins/change_auth_plan/models.py:93
+#: xpack/plugins/change_auth_plan/models.py:264
msgid "Password"
msgstr "密码"
-#: assets/forms/user.py:29 assets/serializers/asset_user.py:27
+#: assets/forms/user.py:29 assets/serializers/asset_user.py:41
+#: assets/serializers/asset_user.py:94
+#: assets/templates/assets/_asset_user_auth_update_modal.html:27
#: users/models/user.py:90
msgid "Private key"
msgstr "ssh私钥"
@@ -772,7 +777,7 @@ msgid "* Automatic login mode must fill in the username."
msgstr "自动登录模式,必须填写用户名"
#: assets/forms/user.py:151 assets/models/cmd_filter.py:31
-#: assets/models/user.py:141 assets/templates/assets/_system_user.html:66
+#: assets/models/user.py:142 assets/templates/assets/_system_user.html:66
#: assets/templates/assets/system_user_detail.html:165
msgid "Command filter"
msgstr "命令过滤器"
@@ -800,21 +805,20 @@ msgid "Use comma split multi command, ex: /bin/whoami,/bin/ifconfig"
msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig"
#: assets/models/asset.py:73 assets/models/asset.py:98
-#: assets/models/domain.py:50 assets/templates/assets/admin_user_assets.html:50
+#: assets/models/domain.py:50
#: assets/templates/assets/domain_gateway_list.html:69
-#: assets/templates/assets/system_user_asset.html:52
#: assets/templates/assets/user_asset_list.html:168
#: settings/templates/settings/replay_storage_create.html:59
msgid "Port"
msgstr "端口"
#: assets/models/asset.py:93 assets/models/domain.py:49
+#: assets/serializers/asset_user.py:28
#: assets/templates/assets/_asset_list_modal.html:46
-#: assets/templates/assets/admin_user_assets.html:49
+#: assets/templates/assets/_asset_user_list.html:15
#: assets/templates/assets/asset_detail.html:64
#: assets/templates/assets/asset_list.html:105
#: assets/templates/assets/domain_gateway_list.html:68
-#: assets/templates/assets/system_user_asset.html:51
#: assets/templates/assets/user_asset_list.html:45
#: assets/templates/assets/user_asset_list.html:167
#: audits/templates/audits/login_log_list.html:54
@@ -825,13 +829,13 @@ msgstr "端口"
msgid "IP"
msgstr "IP"
-#: assets/models/asset.py:94 assets/templates/assets/_asset_list_modal.html:45
-#: assets/templates/assets/_asset_user_auth_modal.html:9
-#: assets/templates/assets/_asset_user_view_auth_modal.html:25
-#: assets/templates/assets/admin_user_assets.html:48
+#: assets/models/asset.py:94 assets/serializers/asset_user.py:27
+#: assets/templates/assets/_asset_list_modal.html:45
+#: assets/templates/assets/_asset_user_auth_update_modal.html:9
+#: assets/templates/assets/_asset_user_auth_view_modal.html:15
+#: assets/templates/assets/_asset_user_list.html:14
#: assets/templates/assets/asset_detail.html:60
#: assets/templates/assets/asset_list.html:104
-#: assets/templates/assets/system_user_asset.html:50
#: assets/templates/assets/user_asset_list.html:44
#: assets/templates/assets/user_asset_list.html:166
#: perms/templates/perms/asset_permission_asset.html:54
@@ -843,7 +847,7 @@ msgid "Hostname"
msgstr "主机名"
#: assets/models/asset.py:97 assets/models/asset.py:100
-#: assets/models/domain.py:51 assets/models/user.py:136
+#: assets/models/domain.py:51 assets/models/user.py:137
#: assets/templates/assets/asset_detail.html:72
#: assets/templates/assets/domain_gateway_list.html:70
#: assets/templates/assets/system_user_detail.html:70
@@ -936,26 +940,23 @@ msgstr "主机名原始"
msgid "Labels"
msgstr "标签管理"
-#: assets/models/asset.py:140 assets/models/base.py:38
+#: assets/models/asset.py:140 assets/models/base.py:39
#: assets/serializers/admin_user.py:22 assets/serializers/system_user.py:19
#: assets/templates/assets/admin_user_list.html:51
#: assets/templates/assets/system_user_list.html:57
msgid "Unreachable"
msgstr "不可达"
-#: assets/models/asset.py:141 assets/models/base.py:39
+#: assets/models/asset.py:141 assets/models/base.py:40
#: assets/serializers/admin_user.py:24 assets/serializers/system_user.py:27
-#: assets/templates/assets/admin_user_assets.html:51
#: assets/templates/assets/admin_user_list.html:50
-#: assets/templates/assets/asset_asset_user_list.html:46
#: assets/templates/assets/asset_list.html:107
-#: assets/templates/assets/system_user_asset.html:53
#: assets/templates/assets/system_user_list.html:56
#: users/templates/users/user_group_granted_asset.html:47
msgid "Reachable"
msgstr "可连接"
-#: assets/models/asset.py:142 assets/models/base.py:40
+#: assets/models/asset.py:142 assets/models/base.py:41
#: authentication/utils.py:9 xpack/plugins/license/models.py:78
msgid "Unknown"
msgstr "未知"
@@ -964,23 +965,25 @@ msgstr "未知"
msgid "Latest version"
msgstr "最新版本"
-#: assets/models/authbook.py:29 ops/templates/ops/adhoc_history.html:58
+#: assets/models/authbook.py:29
+#: assets/templates/assets/_asset_user_list.html:17
+#: ops/templates/ops/adhoc_history.html:58
#: ops/templates/ops/adhoc_history_detail.html:57
#: ops/templates/ops/task_adhoc.html:58 ops/templates/ops/task_history.html:64
msgid "Version"
msgstr "版本"
-#: assets/models/authbook.py:34
+#: assets/models/authbook.py:37
msgid "AuthBook"
msgstr ""
-#: assets/models/base.py:29 xpack/plugins/change_auth_plan/models.py:94
-#: xpack/plugins/change_auth_plan/models.py:267
+#: assets/models/base.py:30 xpack/plugins/change_auth_plan/models.py:97
+#: xpack/plugins/change_auth_plan/models.py:271
msgid "SSH private key"
msgstr "ssh密钥"
-#: assets/models/base.py:30 xpack/plugins/change_auth_plan/models.py:97
-#: xpack/plugins/change_auth_plan/models.py:263
+#: assets/models/base.py:31 xpack/plugins/change_auth_plan/models.py:100
+#: xpack/plugins/change_auth_plan/models.py:267
msgid "SSH public key"
msgstr "ssh公钥"
@@ -1079,7 +1082,7 @@ msgstr "过滤器"
msgid "Type"
msgstr "类型"
-#: assets/models/cmd_filter.py:51 assets/models/user.py:135
+#: assets/models/cmd_filter.py:51 assets/models/user.py:136
#: assets/templates/assets/cmd_filter_rule_list.html:60
msgid "Priority"
msgstr "优先级"
@@ -1140,7 +1143,7 @@ msgstr "默认资产组"
#: terminal/templates/terminal/session_list.html:71 users/forms.py:301
#: users/models/user.py:37 users/models/user.py:473 users/serializers/v1.py:61
#: users/templates/users/user_group_detail.html:78
-#: users/templates/users/user_group_list.html:36 users/views/user.py:399
+#: users/templates/users/user_group_list.html:36 users/views/user.py:406
#: xpack/plugins/orgs/forms.py:26
#: xpack/plugins/orgs/templates/orgs/org_detail.html:113
#: xpack/plugins/orgs/templates/orgs/org_list.html:14
@@ -1164,29 +1167,29 @@ msgstr "键"
msgid "New node"
msgstr "新节点"
-#: assets/models/user.py:129
+#: assets/models/user.py:130
msgid "Automatic login"
msgstr "自动登录"
-#: assets/models/user.py:130
+#: assets/models/user.py:131
msgid "Manually login"
msgstr "手动登录"
-#: assets/models/user.py:137 assets/templates/assets/_system_user.html:59
+#: assets/models/user.py:138 assets/templates/assets/_system_user.html:59
#: assets/templates/assets/system_user_detail.html:122
#: assets/templates/assets/system_user_update.html:10
msgid "Auto push"
msgstr "自动推送"
-#: assets/models/user.py:138 assets/templates/assets/system_user_detail.html:74
+#: assets/models/user.py:139 assets/templates/assets/system_user_detail.html:74
msgid "Sudo"
msgstr "Sudo"
-#: assets/models/user.py:139 assets/templates/assets/system_user_detail.html:79
+#: assets/models/user.py:140 assets/templates/assets/system_user_detail.html:79
msgid "Shell"
msgstr "Shell"
-#: assets/models/user.py:140 assets/templates/assets/system_user_detail.html:66
+#: assets/models/user.py:141 assets/templates/assets/system_user_detail.html:66
#: assets/templates/assets/system_user_list.html:54
msgid "Login mode"
msgstr "登录模式"
@@ -1197,7 +1200,6 @@ msgid "%(value)s is not an even number"
msgstr "%(value)s is not an even number"
#: assets/serializers/admin_user.py:38
-#: assets/templates/assets/asset_asset_user_list.html:47
#: assets/templates/assets/cmd_filter_detail.html:73
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:109
msgid "Date updated"
@@ -1207,7 +1209,8 @@ msgstr "更新日期"
msgid "Hardware info"
msgstr "硬件信息"
-#: assets/serializers/asset.py:53
+#: assets/serializers/asset.py:53 assets/serializers/asset_user.py:29
+#: assets/templates/assets/_asset_user_list.html:18
msgid "Connectivity"
msgstr "连接"
@@ -1219,8 +1222,9 @@ msgstr "组织名"
msgid "Protocol duplicate: {}"
msgstr "协议重复: {}"
-#: assets/serializers/asset_user.py:23 users/forms.py:248
-#: users/models/user.py:93 users/templates/users/first_login.html:42
+#: assets/serializers/asset_user.py:37 assets/serializers/asset_user.py:90
+#: users/forms.py:248 users/models/user.py:93
+#: users/templates/users/first_login.html:42
#: users/templates/users/user_password_update.html:46
#: users/templates/users/user_profile.html:68
#: users/templates/users/user_profile_update.html:43
@@ -1228,6 +1232,14 @@ msgstr "协议重复: {}"
msgid "Public key"
msgstr "ssh公钥"
+#: assets/serializers/asset_user.py:43
+msgid "Backend"
+msgstr ""
+
+#: assets/serializers/asset_user.py:65
+msgid "private key invalid"
+msgstr "密钥不合法"
+
#: assets/serializers/system_user.py:22
msgid "Unreachable assets"
msgstr "不可达资产"
@@ -1297,6 +1309,7 @@ msgid "Test system user connectivity period: {}"
msgstr "定期测试系统用户可连接性: {}"
#: assets/tasks.py:469 assets/tasks.py:555
+#: xpack/plugins/change_auth_plan/models.py:522
msgid "The asset {} system platform {} does not support run Ansible tasks"
msgstr "资产 {} 系统平台 {} 不支持运行 Ansible 任务"
@@ -1318,7 +1331,7 @@ msgstr "推送系统用户到入资产: {}"
msgid "Push system users to asset: {} => {}"
msgstr "推送系统用户到入资产: {} => {}"
-#: assets/tasks.py:600
+#: assets/tasks.py:612
msgid "Test asset user connectivity: {}"
msgstr "测试资产用户可连接性: {}"
@@ -1327,7 +1340,7 @@ msgid "Import admin user"
msgstr "导入管理用户"
#: assets/templates/assets/_admin_user_update_modal.html:4
-#: assets/views/admin_user.py:64
+#: assets/views/admin_user.py:67
msgid "Update admin user"
msgstr "更新管理用户"
@@ -1363,7 +1376,7 @@ msgstr "启用MFA"
msgid "Import assets"
msgstr "导入资产"
-#: assets/templates/assets/_asset_list_modal.html:7 assets/views/asset.py:53
+#: assets/templates/assets/_asset_list_modal.html:7 assets/views/asset.py:54
#: templates/_nav.html:22 xpack/plugins/change_auth_plan/views.py:110
msgid "Asset list"
msgstr "资产列表"
@@ -1372,70 +1385,74 @@ msgstr "资产列表"
msgid "Update assets"
msgstr "更新资产"
-#: assets/templates/assets/_asset_user_auth_modal.html:4
+#: assets/templates/assets/_asset_user_auth_update_modal.html:4
msgid "Update asset user auth"
msgstr "更新资产用户认证信息"
-#: assets/templates/assets/_asset_user_auth_modal.html:23
+#: assets/templates/assets/_asset_user_auth_update_modal.html:23
#: xpack/plugins/change_auth_plan/forms.py:101
msgid "Please input password"
msgstr "请输入密码"
-#: assets/templates/assets/_asset_user_view_auth_modal.html:5
+#: assets/templates/assets/_asset_user_auth_update_modal.html:68
+#: assets/templates/assets/asset_detail.html:312
+#: users/templates/users/user_detail.html:307
+#: users/templates/users/user_detail.html:334
+#: xpack/plugins/interface/views.py:34
+msgid "Update successfully!"
+msgstr "更新成功"
+
+#: assets/templates/assets/_asset_user_auth_view_modal.html:5
msgid "Asset user auth"
msgstr "资产用户信息"
-#: assets/templates/assets/_asset_user_view_auth_modal.html:14
-#: audits/models.py:99 audits/templates/audits/login_log_list.html:56
-#: users/forms.py:160 users/models/user.py:85
-#: users/templates/users/first_login.html:45
-msgid "MFA"
-msgstr "MFA"
-
-#: assets/templates/assets/_asset_user_view_auth_modal.html:17
-msgid "Need otp auth for view auth"
-msgstr "需要二次认证来查看账号信息"
-
-#: assets/templates/assets/_asset_user_view_auth_modal.html:20
-#: assets/templates/assets/admin_user_detail.html:100
-#: assets/templates/assets/asset_detail.html:212
-#: assets/templates/assets/asset_list.html:682
-#: assets/templates/assets/cmd_filter_detail.html:106
-#: assets/templates/assets/system_user_asset.html:112
-#: assets/templates/assets/system_user_detail.html:182
-#: assets/templates/assets/system_user_list.html:170
-#: settings/templates/settings/terminal_setting.html:168
-#: templates/_modal.html:23 terminal/templates/terminal/session_detail.html:108
-#: users/templates/users/user_detail.html:388
-#: users/templates/users/user_detail.html:414
-#: users/templates/users/user_detail.html:437
-#: users/templates/users/user_detail.html:482
-#: users/templates/users/user_group_create_update.html:32
-#: users/templates/users/user_group_list.html:119
-#: users/templates/users/user_list.html:257
-#: users/templates/users/user_profile.html:238
-#: xpack/plugins/cloud/templates/cloud/account_create_update.html:34
-#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:36
-#: xpack/plugins/interface/templates/interface/interface.html:103
-#: xpack/plugins/orgs/templates/orgs/org_create_update.html:33
-msgid "Confirm"
-msgstr "确认"
-
-#: assets/templates/assets/_asset_user_view_auth_modal.html:63
+#: assets/templates/assets/_asset_user_auth_view_modal.html:55
msgid "Copy success"
msgstr "复制成功"
-#: assets/templates/assets/_asset_user_view_auth_modal.html:79
+#: assets/templates/assets/_asset_user_auth_view_modal.html:71
+#: authentication/templates/authentication/_mfa_confirm_modal.html:39
msgid "Get auth info error"
msgstr "获取认证信息错误"
-#: assets/templates/assets/_asset_user_view_auth_modal.html:139
+#: assets/templates/assets/_asset_user_auth_view_modal.html:117
#: assets/templates/assets/_user_asset_detail_modal.html:23
+#: authentication/templates/authentication/_mfa_confirm_modal.html:79
#: settings/templates/settings/_ldap_list_users_modal.html:99
#: templates/_modal.html:22
msgid "Close"
msgstr "关闭"
+#: assets/templates/assets/_asset_user_list.html:19
+#: audits/templates/audits/operate_log_list.html:71
+#: audits/templates/audits/password_change_log_list.html:53
+#: ops/templates/ops/task_adhoc.html:63
+#: terminal/templates/terminal/command_list.html:76
+#: terminal/templates/terminal/session_detail.html:50
+msgid "Datetime"
+msgstr "日期"
+
+#: assets/templates/assets/_asset_user_list.html:59
+#, fuzzy
+#| msgid "View auth"
+msgid "View"
+msgstr "查看认证"
+
+#: assets/templates/assets/_asset_user_list.html:61
+#: assets/templates/assets/admin_user_assets.html:61
+#: assets/templates/assets/asset_asset_user_list.html:57
+#: assets/templates/assets/asset_detail.html:183
+#: assets/templates/assets/system_user_asset.html:63
+#: assets/templates/assets/system_user_detail.html:151
+msgid "Test"
+msgstr "测试"
+
+#: assets/templates/assets/_asset_user_list.html:62
+#: assets/templates/assets/system_user_asset.html:72
+#: assets/templates/assets/system_user_detail.html:142
+msgid "Push"
+msgstr "推送"
+
#: assets/templates/assets/_gateway_test_modal.html:4
msgid "Test gateway test connection"
msgstr "测试连接网关"
@@ -1483,13 +1500,13 @@ msgid "Import system user"
msgstr "导入系统用户"
#: assets/templates/assets/_system_user_update_modal.html:4
-#: assets/views/system_user.py:61
+#: assets/views/system_user.py:64
msgid "Update system user"
msgstr "更新系统用户"
#: assets/templates/assets/_user_asset_detail_modal.html:11
#: assets/templates/assets/asset_asset_user_list.html:13
-#: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:220
+#: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:226
msgid "Asset detail"
msgstr "资产详情"
@@ -1504,59 +1521,20 @@ msgstr "资产列表"
msgid "Asset list of "
msgstr "资产列表"
-#: assets/templates/assets/admin_user_assets.html:64
-#: assets/templates/assets/system_user_asset.html:66
+#: assets/templates/assets/admin_user_assets.html:52
+#: assets/templates/assets/system_user_asset.html:54
#: assets/templates/assets/system_user_detail.html:116
#: perms/templates/perms/asset_permission_detail.html:114
#: perms/templates/perms/remote_app_permission_detail.html:106
msgid "Quick update"
msgstr "快速更新"
-#: assets/templates/assets/admin_user_assets.html:70
-#: assets/templates/assets/asset_asset_user_list.html:67
+#: assets/templates/assets/admin_user_assets.html:58
+#: assets/templates/assets/asset_asset_user_list.html:54
#: assets/templates/assets/asset_detail.html:180
msgid "Test connective"
msgstr "测试可连接性"
-#: assets/templates/assets/admin_user_assets.html:73
-#: assets/templates/assets/admin_user_assets.html:118
-#: assets/templates/assets/asset_asset_user_list.html:70
-#: assets/templates/assets/asset_asset_user_list.html:119
-#: assets/templates/assets/asset_detail.html:183
-#: assets/templates/assets/system_user_asset.html:75
-#: assets/templates/assets/system_user_asset.html:166
-#: assets/templates/assets/system_user_detail.html:151
-msgid "Test"
-msgstr "测试"
-
-#: assets/templates/assets/admin_user_assets.html:116
-#: assets/templates/assets/asset_asset_user_list.html:117
-#: assets/templates/assets/system_user_asset.html:169
-msgid "Update auth"
-msgstr "更新认证"
-
-#: assets/templates/assets/admin_user_assets.html:117
-#: assets/templates/assets/asset_asset_user_list.html:118
-#: assets/templates/assets/system_user_asset.html:167
-msgid "View auth"
-msgstr "查看认证"
-
-#: assets/templates/assets/admin_user_assets.html:196
-#: assets/templates/assets/asset_asset_user_list.html:162
-#: assets/templates/assets/asset_detail.html:312
-#: assets/templates/assets/system_user_asset.html:353
-#: users/templates/users/user_detail.html:307
-#: users/templates/users/user_detail.html:334
-#: xpack/plugins/interface/views.py:34
-msgid "Update successfully!"
-msgstr "更新成功"
-
-#: assets/templates/assets/admin_user_assets.html:199
-#: assets/templates/assets/asset_asset_user_list.html:165
-#: assets/templates/assets/system_user_asset.html:356
-msgid "Update failed!"
-msgstr "更新失败"
-
#: assets/templates/assets/admin_user_detail.html:83
msgid "Replace node assets admin user with this"
msgstr "替换资产的管理员"
@@ -1568,6 +1546,31 @@ msgstr "替换资产的管理员"
msgid "Select nodes"
msgstr "选择节点"
+#: assets/templates/assets/admin_user_detail.html:100
+#: assets/templates/assets/asset_detail.html:212
+#: assets/templates/assets/asset_list.html:682
+#: assets/templates/assets/cmd_filter_detail.html:106
+#: assets/templates/assets/system_user_asset.html:100
+#: assets/templates/assets/system_user_detail.html:182
+#: assets/templates/assets/system_user_list.html:170
+#: authentication/templates/authentication/_mfa_confirm_modal.html:20
+#: settings/templates/settings/terminal_setting.html:168
+#: templates/_modal.html:23 terminal/templates/terminal/session_detail.html:108
+#: users/templates/users/user_detail.html:388
+#: users/templates/users/user_detail.html:414
+#: users/templates/users/user_detail.html:437
+#: users/templates/users/user_detail.html:482
+#: users/templates/users/user_group_create_update.html:32
+#: users/templates/users/user_group_list.html:119
+#: users/templates/users/user_list.html:257
+#: users/templates/users/user_profile.html:238
+#: xpack/plugins/cloud/templates/cloud/account_create_update.html:34
+#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:36
+#: xpack/plugins/interface/templates/interface/interface.html:103
+#: xpack/plugins/orgs/templates/orgs/org_create_update.html:33
+msgid "Confirm"
+msgstr "确认"
+
#: assets/templates/assets/admin_user_list.html:7
msgid ""
"Admin users are asset (charged server) on the root, or have NOPASSWD: ALL "
@@ -1591,6 +1594,7 @@ msgstr "Windows或其它硬件可以随意设置一个"
#: audits/templates/audits/login_log_list.html:85
#: users/templates/users/user_group_list.html:10
#: users/templates/users/user_list.html:10
+#: xpack/plugins/vault/templates/vault/vault.html:55
msgid "Export"
msgstr "导出"
@@ -1601,11 +1605,12 @@ msgstr "导出"
#: users/templates/users/user_group_list.html:15
#: users/templates/users/user_list.html:15
#: xpack/plugins/license/templates/license/license_detail.html:110
+#: xpack/plugins/vault/templates/vault/vault.html:60
msgid "Import"
msgstr "导入"
#: assets/templates/assets/admin_user_list.html:39
-#: assets/views/admin_user.py:48
+#: assets/views/admin_user.py:50
msgid "Create admin user"
msgstr "创建管理用户"
@@ -1626,11 +1631,12 @@ msgstr "比例"
#: users/templates/users/user_group_list.html:194
#: users/templates/users/user_list.html:158
#: users/templates/users/user_list.html:190
+#: xpack/plugins/vault/templates/vault/vault.html:223
msgid "Please select file"
msgstr "选择文件"
#: assets/templates/assets/asset_asset_user_list.html:16
-#: assets/templates/assets/asset_detail.html:23 assets/views/asset.py:69
+#: assets/templates/assets/asset_detail.html:23 assets/views/asset.py:71
msgid "Asset user list"
msgstr "资产用户列表"
@@ -1638,11 +1644,7 @@ msgstr "资产用户列表"
msgid "Asset users of"
msgstr "资产用户"
-#: assets/templates/assets/asset_asset_user_list.html:45
-msgid "Password version"
-msgstr "密码版本"
-
-#: assets/templates/assets/asset_asset_user_list.html:60
+#: assets/templates/assets/asset_asset_user_list.html:47
#: assets/templates/assets/asset_detail.html:149
#: terminal/templates/terminal/session_detail.html:81
#: users/templates/users/user_detail.html:138
@@ -1713,7 +1715,7 @@ msgstr ""
"左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织的,"
"右侧是属于该节点下的资产"
-#: assets/templates/assets/asset_list.html:69 assets/views/asset.py:126
+#: assets/templates/assets/asset_list.html:69 assets/views/asset.py:129
msgid "Create asset"
msgstr "创建资产"
@@ -1888,12 +1890,12 @@ msgid "else match next rule, if none matched, allowed"
msgstr "否则就匹配下一个规则,如果最后没有匹配到规则,则允许执行"
#: assets/templates/assets/cmd_filter_list.html:16
-#: assets/views/cmd_filter.py:47
+#: assets/views/cmd_filter.py:49
msgid "Create command filter"
msgstr "创建命令过滤器"
#: assets/templates/assets/cmd_filter_rule_list.html:33
-#: assets/views/cmd_filter.py:98
+#: assets/views/cmd_filter.py:103
msgid "Command filter rule list"
msgstr "命令过滤器规则列表"
@@ -1920,7 +1922,7 @@ msgid "Gateway list"
msgstr "网关列表"
#: assets/templates/assets/domain_gateway_list.html:56
-#: assets/views/domain.py:127
+#: assets/views/domain.py:134
msgid "Create gateway"
msgstr "创建网关"
@@ -1952,11 +1954,11 @@ msgstr ""
msgid "JMS => Domain gateway => Target assets"
msgstr "JMS => 网域网关 => 目标资产"
-#: assets/templates/assets/domain_list.html:17 assets/views/domain.py:46
+#: assets/templates/assets/domain_list.html:17 assets/views/domain.py:48
msgid "Create domain"
msgstr "创建网域"
-#: assets/templates/assets/label_list.html:6 assets/views/label.py:44
+#: assets/templates/assets/label_list.html:6 assets/views/label.py:46
msgid "Create label"
msgstr "创建标签"
@@ -1964,23 +1966,17 @@ msgstr "创建标签"
msgid "Assets of "
msgstr "资产"
-#: assets/templates/assets/system_user_asset.html:72
+#: assets/templates/assets/system_user_asset.html:60
#: assets/templates/assets/system_user_detail.html:148
msgid "Test assets connective"
msgstr "测试资产可连接性"
-#: assets/templates/assets/system_user_asset.html:81
+#: assets/templates/assets/system_user_asset.html:69
#: assets/templates/assets/system_user_detail.html:139
msgid "Push system user now"
msgstr "立刻推送系统"
-#: assets/templates/assets/system_user_asset.html:84
-#: assets/templates/assets/system_user_asset.html:164
-#: assets/templates/assets/system_user_detail.html:142
-msgid "Push"
-msgstr "推送"
-
-#: assets/templates/assets/system_user_asset.html:103
+#: assets/templates/assets/system_user_asset.html:91
msgid "Add to node"
msgstr "添加到节点"
@@ -2025,7 +2021,7 @@ msgstr ""
"资产中,如果资产(交换机)不支持ansible, 请手动填写账号密码。"
#: assets/templates/assets/system_user_list.html:43
-#: assets/views/system_user.py:45
+#: assets/views/system_user.py:47
msgid "Create system user"
msgstr "创建系统用户"
@@ -2046,99 +2042,99 @@ msgstr "删除系统用户"
msgid "System Users Deleting failed."
msgstr "系统用户删除失败"
-#: assets/views/admin_user.py:30
+#: assets/views/admin_user.py:31
msgid "Admin user list"
msgstr "管理用户列表"
-#: assets/views/admin_user.py:79 assets/views/admin_user.py:103
+#: assets/views/admin_user.py:83 assets/views/admin_user.py:108
msgid "Admin user detail"
msgstr "管理用户详情"
-#: assets/views/asset.py:80 templates/_nav_user.html:4
+#: assets/views/asset.py:82 templates/_nav_user.html:4
msgid "My assets"
msgstr "我的资产"
-#: assets/views/asset.py:141
+#: assets/views/asset.py:144
msgid "Bulk update asset success"
msgstr "批量更新资产成功"
-#: assets/views/asset.py:168
+#: assets/views/asset.py:172
msgid "Bulk update asset"
msgstr "批量更新资产"
-#: assets/views/asset.py:195
+#: assets/views/asset.py:200
msgid "Update asset"
msgstr "更新资产"
-#: assets/views/asset.py:337
+#: assets/views/asset.py:344
msgid "already exists"
msgstr "已经存在"
-#: assets/views/cmd_filter.py:31
+#: assets/views/cmd_filter.py:32
msgid "Command filter list"
msgstr "命令过滤器列表"
-#: assets/views/cmd_filter.py:63
+#: assets/views/cmd_filter.py:66
msgid "Update command filter"
msgstr "更新命令过滤器"
-#: assets/views/cmd_filter.py:79
+#: assets/views/cmd_filter.py:83
msgid "Command filter detail"
msgstr "命令过滤器详情"
-#: assets/views/cmd_filter.py:131
+#: assets/views/cmd_filter.py:137
msgid "Create command filter rule"
msgstr "创建命令过滤器规则"
-#: assets/views/cmd_filter.py:164
+#: assets/views/cmd_filter.py:171
msgid "Update command filter rule"
msgstr "更新命令过滤器规则"
-#: assets/views/domain.py:30 templates/_nav.html:23
+#: assets/views/domain.py:31 templates/_nav.html:23
msgid "Domain list"
msgstr "网域列表"
-#: assets/views/domain.py:62
+#: assets/views/domain.py:65
msgid "Update domain"
msgstr "更新网域"
-#: assets/views/domain.py:75
+#: assets/views/domain.py:79
msgid "Domain detail"
msgstr "网域详情"
-#: assets/views/domain.py:99
+#: assets/views/domain.py:105
msgid "Domain gateway list"
msgstr "域网关列表"
-#: assets/views/domain.py:146
+#: assets/views/domain.py:154
msgid "Update gateway"
msgstr "创建网关"
-#: assets/views/label.py:27
+#: assets/views/label.py:28
msgid "Label list"
msgstr "标签列表"
-#: assets/views/label.py:53
+#: assets/views/label.py:55
msgid "Tips: Avoid using label names reserved internally: {}"
msgstr "提示: 请避免使用内部预留标签名: {}"
-#: assets/views/label.py:70
+#: assets/views/label.py:73
msgid "Update label"
msgstr "更新标签"
-#: assets/views/system_user.py:29
+#: assets/views/system_user.py:30
msgid "System user list"
msgstr "系统用户列表"
-#: assets/views/system_user.py:75
+#: assets/views/system_user.py:79
msgid "System user detail"
msgstr "系统用户详情"
-#: assets/views/system_user.py:96
+#: assets/views/system_user.py:102
msgid "assets"
msgstr "资产管理"
-#: assets/views/system_user.py:97
+#: assets/views/system_user.py:103
msgid "System user asset"
msgstr "系统用户资产"
@@ -2170,7 +2166,7 @@ msgstr "文件名"
msgid "Success"
msgstr "成功"
-#: audits/models.py:32
+#: audits/models.py:32 xpack/plugins/vault/templates/vault/vault.html:46
msgid "Create"
msgstr "创建"
@@ -2237,8 +2233,15 @@ msgstr "登录城市"
msgid "User agent"
msgstr "Agent"
+#: audits/models.py:99 audits/templates/audits/login_log_list.html:56
+#: authentication/templates/authentication/_mfa_confirm_modal.html:14
+#: users/forms.py:160 users/models/user.py:85
+#: users/templates/users/first_login.html:45
+msgid "MFA"
+msgstr "MFA"
+
#: audits/models.py:100 audits/templates/audits/login_log_list.html:57
-#: xpack/plugins/change_auth_plan/models.py:413
+#: xpack/plugins/change_auth_plan/models.py:417
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:15
#: xpack/plugins/cloud/models.py:172
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:69
@@ -2264,8 +2267,8 @@ msgstr "登录日期"
#: perms/templates/perms/asset_permission_detail.html:86
#: perms/templates/perms/remote_app_permission_detail.html:78
#: terminal/models.py:165 terminal/templates/terminal/session_list.html:78
-#: xpack/plugins/change_auth_plan/models.py:246
-#: xpack/plugins/change_auth_plan/models.py:416
+#: xpack/plugins/change_auth_plan/models.py:250
+#: xpack/plugins/change_auth_plan/models.py:420
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:59
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:17
msgid "Date start"
@@ -2315,14 +2318,6 @@ msgstr "城市"
msgid "Date"
msgstr "日期"
-#: audits/templates/audits/operate_log_list.html:71
-#: audits/templates/audits/password_change_log_list.html:53
-#: ops/templates/ops/task_adhoc.html:63
-#: terminal/templates/terminal/command_list.html:76
-#: terminal/templates/terminal/session_detail.html:50
-msgid "Datetime"
-msgstr "日期"
-
#: audits/views.py:86 audits/views.py:130 audits/views.py:167
#: audits/views.py:212 audits/views.py:244 templates/_nav.html:87
#: templates/_nav_audits.html:22
@@ -2440,6 +2435,18 @@ msgstr "MFA 验证码"
msgid "Private Token"
msgstr "ssh密钥"
+#: authentication/templates/authentication/_mfa_confirm_modal.html:5
+msgid "MFA confirm"
+msgstr "MFA确认"
+
+#: authentication/templates/authentication/_mfa_confirm_modal.html:17
+msgid "Need otp auth for view auth"
+msgstr "需要二次认证来查看账号信息"
+
+#: authentication/templates/authentication/_mfa_confirm_modal.html:51
+msgid "Code error"
+msgstr "代码错误"
+
#: authentication/templates/authentication/login.html:27
#: authentication/templates/authentication/login_otp.html:27
#: users/templates/users/reset_password.html:25
@@ -2560,8 +2567,8 @@ msgstr "欢迎回来,请输入用户名和密码登录"
msgid "Please enable cookies and try again."
msgstr "设置你的浏览器支持cookie"
-#: authentication/views/login.py:172 users/views/user.py:545
-#: users/views/user.py:570
+#: authentication/views/login.py:172 users/views/user.py:552
+#: users/views/user.py:577
msgid "MFA code invalid, or ntp sync server time"
msgstr "MFA验证码不正确,或者服务器端时间不对"
@@ -2648,7 +2655,7 @@ msgstr "不能包含特殊字符"
msgid "This field must be unique."
msgstr "字段必须唯一"
-#: jumpserver/views.py:185
+#: jumpserver/views.py:187
msgid ""
"Luna is a separately deployed program, you need to deploy Luna, coco, "
"configure nginx for url distribution,
If you see this page, "
@@ -2741,8 +2748,8 @@ msgstr "完成时间"
#: ops/models/adhoc.py:327 ops/templates/ops/adhoc_history.html:57
#: ops/templates/ops/task_history.html:63 ops/templates/ops/task_list.html:33
-#: xpack/plugins/change_auth_plan/models.py:249
-#: xpack/plugins/change_auth_plan/models.py:419
+#: xpack/plugins/change_auth_plan/models.py:253
+#: xpack/plugins/change_auth_plan/models.py:423
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:58
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:16
msgid "Time"
@@ -2792,7 +2799,7 @@ msgid "Version detail"
msgstr "版本详情"
#: ops/templates/ops/adhoc_detail.html:22
-#: ops/templates/ops/adhoc_history.html:22 ops/views/adhoc.py:122
+#: ops/templates/ops/adhoc_history.html:22 ops/views/adhoc.py:128
msgid "Version run history"
msgstr "执行历史"
@@ -2851,7 +2858,7 @@ msgstr "执行历史"
msgid "F/S/T"
msgstr "失败/成功/总"
-#: ops/templates/ops/adhoc_history_detail.html:19 ops/views/adhoc.py:135
+#: ops/templates/ops/adhoc_history_detail.html:19 ops/views/adhoc.py:142
msgid "Run history detail"
msgstr "执行历史详情"
@@ -2927,12 +2934,12 @@ msgid "Finished"
msgstr "结束"
#: 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:70
+#: ops/templates/ops/task_history.html:19 ops/views/adhoc.py:72
msgid "Task detail"
msgstr "任务详情"
#: 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:83
+#: ops/templates/ops/task_history.html:22 ops/views/adhoc.py:86
msgid "Task versions"
msgstr "任务各版本"
@@ -2977,17 +2984,17 @@ msgstr "任务开始: "
msgid "Update task content: {}"
msgstr "更新任务内容: {}"
-#: ops/views/adhoc.py:44 ops/views/adhoc.py:69 ops/views/adhoc.py:82
-#: ops/views/adhoc.py:95 ops/views/adhoc.py:108 ops/views/adhoc.py:121
-#: ops/views/adhoc.py:134 ops/views/command.py:47 ops/views/command.py:71
+#: ops/views/adhoc.py:45 ops/views/adhoc.py:71 ops/views/adhoc.py:85
+#: ops/views/adhoc.py:99 ops/views/adhoc.py:113 ops/views/adhoc.py:127
+#: ops/views/adhoc.py:141 ops/views/command.py:47 ops/views/command.py:71
msgid "Ops"
msgstr "作业中心"
-#: ops/views/adhoc.py:45 templates/_nav.html:81
+#: ops/views/adhoc.py:46 templates/_nav.html:81
msgid "Task list"
msgstr "任务列表"
-#: ops/views/adhoc.py:96
+#: ops/views/adhoc.py:100
msgid "Task run history"
msgstr "执行历史"
@@ -3197,64 +3204,64 @@ msgstr "添加用户"
msgid "Add user group to this permission"
msgstr "添加用户组"
-#: perms/views/asset_permission.py:33 perms/views/asset_permission.py:65
-#: perms/views/asset_permission.py:80 perms/views/asset_permission.py:95
-#: perms/views/asset_permission.py:130 perms/views/asset_permission.py:162
-#: perms/views/remote_app_permission.py:32
-#: perms/views/remote_app_permission.py:47
-#: perms/views/remote_app_permission.py:62
-#: perms/views/remote_app_permission.py:75
-#: perms/views/remote_app_permission.py:101
-#: perms/views/remote_app_permission.py:137 templates/_nav.html:41
+#: perms/views/asset_permission.py:34 perms/views/asset_permission.py:67
+#: perms/views/asset_permission.py:83 perms/views/asset_permission.py:99
+#: perms/views/asset_permission.py:136 perms/views/asset_permission.py:169
+#: perms/views/remote_app_permission.py:33
+#: perms/views/remote_app_permission.py:49
+#: perms/views/remote_app_permission.py:65
+#: perms/views/remote_app_permission.py:79
+#: perms/views/remote_app_permission.py:106
+#: perms/views/remote_app_permission.py:143 templates/_nav.html:41
#: xpack/plugins/orgs/templates/orgs/org_list.html:21
msgid "Perms"
msgstr "权限管理"
-#: perms/views/asset_permission.py:34
+#: perms/views/asset_permission.py:35
msgid "Asset permission list"
msgstr "资产授权列表"
-#: perms/views/asset_permission.py:66
+#: perms/views/asset_permission.py:68
msgid "Create asset permission"
msgstr "创建权限规则"
-#: perms/views/asset_permission.py:81
+#: perms/views/asset_permission.py:84
msgid "Update asset permission"
msgstr "更新资产授权"
-#: perms/views/asset_permission.py:96
+#: perms/views/asset_permission.py:100
msgid "Asset permission detail"
msgstr "资产授权详情"
-#: perms/views/asset_permission.py:131
+#: perms/views/asset_permission.py:137
msgid "Asset permission user list"
msgstr "资产授权用户列表"
-#: perms/views/asset_permission.py:163
+#: perms/views/asset_permission.py:170
msgid "Asset permission asset list"
msgstr "资产授权资产列表"
-#: perms/views/remote_app_permission.py:33
+#: perms/views/remote_app_permission.py:34
msgid "RemoteApp permission list"
msgstr "远程应用授权列表"
-#: perms/views/remote_app_permission.py:48
+#: perms/views/remote_app_permission.py:50
msgid "Create RemoteApp permission"
msgstr "创建远程应用授权规则"
-#: perms/views/remote_app_permission.py:63
+#: perms/views/remote_app_permission.py:66
msgid "Update RemoteApp permission"
msgstr "更新远程应用授权规则"
-#: perms/views/remote_app_permission.py:76
+#: perms/views/remote_app_permission.py:80
msgid "RemoteApp permission detail"
msgstr "远程应用授权详情"
-#: perms/views/remote_app_permission.py:102
+#: perms/views/remote_app_permission.py:107
msgid "RemoteApp permission user list"
msgstr "远程应用授权用户列表"
-#: perms/views/remote_app_permission.py:138
+#: perms/views/remote_app_permission.py:144
msgid "RemoteApp permission RemoteApp list"
msgstr "远程应用授权远程应用列表"
@@ -3602,7 +3609,7 @@ msgstr "已存在"
#: settings/templates/settings/ldap_setting.html:15
#: settings/templates/settings/security_setting.html:15
#: settings/templates/settings/terminal_setting.html:16
-#: settings/templates/settings/terminal_setting.html:49 settings/views.py:19
+#: settings/templates/settings/terminal_setting.html:49 settings/views.py:20
msgid "Basic setting"
msgstr "基本设置"
@@ -3611,7 +3618,7 @@ msgstr "基本设置"
#: settings/templates/settings/email_setting.html:18
#: settings/templates/settings/ldap_setting.html:18
#: settings/templates/settings/security_setting.html:18
-#: settings/templates/settings/terminal_setting.html:20 settings/views.py:45
+#: settings/templates/settings/terminal_setting.html:20 settings/views.py:47
msgid "Email setting"
msgstr "邮件设置"
@@ -3620,7 +3627,7 @@ msgstr "邮件设置"
#: settings/templates/settings/email_setting.html:21
#: settings/templates/settings/ldap_setting.html:21
#: settings/templates/settings/security_setting.html:21
-#: settings/templates/settings/terminal_setting.html:23 settings/views.py:178
+#: settings/templates/settings/terminal_setting.html:23 settings/views.py:186
msgid "Email content setting"
msgstr "邮件内容设置"
@@ -3629,7 +3636,7 @@ msgstr "邮件内容设置"
#: settings/templates/settings/email_setting.html:24
#: settings/templates/settings/ldap_setting.html:24
#: settings/templates/settings/security_setting.html:24
-#: settings/templates/settings/terminal_setting.html:27 settings/views.py:71
+#: settings/templates/settings/terminal_setting.html:27 settings/views.py:74
msgid "LDAP setting"
msgstr "LDAP设置"
@@ -3638,7 +3645,7 @@ msgstr "LDAP设置"
#: settings/templates/settings/email_setting.html:27
#: settings/templates/settings/ldap_setting.html:27
#: settings/templates/settings/security_setting.html:27
-#: settings/templates/settings/terminal_setting.html:31 settings/views.py:100
+#: settings/templates/settings/terminal_setting.html:31 settings/views.py:104
msgid "Terminal setting"
msgstr "终端设置"
@@ -3648,7 +3655,7 @@ msgstr "终端设置"
#: settings/templates/settings/ldap_setting.html:30
#: settings/templates/settings/security_setting.html:30
#: settings/templates/settings/security_setting.html:45
-#: settings/templates/settings/terminal_setting.html:34 settings/views.py:152
+#: settings/templates/settings/terminal_setting.html:34 settings/views.py:159
msgid "Security setting"
msgstr "安全设置"
@@ -3789,22 +3796,22 @@ msgstr "在ou:{}中没有匹配条目"
msgid "The user source is not LDAP"
msgstr "用户来源不是LDAP"
-#: settings/views.py:18 settings/views.py:44 settings/views.py:70
-#: settings/views.py:99 settings/views.py:126 settings/views.py:138
-#: settings/views.py:151 settings/views.py:177 templates/_nav.html:122
+#: settings/views.py:19 settings/views.py:46 settings/views.py:73
+#: settings/views.py:103 settings/views.py:131 settings/views.py:144
+#: settings/views.py:158 settings/views.py:185 templates/_nav.html:122
msgid "Settings"
msgstr "系统设置"
-#: settings/views.py:29 settings/views.py:55 settings/views.py:81
-#: settings/views.py:112 settings/views.py:162 settings/views.py:188
+#: settings/views.py:30 settings/views.py:57 settings/views.py:84
+#: settings/views.py:116 settings/views.py:169 settings/views.py:196
msgid "Update setting successfully"
msgstr "更新设置成功"
-#: settings/views.py:127
+#: settings/views.py:132
msgid "Create replay storage"
msgstr "创建录像存储"
-#: settings/views.py:139
+#: settings/views.py:145
msgid "Create command storage"
msgstr "创建命令存储"
@@ -3827,7 +3834,7 @@ msgstr "商业支持"
#: users/templates/users/user_profile.html:17
#: users/templates/users/user_profile_update.html:37
#: users/templates/users/user_profile_update.html:57
-#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:381
+#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:388
msgid "Profile"
msgstr "个人信息"
@@ -3920,15 +3927,15 @@ msgstr ""
"\"%(user_pubkey_update)s\"> 链接 更新\n"
" "
-#: templates/_nav.html:10 users/views/group.py:27 users/views/group.py:43
-#: users/views/group.py:59 users/views/group.py:75 users/views/group.py:91
-#: users/views/login.py:154 users/views/user.py:70 users/views/user.py:86
-#: users/views/user.py:129 users/views/user.py:207 users/views/user.py:368
-#: users/views/user.py:418 users/views/user.py:458
+#: templates/_nav.html:10 users/views/group.py:28 users/views/group.py:45
+#: users/views/group.py:62 users/views/group.py:79 users/views/group.py:96
+#: users/views/login.py:154 users/views/user.py:71 users/views/user.py:88
+#: users/views/user.py:132 users/views/user.py:212 users/views/user.py:375
+#: users/views/user.py:425 users/views/user.py:465
msgid "Users"
msgstr "用户管理"
-#: templates/_nav.html:13 users/views/user.py:71
+#: templates/_nav.html:13 users/views/user.py:72
msgid "User list"
msgstr "用户列表"
@@ -3966,8 +3973,8 @@ msgstr "文件管理"
#: templates/_nav.html:72 terminal/views/command.py:51
#: terminal/views/session.py:74 terminal/views/session.py:92
-#: terminal/views/session.py:116 terminal/views/terminal.py:31
-#: terminal/views/terminal.py:46 terminal/views/terminal.py:58
+#: terminal/views/session.py:116 terminal/views/terminal.py:32
+#: terminal/views/terminal.py:48 terminal/views/terminal.py:61
msgid "Terminal"
msgstr "终端管理"
@@ -4292,24 +4299,24 @@ msgstr "时长"
msgid "Terminate"
msgstr "终断"
-#: terminal/templates/terminal/session_list.html:121
+#: terminal/templates/terminal/session_list.html:122
msgid "Terminate selected"
msgstr "终断所选"
-#: terminal/templates/terminal/session_list.html:122
+#: terminal/templates/terminal/session_list.html:123
msgid "Confirm finished"
msgstr "确认已完成"
-#: terminal/templates/terminal/session_list.html:142
+#: terminal/templates/terminal/session_list.html:144
msgid "Terminate task send, waiting ..."
msgstr "终断任务已发送,请等待"
-#: terminal/templates/terminal/session_list.html:155
+#: terminal/templates/terminal/session_list.html:157
msgid "Finish session success"
msgstr "标记会话完成成功"
#: terminal/templates/terminal/terminal_detail.html:13
-#: terminal/views/terminal.py:59
+#: terminal/views/terminal.py:62
msgid "Terminal detail"
msgstr "终端详情"
@@ -4353,23 +4360,23 @@ msgstr "在线会话"
msgid "Session offline list"
msgstr "离线会话"
-#: terminal/views/terminal.py:32
+#: terminal/views/terminal.py:33
msgid "Terminal list"
msgstr "终端列表"
-#: terminal/views/terminal.py:46
+#: terminal/views/terminal.py:48
msgid "Update terminal"
msgstr "更新终端"
-#: terminal/views/terminal.py:105 terminal/views/terminal.py:106
+#: terminal/views/terminal.py:111 terminal/views/terminal.py:112
msgid "Redirect to web terminal"
msgstr "重定向到web terminal"
-#: terminal/views/terminal.py:113
+#: terminal/views/terminal.py:119
msgid "Connect ssh terminal"
msgstr "连接ssh终端"
-#: terminal/views/terminal.py:114
+#: terminal/views/terminal.py:120
msgid ""
"You should use your ssh client tools connect terminal: {}
{}"
msgstr "你可以使用ssh客户端工具连接终端"
@@ -4422,7 +4429,7 @@ msgstr "生成重置密码链接,通过邮件发送给用户"
msgid "Set password"
msgstr "设置密码"
-#: users/forms.py:118 xpack/plugins/change_auth_plan/models.py:83
+#: users/forms.py:118 xpack/plugins/change_auth_plan/models.py:86
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:51
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:69
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:57
@@ -4537,7 +4544,7 @@ msgid "Date password last updated"
msgstr "最后更新密码日期"
#: users/models/user.py:138 users/templates/users/user_update.html:22
-#: users/views/login.py:47 users/views/login.py:108 users/views/user.py:431
+#: users/views/login.py:47 users/views/login.py:108 users/views/user.py:438
msgid "User auth from {}, go there change password"
msgstr "用户认证源来自 {}, 请去相应系统修改密码"
@@ -4614,7 +4621,7 @@ msgid "Import user groups"
msgstr "导入用户组"
#: users/templates/users/_user_groups_update_modal.html:4
-#: users/views/group.py:60
+#: users/views/group.py:63
msgid "Update user group"
msgstr "更新用户组"
@@ -4623,7 +4630,7 @@ msgid "Import users"
msgstr "导入用户"
#: users/templates/users/_user_update_modal.html:4
-#: users/templates/users/user_update.html:4 users/views/user.py:130
+#: users/templates/users/user_update.html:4 users/views/user.py:133
msgid "Update user"
msgstr "更新用户"
@@ -4696,7 +4703,7 @@ msgid "Always young, always with tears in my eyes. Stay foolish Stay hungry"
msgstr "永远年轻,永远热泪盈眶 stay foolish stay hungry"
#: users/templates/users/reset_password.html:46
-#: users/templates/users/user_detail.html:373 users/utils.py:98
+#: users/templates/users/user_detail.html:373 users/utils.py:88
msgid "Reset password"
msgstr "重置密码"
@@ -4761,12 +4768,12 @@ msgid "Very strong"
msgstr "很强"
#: users/templates/users/user_create.html:4
-#: users/templates/users/user_list.html:28 users/views/user.py:87
+#: users/templates/users/user_list.html:28 users/views/user.py:89
msgid "Create user"
msgstr "创建用户"
#: users/templates/users/user_detail.html:19
-#: users/templates/users/user_granted_asset.html:18 users/views/user.py:208
+#: users/templates/users/user_granted_asset.html:18 users/views/user.py:213
msgid "User detail"
msgstr "用户详情"
@@ -4869,7 +4876,7 @@ msgstr "重置用户MFA成功"
#: users/templates/users/user_group_detail.html:22
#: users/templates/users/user_group_granted_asset.html:18
-#: users/views/group.py:76
+#: users/views/group.py:80
msgid "User group detail"
msgstr "用户组详情"
@@ -4878,7 +4885,7 @@ msgstr "用户组详情"
msgid "Add user"
msgstr "添加用户"
-#: users/templates/users/user_group_list.html:28 users/views/group.py:44
+#: users/templates/users/user_group_list.html:28 users/views/group.py:46
msgid "Create user group"
msgstr "创建用户组"
@@ -4969,8 +4976,8 @@ msgstr "安装完成后点击下一步进入绑定页面(如已安装,直接
msgid "Administrator Settings force MFA login"
msgstr "管理员设置强制使用MFA登录"
-#: users/templates/users/user_profile.html:120 users/views/user.py:244
-#: users/views/user.py:298
+#: users/templates/users/user_profile.html:120 users/views/user.py:249
+#: users/views/user.py:304
msgid "User groups"
msgstr "用户组"
@@ -5020,7 +5027,7 @@ msgid ""
"corresponding private key."
msgstr "新的公钥已设置成功,请下载对应的私钥"
-#: users/utils.py:38
+#: users/utils.py:28
#, python-format
msgid ""
"\n"
@@ -5065,16 +5072,16 @@ msgstr ""
"
\n"
" "
-#: users/utils.py:73
+#: users/utils.py:63
msgid "Create account successfully"
msgstr "创建账户成功"
-#: users/utils.py:77
+#: users/utils.py:67
#, python-format
msgid "Hello %(name)s"
msgstr "您好 %(name)s"
-#: users/utils.py:100
+#: users/utils.py:90
#, python-format
msgid ""
"\n"
@@ -5118,11 +5125,11 @@ msgstr ""
" \n"
" "
-#: users/utils.py:131
+#: users/utils.py:121
msgid "Security notice"
msgstr "安全通知"
-#: users/utils.py:133
+#: users/utils.py:123
#, python-format
msgid ""
"\n"
@@ -5171,11 +5178,11 @@ msgstr ""
" \n"
" "
-#: users/utils.py:169
+#: users/utils.py:159
msgid "SSH Key Reset"
msgstr "重置ssh密钥"
-#: users/utils.py:171
+#: users/utils.py:161
#, python-format
msgid ""
"\n"
@@ -5200,23 +5207,23 @@ msgstr ""
" \n"
" "
-#: users/utils.py:204
+#: users/utils.py:194
msgid "User not exist"
msgstr "用户不存在"
-#: users/utils.py:206
+#: users/utils.py:196
msgid "Disabled or expired"
msgstr "禁用或失效"
-#: users/utils.py:219
+#: users/utils.py:209
msgid "Password or SSH public key invalid"
msgstr "密码或密钥不合法"
-#: users/views/group.py:28
+#: users/views/group.py:29
msgid "User group list"
msgstr "用户组列表"
-#: users/views/group.py:92
+#: users/views/group.py:97
msgid "User group granted asset"
msgstr "用户组授权资产"
@@ -5249,7 +5256,7 @@ msgstr "Token错误或失效"
msgid "Password not same"
msgstr "密码不一致"
-#: users/views/login.py:115 users/views/user.py:144 users/views/user.py:441
+#: users/views/login.py:115 users/views/user.py:147 users/views/user.py:448
msgid "* Your password does not meet the requirements"
msgstr "* 您的密码不符合要求"
@@ -5257,51 +5264,51 @@ msgstr "* 您的密码不符合要求"
msgid "First login"
msgstr "首次登录"
-#: users/views/user.py:161
+#: users/views/user.py:164
msgid "Bulk update user success"
msgstr "批量更新用户成功"
-#: users/views/user.py:188
+#: users/views/user.py:192
msgid "Bulk update user"
msgstr "批量更新用户"
-#: users/views/user.py:273
+#: users/views/user.py:279
msgid "Invalid file."
msgstr "文件不合法"
-#: users/views/user.py:369
+#: users/views/user.py:376
msgid "User granted assets"
msgstr "用户授权资产"
-#: users/views/user.py:400
+#: users/views/user.py:407
msgid "Profile setting"
msgstr "个人信息设置"
-#: users/views/user.py:419
+#: users/views/user.py:426
msgid "Password update"
msgstr "密码更新"
-#: users/views/user.py:459
+#: users/views/user.py:466
msgid "Public key update"
msgstr "密钥更新"
-#: users/views/user.py:500
+#: users/views/user.py:507
msgid "Password invalid"
msgstr "用户名或密码无效"
-#: users/views/user.py:600
+#: users/views/user.py:607
msgid "MFA enable success"
msgstr "MFA 绑定成功"
-#: users/views/user.py:601
+#: users/views/user.py:608
msgid "MFA enable success, return login page"
msgstr "MFA 绑定成功,返回到登录页面"
-#: users/views/user.py:603
+#: users/views/user.py:610
msgid "MFA disable success"
msgstr "MFA 解绑成功"
-#: users/views/user.py:604
+#: users/views/user.py:611
msgid "MFA disable success, return login page"
msgstr "MFA 解绑成功,返回登录页面"
@@ -5330,23 +5337,17 @@ msgstr "定时执行"
#: xpack/plugins/change_auth_plan/forms.py:120
msgid ""
-"Tips: Currently only unix-like assets are supported, while Windows assets "
-"are not"
-msgstr ""
-
-#: xpack/plugins/change_auth_plan/forms.py:122
-msgid ""
"Tips: The username of the user on the asset to be modified. if the user "
"exists, change the password; If the user does not exist, create the user."
msgstr ""
"提示:用户名为将要修改的资产上的用户的用户名。如果用户存在,则修改密码;如果"
"用户不存在,则创建用户。"
-#: xpack/plugins/change_auth_plan/forms.py:126
+#: xpack/plugins/change_auth_plan/forms.py:124
msgid "Tips: (Units: hour)"
msgstr "提示:(单位: 时)"
-#: xpack/plugins/change_auth_plan/forms.py:127
+#: xpack/plugins/change_auth_plan/forms.py:125
msgid ""
"eg: Every Sunday 03:05 run <5 3 * * 0>
Tips: Using 5 digits linux "
"crontab expressions (