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 %} -
- {% csrf_token %} -
- -
-

-
-
-
- -
-

-
-
-
- -
- -
-
-
-{% 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 %} +
+ {% csrf_token %} +
+ +
+

+
+
+
+ +
+

+
+
+
+ +
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+ +{% 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 @@ }
-
- -
- - {% trans "Need otp auth for view auth" %} -
- -
-
@@ -132,50 +120,9 @@ - {% include 'assets/_asset_user_auth_modal.html' %} - {% include 'assets/_asset_user_view_auth_modal.html' %} {% endblock %} {% block custom_foot_js %} {% endblock %} diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index fec960dab..3309e44c2 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -18,6 +18,7 @@ router.register(r'domain', api.DomainViewSet, 'domain') router.register(r'gateway', api.GatewayViewSet, 'gateway') router.register(r'cmd-filter', api.CommandFilterViewSet, 'cmd-filter') router.register(r'asset-user', api.AssetUserViewSet, 'asset-user') +router.register(r'asset-user-info', api.AssetUserExportViewSet, 'asset-user-info') cmd_filter_router = routers.NestedDefaultRouter(router, r'cmd-filter', lookup='filter') cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-rule') diff --git a/apps/assets/views/admin_user.py b/apps/assets/views/admin_user.py index 6c6f866da..089efe352 100644 --- a/apps/assets/views/admin_user.py +++ b/apps/assets/views/admin_user.py @@ -99,7 +99,7 @@ class AdminUserAssetsView(PermissionsMixin, SingleObjectMixin, ListView): return super().get(request, *args, **kwargs) def get_queryset(self): - self.queryset = self.object.asset_set.all() + self.queryset = self.object.assets.all() return self.queryset def get_context_data(self, **kwargs): diff --git a/apps/assets/views/asset.py b/apps/assets/views/asset.py index 36e9bb8ea..72ead3ca3 100644 --- a/apps/assets/views/asset.py +++ b/apps/assets/views/asset.py @@ -27,7 +27,7 @@ from django.forms.formsets import formset_factory from common.mixins import JSONResponseMixin from common.utils import get_object_or_none, get_logger -from common.permissions import PermissionsMixin ,IsOrgAdmin +from common.permissions import PermissionsMixin, IsOrgAdmin, IsValidUser from common.const import ( create_success_msg, update_success_msg, KEY_CACHE_RESOURCES_ID ) @@ -74,8 +74,9 @@ class AssetUserListView(PermissionsMixin, DetailView): return super().get_context_data(**kwargs) -class UserAssetListView(LoginRequiredMixin, TemplateView): +class UserAssetListView(PermissionsMixin, TemplateView): template_name = 'assets/user_asset_list.html' + permission_classes = [IsValidUser] def get_context_data(self, **kwargs): context = { @@ -214,10 +215,11 @@ class AssetDeleteView(PermissionsMixin, DeleteView): permission_classes = [IsOrgAdmin] -class AssetDetailView(LoginRequiredMixin, DetailView): +class AssetDetailView(PermissionsMixin, DetailView): model = Asset context_object_name = 'asset' template_name = 'assets/asset_detail.html' + permission_classes = [IsValidUser] def get_context_data(self, **kwargs): nodes_remain = Node.objects.exclude(assets=self.object) @@ -231,7 +233,9 @@ class AssetDetailView(LoginRequiredMixin, DetailView): @method_decorator(csrf_exempt, name='dispatch') -class AssetExportView(LoginRequiredMixin, View): +class AssetExportView(PermissionsMixin, View): + permission_classes = [IsValidUser] + def get(self, request): spm = request.GET.get('spm', '') assets_id_default = [Asset.objects.first().id] if Asset.objects.first() else [] diff --git a/apps/audits/views.py b/apps/audits/views.py index a8632e1a8..cab4f1fbf 100644 --- a/apps/audits/views.py +++ b/apps/audits/views.py @@ -14,12 +14,11 @@ from django.views import View from django.views.decorators.csrf import csrf_exempt from django.views.generic import ListView from django.utils.translation import ugettext as _ -from django.contrib.auth.mixins import LoginRequiredMixin from django.db.models import Q from audits.utils import get_excel_response, write_content_to_excel from common.mixins import DatetimeSearchMixin -from common.permissions import PermissionsMixin, IsOrgAdmin, IsAuditor +from common.permissions import PermissionsMixin, IsOrgAdmin, IsAuditor, IsValidUser from orgs.utils import current_org from ops.views import CommandExecutionListView as UserCommandExecutionListView @@ -253,7 +252,8 @@ class CommandExecutionListView(UserCommandExecutionListView): @method_decorator(csrf_exempt, name='dispatch') -class LoginLogExportView(LoginRequiredMixin, View): +class LoginLogExportView(PermissionsMixin, View): + permission_classes = [IsValidUser] def get(self, request): fields = [ diff --git a/apps/authentication/templates/authentication/_mfa_confirm_modal.html b/apps/authentication/templates/authentication/_mfa_confirm_modal.html new file mode 100644 index 000000000..9e1932911 --- /dev/null +++ b/apps/authentication/templates/authentication/_mfa_confirm_modal.html @@ -0,0 +1,80 @@ +{% extends '_modal.html' %} +{% load i18n %} +{% load static %} +{% block modal_id %}mfa_auth_confirm{% endblock %} +{% block modal_title%}{% trans "MFA confirm" %}{% endblock %} +{% block modal_body %} + +
+
+ +
+ + {% trans "Need otp auth for view auth" %} +
+ +
+
+ +{% 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 (