Commit 4e705a52 authored by BaiJiangJie's avatar BaiJiangJie Committed by 老广

[Feature] 添加资产用户管理器 (#2489)

* [Feature] 1. 资产用户管理器

* [Feature] 2. 资产用户管理器: 更新AuthBook

* [Feature] 3. 资产用户管理器: 添加 AssetUser API

* [Feature] 4. AssetUser Model: 添加方法 load_related_asset_auth

* [Feature] 5. AdminUser: 更新管理用户获取认证信息时,先加载相关资产的认证

* [Feature] 6. SystemUser: 更新系统用户获取认证信息时,先加载相关资产的认证

* [Feature] 前端页面: 添加资产用户列表页面

* [Feature] 前端页面: 管理用户的资产管理页面添加按钮: 修改资产用户认证信息

* [Feature] 前端页面: 系统用户的资产管理页面添加按钮: 修改资产用户认证信息

* [Feature] 优化: 从管理用户和系统用户的backend中获取相关资产用户的逻辑

* [Update] Fix 1

* [Feature] 优化: SystemUserBackend之filter功能

* [Feature] 优化: AdminUserBackend之filter功能

* [Feature] 优化: AdminUserBackend和SystemUserBackend功能

* [Feature] 更新翻译: 资产用户管理器

* [Update] 更新资产用户列表页名称为: asset_asset_user_list.html

* [Bugfix] 修改bug: SystemUserBackend 根据用户名过滤系统用户

* [Feature] 添加: 资产用户列表中可测试资产用户的连接性

* [Update] 修改: AdHoc model的run_as字段从SystemUser外键修改为username字符串

* [Feature] 添加: 获取系统用户认证信息(对应某个资产)API

* [Update] 更新: API获取asset user时进行排序

* [Bugfix] 修改: 资产用户可连接性CACHE_KEY

* [Update] 更新翻译信息

* [Update] 修改获取资产用户认证信息API的返回响应(200/400)

* [Update] 修改BaseUser获取特定资产的方法名

* [Update] 修改logger输出,AuthBook set_version_and_latest

* [Update] 修改日志输出添加exc_info参数

* [Update] 移除AuthBook迁移文件0026

* [Bugfix] 修复AdminUserBackend获取instances为空的bug
parent 9bb58afe
......@@ -5,3 +5,4 @@ from .system_user import *
from .node import *
from .domain import *
from .cmd_filter import *
from .asset_user import *
# -*- coding: utf-8 -*-
#
from rest_framework.response import Response
from rest_framework import viewsets, status, generics
from rest_framework.pagination import LimitOffsetPagination
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 .. import serializers
from ..tasks import test_asset_users_connectivity_manual
__all__ = [
'AssetUserViewSet', 'AssetUserAuthInfoApi', 'AssetUserTestConnectiveApi',
]
logger = get_logger(__name__)
class AssetUserViewSet(viewsets.GenericViewSet):
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)
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)
return queryset
def filter_queryset(self, queryset):
queryset = sorted(
queryset,
key=lambda q: (q.asset.hostname, q.connectivity, q.username)
)
return queryset
class AssetUserAuthInfoApi(generics.RetrieveAPIView):
serializer_class = serializers.AssetUserAuthInfoSerializer
permission_classes = (IsOrgAdminOrAppUser,)
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
status_code = status.HTTP_200_OK
if not instance:
status_code = status.HTTP_400_BAD_REQUEST
return Response(serializer.data, status=status_code)
def get_object(self):
username = self.request.GET.get('username')
asset_id = self.request.GET.get('asset_id')
asset = get_object_or_none(Asset, pk=asset_id)
try:
instance = AssetUserManager.get(username, asset)
except Exception as e:
logger.error(e, exc_info=True)
return None
else:
return instance
class AssetUserTestConnectiveApi(generics.RetrieveAPIView):
"""
Test asset users connective
"""
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)
return asset_users
def retrieve(self, request, *args, **kwargs):
asset_users = self.get_asset_users()
task = test_asset_users_connectivity_manual.delay(asset_users)
return Response({"task": task.id})
......@@ -30,7 +30,7 @@ from ..tasks import push_system_user_to_assets_manual, \
logger = get_logger(__file__)
__all__ = [
'SystemUserViewSet', 'SystemUserAuthInfoApi',
'SystemUserViewSet', 'SystemUserAuthInfoApi', 'SystemUserAssetAuthInfoApi',
'SystemUserPushApi', 'SystemUserTestConnectiveApi',
'SystemUserAssetsListView', 'SystemUserPushToAssetApi',
'SystemUserTestAssetConnectivityApi', 'SystemUserCommandFilterRuleListApi',
......@@ -68,6 +68,22 @@ class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
return Response(status=204)
class SystemUserAssetAuthInfoApi(generics.RetrieveAPIView):
"""
Get system user with asset auth info
"""
queryset = SystemUser.objects.all()
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.SystemUserAuthSerializer
def get_object(self):
instance = super().get_object()
aid = self.kwargs.get('aid')
asset = get_object_or_404(Asset, pk=aid)
instance.load_specific_asset_auth(asset)
return instance
class SystemUserPushApi(generics.RetrieveAPIView):
"""
Push system user to cluster assets api
......
# -*- coding: utf-8 -*-
#
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
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):
"""
:param username: 用户名
:param asset: <Asset>对象
:param latest: 是否是最新记录
:return: 元素为<AuthBook>的可迭代对象(<list> or <QuerySet>)
"""
pass
@classmethod
@abstractmethod
def create(cls, **kwargs):
"""
:param kwargs:
{
name, username, asset, comment, password, public_key, private_key,
(org_id)
}
:return: <AuthBook>对象
"""
pass
@classmethod
def raise_does_not_exist(cls, name):
raise cls.ObjectDoesNotExist(cls.MSG_NOT_EXIST.format(name))
@classmethod
def raise_multiple_return(cls, name, length):
raise cls.MultipleObjectsReturned(cls.MSG_MULTIPLE.format(name, length))
# -*- coding: utf-8 -*-
#
from assets.models import AuthBook
from ..base import BaseBackend
class AuthBookBackend(BaseBackend):
@classmethod
def filter(cls, username=None, asset=None, latest=True):
queryset = AuthBook.objects.all()
if username:
queryset = queryset.filter(username=username)
if asset:
queryset = queryset.filter(asset=asset)
if latest:
queryset = queryset.latest_version()
return queryset
@classmethod
def create(cls, **kwargs):
auth_info = {
'password': kwargs.pop('password', ''),
'public_key': kwargs.pop('public_key', ''),
'private_key': kwargs.pop('private_key', '')
}
obj = AuthBook.objects.create(**kwargs)
obj.set_auth(**auth_info)
return obj
# -*- coding: utf-8 -*-
#
# from django.conf import settings
from .db import AuthBookBackend
# from .vault import VaultBackend
def get_backend():
default_backend = AuthBookBackend
# if settings.BACKEND_ASSET_USER_AUTH_VAULT:
# return VaultBackend
return default_backend
# -*- coding: utf-8 -*-
#
from ..base import BaseBackend
class VaultBackend(BaseBackend):
@classmethod
def get(cls, username, asset):
pass
@classmethod
def filter(cls, username=None, asset=None, latest=True):
pass
@classmethod
def create(cls, **kwargs):
pass
# -*- 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 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")
# -*- 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")
# -*- 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:
_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(<SystemUser>, <SystemUser>, ...) }
"""
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: [<AuthBook>, <AuthBook>, ...]
"""
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")
# -*- coding: utf-8 -*-
#
from assets.models import AuthBook
def construct_authbook_object(asset_user, asset):
"""
作用: 将<AssetUser>对象构造成为<AuthBook>对象并返回
:param asset_user: <AdminUser>或<SystemUser>对象
:param asset: <Asset>对象
:return: <AuthBook>对象
"""
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
# -*- 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
......@@ -32,6 +32,18 @@ TEST_SYSTEM_USER_CONN_TASKS = [
}
]
ASSET_USER_CONN_CACHE_KEY = 'ASSET_USER_CONN_{}_{}'
TEST_ASSET_USER_CONN_TASKS = [
{
"name": "ping",
"action": {
"module": "ping",
}
}
]
TASK_OPTIONS = {
'timeout': 10,
'forks': 10,
......
......@@ -7,3 +7,4 @@ from .node import *
from .asset import *
from .cmd_filter import *
from .utils import *
from .authbook import *
......@@ -197,6 +197,7 @@ class Asset(OrgModelMixin):
def get_auth_info(self):
if self.admin_user:
self.admin_user.load_specific_asset_auth(self)
return {
'username': self.admin_user.username,
'password': self.admin_user.password,
......@@ -232,6 +233,7 @@ class Asset(OrgModelMixin):
"""
data = self.to_json()
if self.admin_user:
self.admin_user.load_specific_asset_auth(self)
admin_user = self.admin_user
data.update({
'username': admin_user.username,
......
# -*- coding: utf-8 -*-
#
from django.db import models
from django.utils.translation import ugettext as _
from django.core.cache import cache
from orgs.mixins import OrgManager
from .base import AssetUser
from ..const import ASSET_USER_CONN_CACHE_KEY
__all__ = ['AuthBook']
class AuthBookQuerySet(models.QuerySet):
def latest_version(self):
return self.filter(is_latest=True)
class AuthBookManager(OrgManager):
pass
class AuthBook(AssetUser):
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset'))
is_latest = models.BooleanField(default=False, verbose_name=_('Latest version'))
version = models.IntegerField(default=1, verbose_name=_('Version'))
objects = AuthBookManager.from_queryset(AuthBookQuerySet)()
class Meta:
verbose_name = _('AuthBook')
def _set_latest(self):
self._remove_pre_obj_latest()
self.is_latest = True
self.save()
def _get_pre_obj(self):
pre_obj = self.__class__.objects.filter(
username=self.username, asset=self.asset).latest_version().first()
return pre_obj
def _remove_pre_obj_latest(self):
pre_obj = self._get_pre_obj()
if pre_obj:
pre_obj.is_latest = False
pre_obj.save()
def _set_version(self):
pre_obj = self._get_pre_obj()
if pre_obj:
self.version = pre_obj.version + 1
else:
self.version = 1
self.save()
def set_version_and_latest(self):
self._set_version()
self._set_latest()
@property
def _conn_cache_key(self):
return ASSET_USER_CONN_CACHE_KEY.format(self.id, self.asset.id)
@property
def connectivity(self):
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', {}).keys():
if host == self.asset.hostname:
_connectivity = self.REACHABLE
cache.set(self._conn_cache_key, _connectivity, 3600)
@property
def keyword(self):
return {'username': self.username, 'asset': self.asset}
def __str__(self):
return '{}@{}'.format(self.username, self.asset)
......@@ -9,13 +9,17 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from common.utils import get_signer, ssh_key_string_to_obj, ssh_key_gen
from common.utils import (
get_signer, ssh_key_string_to_obj, ssh_key_gen, get_logger
)
from common.validators import alphanumeric
from orgs.mixins import OrgModelMixin
from .utils import private_key_validator
signer = get_signer()
logger = get_logger(__file__)
class AssetUser(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
......@@ -45,8 +49,8 @@ class AssetUser(OrgModelMixin):
@password.setter
def password(self, password_raw):
raise AttributeError("Using set_auth do that")
# self._password = signer.sign(password_raw)
# raise AttributeError("Using set_auth do that")
self._password = signer.sign(password_raw)
@property
def private_key(self):
......@@ -55,8 +59,8 @@ class AssetUser(OrgModelMixin):
@private_key.setter
def private_key(self, private_key_raw):
raise AttributeError("Using set_auth do that")
# self._private_key = signer.sign(private_key_raw)
# raise AttributeError("Using set_auth do that")
self._private_key = signer.sign(private_key_raw)
@property
def private_key_obj(self):
......@@ -88,6 +92,11 @@ class AssetUser(OrgModelMixin):
else:
return None
@public_key.setter
def public_key(self, public_key_raw):
# raise AttributeError("Using set_auth do that")
self._public_key = signer.sign(public_key_raw)
@property
def public_key_obj(self):
if self.public_key:
......@@ -115,6 +124,25 @@ class AssetUser(OrgModelMixin):
def get_auth(self, asset=None):
pass
def load_specific_asset_auth(self, asset):
from ..backends.multi import AssetUserManager
try:
other = AssetUserManager.get(username=self.username, asset=asset)
except Exception as e:
logger.error(e, exc_info=True)
else:
self._merge_auth(other)
def _merge_auth(self, other):
if not other:
return
if other.password:
self.password = other.password
if other.public_key:
self.public_key = other.public_key
if other.private_key:
self.private_key = other.private_key
def clear_auth(self):
self._password = ''
self._private_key = ''
......
......@@ -8,3 +8,4 @@ from .system_user import *
from .node import *
from .domain import *
from .cmd_filter import *
from .asset_user import *
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext as _
from rest_framework import serializers
from ..models import AuthBook
from ..backends.multi import AssetUserManager
__all__ = [
'AssetUserSerializer', 'AssetUserAuthInfoSerializer',
]
class AssetUserSerializer(serializers.ModelSerializer):
password = serializers.CharField(
max_length=256, allow_blank=True, allow_null=True, write_only=True,
required=False, help_text=_('Password')
)
public_key = serializers.CharField(
max_length=4096, allow_blank=True, allow_null=True, write_only=True,
required=False, help_text=_('Public key')
)
private_key = serializers.CharField(
max_length=4096, allow_blank=True, allow_null=True, write_only=True,
required=False, help_text=_('Private key')
)
class Meta:
model = AuthBook
read_only_fields = (
'date_created', 'date_updated', 'created_by',
'is_latest', 'version', 'connectivity',
)
fields = '__all__'
extra_kwargs = {
'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 create(self, validated_data):
kwargs = {
'name': validated_data.get('name'),
'username': validated_data.get('username'),
'asset': validated_data.get('asset'),
'comment': validated_data.get('comment', ''),
'org_id': validated_data.get('org_id', ''),
'password': validated_data.get('password'),
'public_key': validated_data.get('public_key'),
'private_key': validated_data.get('private_key')
}
instance = AssetUserManager.create(**kwargs)
return instance
class AssetUserAuthInfoSerializer(serializers.ModelSerializer):
class Meta:
model = AuthBook
fields = ['password', 'private_key', 'public_key']
......@@ -5,9 +5,12 @@ from django.db.models.signals import post_save, m2m_changed, post_delete
from django.dispatch import receiver
from common.utils import get_logger
from .models import Asset, SystemUser, Node
from .tasks import update_assets_hardware_info_util, \
test_asset_connectivity_util, push_system_user_to_assets
from .models import Asset, SystemUser, Node, AuthBook
from .tasks import (
update_assets_hardware_info_util,
test_asset_connectivity_util,
push_system_user_to_assets
)
logger = get_logger(__file__)
......@@ -109,3 +112,10 @@ def on_node_assets_changed(sender, instance=None, **kwargs):
def on_node_update_or_created(sender, instance=None, created=False, **kwargs):
if instance and not created:
instance.expire_full_value()
@receiver(post_save, sender=AuthBook)
def on_auth_book_created(sender, instance=None, created=False, **kwargs):
if created:
logger.debug('Receive create auth book object signal.')
instance.set_version_and_latest()
......@@ -26,16 +26,22 @@ disk_pattern = re.compile(r'^hd|sd|xvd|vd')
PERIOD_TASK = os.environ.get("PERIOD_TASK", "on")
def check_asset_can_run_ansible(asset):
if not asset.is_active:
msg = _("Asset has been disabled, skipped: {}").format(asset)
logger.info(msg)
return False
if not asset.support_ansible():
msg = _("Asset may not be support ansible, skipped: {}").format(asset)
logger.info(msg)
return False
return True
def clean_hosts(assets):
clean_assets = []
for asset in assets:
if not asset.is_active:
msg = _("Asset has been disabled, skipped: {}").format(asset)
logger.info(msg)
continue
if not asset.support_ansible():
msg = _("Asset may not be support ansible, skipped: {}").format(asset)
logger.info(msg)
if not check_asset_can_run_ansible(asset):
continue
clean_assets.append(asset)
if not clean_assets:
......@@ -259,7 +265,7 @@ def test_system_user_connectivity_util(system_user, assets, task_name):
task, created = update_or_create_ansible_task(
task_name, hosts=hosts, tasks=tasks, pattern='all',
options=const.TASK_OPTIONS,
run_as=system_user, created_by=system_user.org_id,
run_as=system_user.username, created_by=system_user.org_id,
)
result = task.run()
set_system_user_connectivity_info(system_user, result)
......@@ -370,16 +376,18 @@ def push_system_user_util(system_user, assets, task_name):
logger.info(msg)
return
tasks = get_push_system_user_tasks(system_user)
hosts = clean_hosts(assets)
if not hosts:
return {}
task, created = update_or_create_ansible_task(
task_name=task_name, hosts=hosts, tasks=tasks, pattern='all',
options=const.TASK_OPTIONS, run_as_admin=True,
created_by=system_user.org_id,
)
return task.run()
for host in hosts:
system_user.load_specific_asset_auth(host)
tasks = get_push_system_user_tasks(system_user)
task, created = update_or_create_ansible_task(
task_name=task_name, hosts=[host], tasks=tasks, pattern='all',
options=const.TASK_OPTIONS, run_as_admin=True,
created_by=system_user.org_id,
)
task.run()
@shared_task
......@@ -415,6 +423,43 @@ def test_admin_user_connectability_period():
pass
@shared_task
def set_asset_user_connectivity_info(asset_user, result):
summary = result[1]
asset_user.connectivity = summary
@shared_task
def test_asset_user_connectivity_util(asset_user, task_name):
"""
:param asset_user: <AuthBook>对象
:param task_name:
:return:
"""
from ops.utils import update_or_create_ansible_task
tasks = const.TEST_ASSET_USER_CONN_TASKS
if not check_asset_can_run_ansible(asset_user.asset):
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
)
result = task.run()
set_asset_user_connectivity_info(asset_user, result)
@shared_task
def test_asset_users_connectivity_manual(asset_users):
"""
:param asset_users: <AuthBook>对象
"""
for asset_user in asset_users:
task_name = _("Test asset user connectivity: {}").format(asset_user)
test_asset_user_connectivity_util(asset_user, task_name)
# @shared_task
# @register_as_period_task(interval=3600)
# @after_app_ready_start
......
{% 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 %}
<form class="form-horizontal" role="form" onkeydown="if(event.keyCode==13){ $('#btn_asset_user_auth_modal_confirm').trigger('click'); return false;}">
{% csrf_token %}
<div class="form-group">
<label class="col-sm-2 control-label">{% trans "Hostname" %}</label>
<div class="col-sm-10">
<p class="form-control-static" id="id_hostname_p"></p>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">{% trans "Username" %}</label>
<div class="col-sm-10">
<p class="form-control-static" id="id_username_p"></p>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">{% trans "Password" %}</label>
<div class="col-sm-10">
<input class="form-control" id="id_password" type="password" name="password" placeholder="{% trans 'Please input password' %}"/>
</div>
</div>
</form>
{% endblock %}
{% block modal_confirm_id %}btn_asset_user_auth_modal_confirm{% endblock %}
......@@ -84,6 +84,7 @@
</div>
</div>
</div>
{% include 'assets/_asset_user_auth_modal.html' %}
{% endblock %}
{% block custom_foot_js %}
<script>
......@@ -109,9 +110,10 @@ function initTable() {
$(td).html('')
}
}},
{targets: 4, createdCell: function (td, cellData) {
{targets: 4, createdCell: function (td, cellData, rowData) {
var test_btn = ' <a class="btn btn-xs btn-info btn-test-asset" data-uid="{{ DEFAULT_PK }}" >{% trans "Test" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
$(td).html(test_btn);
var update_auth_btn = ' <a class="btn btn-xs btn-primary btn-update-asset-user-auth" data-aid="{{ DEFAULT_PK }}" data-hostname="hostname777">{% trans "Update auth" %}</a>'.replace("{{ DEFAULT_PK }}", cellData).replace("hostname777", rowData.hostname);
$(td).html(test_btn + update_auth_btn);
}}
],
......@@ -124,6 +126,15 @@ function initTable() {
jumpserver.initServerSideDataTable(options);
}
function initAssetUserAuthModalForm(hostname, username){
$('#id_hostname_p').html(hostname);
$('#id_username_p').html(username);
$('#id_password').parent().removeClass('has-error');
$('#id_password').val('');
}
var assetId ;
$(document).ready(function () {
initTable();
})
......@@ -156,5 +167,38 @@ $(document).ready(function () {
flash_message: false
});
})
.on('click', '.btn-update-asset-user-auth', function() {
assetId = $(this).data('aid');
var hostname = $(this).data('hostname');
var username = '{{ admin_user.username }}';
initAssetUserAuthModalForm(hostname, username);
$("#asset_user_auth_modal").modal();
})
.on('click', '#btn_asset_user_auth_modal_confirm', function(){
var password = $('#id_password').val();
if (password){
var data = {
'name': "{{ admin_user.username }}",
'asset': assetId,
'username': "{{ admin_user.username }}",
'password': password
};
formSubmit({
data: data,
url: "{% url 'api-assets:asset-user-list' %}",
method: 'POST',
success: function () {
toastr.success("{% trans 'Update successfully!' %}");
},
error: function () {
toastr.error("{% trans 'Update failed!' %}");
}
});
$("#asset_user_auth_modal").modal('hide');
}
else{
$('#id_password').parent().addClass('has-error');
}
})
</script>
{% endblock %}
{% extends 'base.html' %}
{% load common_tags %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li>
<a href="{% url 'assets:asset-detail' pk=asset.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Asset detail' %}</a>
</li>
<li class="active">
<a href="{% url 'assets:asset-user-list' pk=asset.id %}" class="text-center"><i class="fa fa-bar-chart-o"></i> {% trans 'Asset user list' %}</a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-8" style="padding-left: 0;">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span style="float: left">{% trans 'Asset users of' %} <b>{{ asset.hostname }} </b></span>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<table class="table table-hover" id="asset_user_list">
<thead>
<tr>
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
<th class="text-center">{% trans 'Username' %}</th>
<th class="text-center">{% trans 'Version' %}</th>
<th class="text-center">{% trans 'Reachable' %}</th>
<th class="text-center">{% trans 'Date updated' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-sm-4" style="padding-left: 0;padding-right: 0">
<div class="panel panel-primary">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Quick modify' %}
</div>
<div class="panel-body">
<table class="table">
<tbody>
{% if asset.protocol == 'ssh' %}
<tr class="no-borders-tr">
<td>{% trans 'Test connective' %}:</td>
<td>
<span class="pull-right">
<button type="button" class="btn btn-primary btn-xs" id="btn-bulk-test-connective" style="width: 54px">{% trans 'Test' %}</button>
</span>
</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% include 'assets/_asset_user_auth_modal.html' %}
{% endblock %}
{% block custom_foot_js %}
<script>
function initAssetUserAuthModalForm(hostname){
$('#id_hostname_p').html(hostname);
$('#id_username_p').html(username);
$('#id_password').parent().removeClass('has-error');
$('#id_password').val('');
}
function initAssetUserTable() {
var reachable = {{ asset.admin_user.REACHABLE }};
var unreachable = {{ asset.admin_user.UNREACHABLE }};
var options = {
ele: $('#asset_user_list'),
buttons: [],
order: [],
columnDefs: [
{targets: 3, createdCell: function (td, cellData) {
if (cellData === unreachable) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else if (cellData === reachable) {
$(td).html('<i class="fa fa-check text-navy"></i>')
} else {
$(td).html('')
}
}},
{targets: 4, createdCell: function (td, cellData) {
$(td).html(cellData.slice(0, -6));
}},
{targets: 5, createdCell: function (td, cellData) {
var update_auth_btn = ' <a class="btn btn-xs btn-primary btn-update-asset-user-auth" data-username="DEFAULT_USERNAME">{% trans "Update auth" %}</a>'.replace("DEFAULT_USERNAME", cellData);
{% if asset.protocol == 'ssh' %}
var test_btn = ' <a class="btn btn-xs btn-info btn-test-connective" data-username="DEFAULT_USERNAME">{% trans "Test" %}</a>'.replace("DEFAULT_USERNAME", cellData);
$(td).html(test_btn + update_auth_btn);
{% else %}
$(td).html(update_auth_btn);
{% endif %}
{#var check_btn = ' <a class="btn btn-xs btn-info btn-check-asset-user-auth" data-username="DEFAULT_USERNAME">{% trans "Check auth" %}</a>'.replace("DEFAULT_USERNAME", cellData);#}
}}
],
ajax_url: '{% url "api-assets:asset-user-list" %}' + '?asset_id={{ asset.id }}',
columns: [
{data: function (){return ''}}, {data: "username" },
{data: "version"}, {data: "connectivity"}, {data: "date_updated"},
{data: "username", orderable: false}
],
op_html: $('#actions').html()
};
jumpserver.initDataTable(options);
}
var username;
$(document).ready(function () {
initAssetUserTable();
})
{#.on('click', '.btn-check-asset-user-auth', function(){#}
{# var username = $(this).data('username');#}
{# var the_url = "{% url 'api-assets:asset-user-auth-info' %}" + '?asset_id={{ asset.id }}' + '&username=' + username;#}
{# $.ajax({#}
{# url: the_url,#}
{# method: 'GET',#}
{# success: function (data) {#}
{# alert("Password: " + data.password);#}
{# }#}
{# });#}
{# })#}
.on('click', '.btn-update-asset-user-auth', function() {
username = $(this).data('username');
var hostname = "{{ asset.hostname }}";
initAssetUserAuthModalForm(hostname, username);
$("#asset_user_auth_modal").modal();
})
.on('click', '#btn_asset_user_auth_modal_confirm', function(){
var password = $('#id_password').val();
if (password){
var data = {
'name': username,
'asset': "{{ asset.id }}",
'username': username,
'password': password
};
formSubmit({
data: data,
url: "{% url 'api-assets:asset-user-list' %}",
method: 'POST',
success: function () {
toastr.success("{% trans 'Update successfully!' %}");
},
error: function () {
toastr.error("{% trans 'Update failed!' %}");
}
});
$("#asset_user_auth_modal").modal('hide');
}
else{
$('#id_password').parent().addClass('has-error');
}
})
.on('click', '.btn-test-connective', function () {
var username = $(this).data('username');
var the_url = "{% url 'api-assets:asset-user-connective' %}" + "?asset_id={{ asset.id }}" + "&username=" + username;
var success = function (data) {
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
APIUpdateAttr({
url: the_url,
method: 'GET',
success: success,
flash_message: false
});
})
.on('click', '#btn-bulk-test-connective', function () {
var the_url = "{% url 'api-assets:asset-user-connective' %}" + "?asset_id={{ asset.id }}";
var success = function (data) {
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
APIUpdateAttr({
url: the_url,
method: 'GET',
success: success,
flash_message: false
});
})
</script>
{% endblock %}
\ No newline at end of file
......@@ -19,6 +19,9 @@
<li class="active">
<a href="{% url 'assets:asset-detail' pk=asset.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Asset detail' %} </a>
</li>
<li>
<a href="{% url 'assets:asset-user-list' pk=asset.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Asset user list' %} </a>
</li>
{% if user.is_superuser %}
<li class="pull-right">
<a class="btn btn-outline btn-default" href="{% url 'assets:asset-update' pk=asset.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
......@@ -32,7 +35,7 @@
</ul>
</div>
<div class="tab-content">
<div class="col-sm-7" style="padding-left: 0">
<div class="col-sm-8" style="padding-left: 0">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span class="label"><b>{{ asset.hostname }}</b></span>
......@@ -139,7 +142,7 @@
</div>
</div>
{% if user.is_superuser or user.is_org_admin %}
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
<div class="col-sm-4" style="padding-left: 0;padding-right: 0">
<div class="panel panel-primary">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Quick modify' %}
......
......@@ -132,6 +132,7 @@
</div>
</div>
</div>
{% include 'assets/_asset_user_auth_modal.html' %}
{% endblock %}
{% block custom_foot_js %}
<script>
......@@ -155,14 +156,15 @@ function initAssetsTable() {
$(td).html('')
}
}},
{targets: 4, createdCell: function (td, cellData) {
{targets: 4, createdCell: function (td, cellData, rowData) {
var push_btn = '';
{% if system_user.auto_push %}
push_btn = '<a class="btn btn-xs btn-primary btn-push-asset" data-uid="{{ DEFAULT_PK }}" >{% trans "Push" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
{% endif %}
var test_btn = ' <a class="btn btn-xs btn-info btn-test-asset" data-uid="{{ DEFAULT_PK }}" >{% trans "Test" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
{#var unbound_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-asset-unbound" data-uid="{{ DEFAULT_PK }}">{% trans "Unbound" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);#}
$(td).html(push_btn + test_btn);
var update_auth_btn = ' <a class="btn btn-xs btn-primary btn-update-asset-user-auth" data-aid="{{ DEFAULT_PK }}" data-hostname="hostname777">{% trans "Update auth" %}</a>'.replace("{{ DEFAULT_PK }}", cellData).replace("hostname777", rowData.hostname);
$(td).html(push_btn + test_btn + update_auth_btn);
}}
],
ajax_url: '{% url "api-assets:system-user-assets" pk=system_user.id %}',
......@@ -202,6 +204,15 @@ function updateSystemUserNode(nodes) {
}
jumpserver.nodes_selected = {};
function initAssetUserAuthModalForm(hostname, username){
$('#id_hostname_p').html(hostname);
$('#id_username_p').html(username);
$('#id_password').parent().removeClass('has-error');
$('#id_password').val('');
}
var assetId;
$(document).ready(function () {
$('.select2').select2()
.on('select2:select', function(evt) {
......@@ -315,6 +326,38 @@ $(document).ready(function () {
error: error
})
})
.on('click', '.btn-update-asset-user-auth', function() {
assetId = $(this).data('aid');
var hostname = $(this).data('hostname');
var username = '{{ system_user.username }}';
initAssetUserAuthModalForm(hostname, username);
$("#asset_user_auth_modal").modal();
})
.on('click', '#btn_asset_user_auth_modal_confirm', function(){
var password = $('#id_password').val();
if (password){
var data = {
'name': "{{ system_user.username }}",
'asset': assetId,
'username': "{{ system_user.username }}",
'password': password
};
formSubmit({
data: data,
url: "{% url 'api-assets:asset-user-list' %}",
method: 'POST',
success: function () {
toastr.success("{% trans 'Update successfully!' %}");
},
error: function () {
toastr.error("{% trans 'Update failed!' %}");
}
});
$("#asset_user_auth_modal").modal('hide');
}
else{
$('#id_password').parent().addClass('has-error');
}
})
</script>
{% endblock %}
......@@ -17,6 +17,7 @@ router.register(r'nodes', api.NodeViewSet, 'node')
router.register(r'domain', api.DomainViewSet, 'domain')
router.register(r'gateway', api.GatewayViewSet, 'gateway')
router.register(r'cmd-filter', api.CommandFilterViewSet, 'cmd-filter')
router.register(r'asset-user', api.AssetUserViewSet, 'asset-user')
cmd_filter_router = routers.NestedDefaultRouter(router, r'cmd-filter', lookup='filter')
cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-rule')
......@@ -31,6 +32,12 @@ urlpatterns = [
path('assets/<uuid:pk>/gateway/',
api.AssetGatewayApi.as_view(), name='asset-gateway'),
path('asset-user/auth-info/',
api.AssetUserAuthInfoApi.as_view(), name='asset-user-auth-info'),
path('asset-user/test-connective/',
api.AssetUserTestConnectiveApi.as_view(), name='asset-user-connective'),
path('admin-user/<uuid:pk>/nodes/',
api.ReplaceNodesAdminUserApi.as_view(), name='replace-nodes-admin-user'),
path('admin-user/<uuid:pk>/auth/',
......@@ -42,6 +49,8 @@ urlpatterns = [
path('system-user/<uuid:pk>/auth-info/',
api.SystemUserAuthInfoApi.as_view(), name='system-user-auth-info'),
path('system-user/<uuid:pk>/asset/<uuid:aid>/auth-info/',
api.SystemUserAssetAuthInfoApi.as_view(), name='system-user-asset-auth-info'),
path('system-user/<uuid:pk>/assets/',
api.SystemUserAssetsListView.as_view(), name='system-user-assets'),
path('system-user/<uuid:pk>/push/',
......@@ -79,6 +88,7 @@ urlpatterns = [
path('gateway/<uuid:pk>/test-connective/',
api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'),
]
urlpatterns += router.urls + cmd_filter_router.urls
......
......@@ -15,6 +15,8 @@ urlpatterns = [
path('asset/<uuid:pk>/update/', views.AssetUpdateView.as_view(), name='asset-update'),
path('asset/<uuid:pk>/delete/', views.AssetDeleteView.as_view(), name='asset-delete'),
path('asset/update/', views.AssetBulkUpdateView.as_view(), name='asset-bulk-update'),
# Asset user view
path('asset/<uuid:pk>/asset-user/', views.AssetUserListView.as_view(), name='asset-user-list'),
# User asset view
path('user-asset/', views.UserAssetListView.as_view(), name='user-asset-list'),
......
......@@ -34,7 +34,7 @@ from ..models import Asset, AdminUser, SystemUser, Label, Node, Domain
__all__ = [
'AssetListView', 'AssetCreateView', 'AssetUpdateView',
'AssetListView', 'AssetCreateView', 'AssetUpdateView', 'AssetUserListView',
'UserAssetListView', 'AssetBulkUpdateView', 'AssetDetailView',
'AssetDeleteView', 'AssetExportView', 'BulkImportAssetView',
]
......@@ -56,6 +56,20 @@ class AssetListView(AdminUserRequiredMixin, TemplateView):
return super().get_context_data(**kwargs)
class AssetUserListView(AdminUserRequiredMixin, DetailView):
model = Asset
context_object_name = 'asset'
template_name = 'assets/asset_asset_user_list.html'
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
'action': _('Asset user list'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class UserAssetListView(LoginRequiredMixin, TemplateView):
template_name = 'assets/user_asset_list.html'
......
......@@ -572,3 +572,6 @@ LOGIN_LOG_KEEP_DAYS = CONFIG.LOGIN_LOG_KEEP_DAYS
# User or user group permission cache time, default 3600 seconds
ASSETS_PERM_CACHE_TIME = CONFIG.ASSETS_PERM_CACHE_TIME
# Asset user auth external backend, default AuthBook backend
BACKEND_ASSET_USER_AUTH_VAULT = False
......@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Jumpserver 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-02-26 16:49+0800\n"
"POT-Creation-Date: 2019-03-14 16:26+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: Jumpserver team<ibuler@qq.com>\n"
......@@ -30,8 +30,8 @@ msgid "Test if the assets under the node are connectable: {}"
msgstr "测试节点下资产是否可连接: {}"
#: assets/forms/asset.py:27 assets/models/asset.py:80 assets/models/user.py:133
#: assets/templates/assets/asset_detail.html:191
#: assets/templates/assets/asset_detail.html:199
#: assets/templates/assets/asset_detail.html:194
#: assets/templates/assets/asset_detail.html:202
#: assets/templates/assets/system_user_asset.html:95 perms/models.py:32
msgid "Nodes"
msgstr "节点管理"
......@@ -39,7 +39,7 @@ msgstr "节点管理"
#: assets/forms/asset.py:30 assets/forms/asset.py:66 assets/forms/asset.py:105
#: assets/forms/asset.py:109 assets/models/asset.py:84
#: assets/models/cluster.py:19 assets/models/user.py:91
#: assets/templates/assets/asset_detail.html:77 templates/_nav.html:24
#: assets/templates/assets/asset_detail.html:80 templates/_nav.html:24
#: xpack/plugins/cloud/models.py:124
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:67
#: xpack/plugins/orgs/templates/orgs/org_list.html:18
......@@ -52,15 +52,15 @@ msgstr "管理用户"
#: assets/templates/assets/asset_list.html:81
#: assets/templates/assets/asset_update.html:41
#: assets/templates/assets/asset_update.html:43
#: assets/templates/assets/user_asset_list.html:34
#: assets/templates/assets/user_asset_list.html:33
#: xpack/plugins/orgs/templates/orgs/org_list.html:20
msgid "Label"
msgstr "标签"
#: assets/forms/asset.py:37 assets/forms/asset.py:73 assets/models/asset.py:79
#: assets/models/domain.py:26 assets/models/domain.py:52
#: assets/templates/assets/asset_detail.html:81
#: assets/templates/assets/user_asset_list.html:157
#: assets/templates/assets/asset_detail.html:84
#: assets/templates/assets/user_asset_list.html:163
#: xpack/plugins/orgs/templates/orgs/org_list.html:17
msgid "Domain"
msgstr "网域"
......@@ -104,16 +104,17 @@ msgstr "选择资产"
#: assets/forms/asset.py:101 assets/models/asset.py:77
#: assets/models/domain.py:50 assets/templates/assets/admin_user_assets.html:50
#: assets/templates/assets/asset_detail.html:69
#: assets/templates/assets/asset_detail.html:72
#: assets/templates/assets/domain_gateway_list.html:58
#: assets/templates/assets/system_user_asset.html:52
#: assets/templates/assets/user_asset_list.html:152
#: assets/templates/assets/user_asset_list.html:158
#: settings/templates/settings/replay_storage_create.html:59
msgid "Port"
msgstr "端口"
#: assets/forms/domain.py:15 assets/forms/label.py:13
#: assets/models/asset.py:277 assets/templates/assets/admin_user_list.html:28
#: assets/models/asset.py:279 assets/models/authbook.py:27
#: assets/templates/assets/admin_user_list.html:28
#: assets/templates/assets/domain_detail.html:60
#: assets/templates/assets/domain_list.html:26
#: assets/templates/assets/label_list.html:16
......@@ -129,6 +130,7 @@ msgstr "端口"
#: terminal/templates/terminal/command_list.html:73
#: terminal/templates/terminal/session_list.html:41
#: terminal/templates/terminal/session_list.html:72
#: xpack/plugins/change_asset_password_plan/models.py:17
#: xpack/plugins/cloud/models.py:187
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:65
#: xpack/plugins/orgs/templates/orgs/org_list.html:16
......@@ -153,7 +155,7 @@ msgstr "不能包含特殊字符"
#: assets/templates/assets/label_list.html:14
#: assets/templates/assets/system_user_detail.html:58
#: assets/templates/assets/system_user_list.html:29 ops/models/adhoc.py:37
#: ops/templates/ops/task_detail.html:60 ops/templates/ops/task_list.html:35
#: ops/templates/ops/task_detail.html:60 ops/templates/ops/task_list.html:27
#: orgs/models.py:12 perms/models.py:28
#: perms/templates/perms/asset_permission_detail.html:62
#: perms/templates/perms/asset_permission_list.html:53
......@@ -165,13 +167,14 @@ msgstr "不能包含特殊字符"
#: settings/templates/settings/terminal_setting.html:102 terminal/models.py:22
#: terminal/models.py:233 terminal/templates/terminal/terminal_detail.html:43
#: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14
#: users/models/user.py:55 users/templates/users/_select_user_modal.html:13
#: users/models/user.py:54 users/templates/users/_select_user_modal.html:13
#: users/templates/users/user_detail.html:63
#: users/templates/users/user_group_detail.html:55
#: users/templates/users/user_group_list.html:12
#: users/templates/users/user_list.html:23
#: users/templates/users/user_profile.html:51
#: users/templates/users/user_pubkey_update.html:53
#: xpack/plugins/change_asset_password_plan/models.py:15
#: xpack/plugins/cloud/models.py:49 xpack/plugins/cloud/models.py:119
#: xpack/plugins/cloud/templates/cloud/account_detail.html:52
#: xpack/plugins/cloud/templates/cloud/account_list.html:12
......@@ -183,20 +186,23 @@ msgid "Name"
msgstr "名称"
#: assets/forms/domain.py:71 assets/forms/user.py:81 assets/forms/user.py:143
#: assets/models/base.py:23 assets/templates/assets/admin_user_detail.html:60
#: assets/models/base.py:23
#: assets/templates/assets/_asset_user_auth_modal.html:15
#: assets/templates/assets/admin_user_detail.html:60
#: assets/templates/assets/admin_user_list.html:27
#: assets/templates/assets/asset_asset_user_list.html:48
#: assets/templates/assets/domain_gateway_list.html:60
#: assets/templates/assets/system_user_detail.html:62
#: assets/templates/assets/system_user_list.html:30
#: audits/templates/audits/login_log_list.html:49
#: perms/templates/perms/asset_permission_list.html:74
#: perms/templates/perms/asset_permission_user.html:55 users/forms.py:15
#: users/forms.py:33 users/models/authentication.py:77 users/models/user.py:53
#: users/templates/users/_select_user_modal.html:14
#: assets/templates/assets/system_user_list.html:30 audits/models.py:93
#: audits/templates/audits/login_log_list.html:49 authentication/forms.py:11
#: ops/models/adhoc.py:164 perms/templates/perms/asset_permission_list.html:74
#: perms/templates/perms/asset_permission_user.html:55 users/forms.py:13
#: users/models/user.py:52 users/templates/users/_select_user_modal.html:14
#: users/templates/users/login.html:64 users/templates/users/new_login.html:85
#: users/templates/users/user_detail.html:67
#: users/templates/users/user_list.html:24
#: users/templates/users/user_profile.html:47
#: xpack/plugins/change_asset_password_plan/models.py:16
msgid "Username"
msgstr "用户名"
......@@ -204,9 +210,12 @@ msgstr "用户名"
msgid "Password or private key passphrase"
msgstr "密码或密钥密码"
#: assets/forms/user.py:26 assets/models/base.py:24 settings/forms.py:103
#: users/forms.py:17 users/forms.py:35 users/forms.py:47
#: users/templates/users/login.html:67 users/templates/users/new_login.html:90
#: assets/forms/user.py:26 assets/models/base.py:24
#: assets/serializers/asset_user.py:19
#: assets/templates/assets/_asset_user_auth_modal.html:21
#: authentication/forms.py:13 settings/forms.py:103 users/forms.py:15
#: users/forms.py:27 users/templates/users/login.html:67
#: users/templates/users/new_login.html:90
#: users/templates/users/reset_password.html:53
#: users/templates/users/user_create.html:10
#: users/templates/users/user_password_authentication.html:18
......@@ -214,10 +223,13 @@ 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_asset_password_plan/models.py:19
msgid "Password"
msgstr "密码"
#: assets/forms/user.py:29 users/models/user.py:82
#: assets/forms/user.py:29 assets/serializers/asset_user.py:27
#: users/models/user.py:81
#: xpack/plugins/change_asset_password_plan/models.py:20
msgid "Private key"
msgstr "ssh私钥"
......@@ -264,72 +276,73 @@ msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig"
#: assets/models/asset.py:74 assets/models/domain.py:49
#: assets/templates/assets/_asset_list_modal.html:46
#: assets/templates/assets/admin_user_assets.html:49
#: assets/templates/assets/asset_detail.html:61
#: assets/templates/assets/asset_detail.html:64
#: assets/templates/assets/asset_list.html:93
#: assets/templates/assets/domain_gateway_list.html:57
#: assets/templates/assets/system_user_asset.html:51
#: assets/templates/assets/user_asset_list.html:46
#: assets/templates/assets/user_asset_list.html:151
#: assets/templates/assets/user_asset_list.html:45
#: assets/templates/assets/user_asset_list.html:157
#: audits/templates/audits/login_log_list.html:52
#: perms/templates/perms/asset_permission_asset.html:55 settings/forms.py:132
#: perms/templates/perms/asset_permission_asset.html:55 settings/forms.py:133
#: users/templates/users/user_granted_asset.html:45
#: users/templates/users/user_group_granted_asset.html:45
msgid "IP"
msgstr "IP"
#: assets/models/asset.py:75 assets/templates/assets/_asset_list_modal.html:45
#: assets/templates/assets/_asset_user_auth_modal.html:9
#: assets/templates/assets/admin_user_assets.html:48
#: assets/templates/assets/asset_detail.html:57
#: assets/templates/assets/asset_detail.html:60
#: assets/templates/assets/asset_list.html:92
#: assets/templates/assets/system_user_asset.html:50
#: assets/templates/assets/user_asset_list.html:45
#: assets/templates/assets/user_asset_list.html:150
#: assets/templates/assets/user_asset_list.html:44
#: assets/templates/assets/user_asset_list.html:156
#: perms/templates/perms/asset_permission_asset.html:54
#: perms/templates/perms/asset_permission_list.html:77 settings/forms.py:131
#: perms/templates/perms/asset_permission_list.html:77 settings/forms.py:132
#: users/templates/users/user_granted_asset.html:44
#: users/templates/users/user_group_granted_asset.html:44
msgid "Hostname"
msgstr "主机名"
#: assets/models/asset.py:76 assets/models/domain.py:51
#: assets/models/user.py:136 assets/templates/assets/asset_detail.html:73
#: assets/models/user.py:136 assets/templates/assets/asset_detail.html:76
#: assets/templates/assets/domain_gateway_list.html:59
#: assets/templates/assets/system_user_detail.html:70
#: assets/templates/assets/system_user_list.html:31
#: assets/templates/assets/user_asset_list.html:153
#: assets/templates/assets/user_asset_list.html:159
#: terminal/templates/terminal/session_list.html:75
msgid "Protocol"
msgstr "协议"
#: assets/models/asset.py:78 assets/templates/assets/asset_detail.html:105
#: assets/templates/assets/user_asset_list.html:154
#: assets/models/asset.py:78 assets/templates/assets/asset_detail.html:108
#: assets/templates/assets/user_asset_list.html:160
msgid "Platform"
msgstr "系统平台"
#: assets/models/asset.py:81 assets/models/cmd_filter.py:21
#: assets/models/domain.py:54 assets/models/label.py:22
#: assets/templates/assets/asset_detail.html:113
#: assets/templates/assets/user_asset_list.html:158
#: assets/templates/assets/asset_detail.html:116
#: assets/templates/assets/user_asset_list.html:164
msgid "Is active"
msgstr "激活"
#: assets/models/asset.py:87 assets/templates/assets/asset_detail.html:65
#: assets/models/asset.py:87 assets/templates/assets/asset_detail.html:68
msgid "Public IP"
msgstr "公网IP"
#: assets/models/asset.py:88 assets/templates/assets/asset_detail.html:121
#: assets/models/asset.py:88 assets/templates/assets/asset_detail.html:124
msgid "Asset number"
msgstr "资产编号"
#: assets/models/asset.py:91 assets/templates/assets/asset_detail.html:85
#: assets/models/asset.py:91 assets/templates/assets/asset_detail.html:88
msgid "Vendor"
msgstr "制造商"
#: assets/models/asset.py:92 assets/templates/assets/asset_detail.html:89
#: assets/models/asset.py:92 assets/templates/assets/asset_detail.html:92
msgid "Model"
msgstr "型号"
#: assets/models/asset.py:93 assets/templates/assets/asset_detail.html:117
#: assets/models/asset.py:93 assets/templates/assets/asset_detail.html:120
msgid "Serial number"
msgstr "序列号"
......@@ -350,7 +363,7 @@ msgstr "CPU核数"
msgid "CPU vcpus"
msgstr "CPU总数"
#: assets/models/asset.py:99 assets/templates/assets/asset_detail.html:97
#: assets/models/asset.py:99 assets/templates/assets/asset_detail.html:100
msgid "Memory"
msgstr "内存"
......@@ -362,8 +375,8 @@ msgstr "硬盘大小"
msgid "Disk info"
msgstr "硬盘信息"
#: assets/models/asset.py:103 assets/templates/assets/asset_detail.html:109
#: assets/templates/assets/user_asset_list.html:155
#: assets/models/asset.py:103 assets/templates/assets/asset_detail.html:112
#: assets/templates/assets/user_asset_list.html:161
msgid "OS"
msgstr "操作系统"
......@@ -380,7 +393,7 @@ msgid "Hostname raw"
msgstr "主机名原始"
#: assets/models/asset.py:108 assets/templates/assets/asset_create.html:34
#: assets/templates/assets/asset_detail.html:228
#: assets/templates/assets/asset_detail.html:231
#: assets/templates/assets/asset_update.html:39 templates/_nav.html:26
msgid "Labels"
msgstr "标签管理"
......@@ -389,13 +402,13 @@ msgstr "标签管理"
#: 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
#: assets/templates/assets/asset_detail.html:125
#: assets/templates/assets/asset_detail.html:128
#: assets/templates/assets/cmd_filter_detail.html:77
#: assets/templates/assets/domain_detail.html:72
#: assets/templates/assets/system_user_detail.html:100
#: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:15 perms/models.py:37
#: perms/models.py:90 perms/templates/perms/asset_permission_detail.html:98
#: users/models/user.py:96 users/templates/users/user_detail.html:111
#: users/models/user.py:95 users/templates/users/user_detail.html:111
#: xpack/plugins/cloud/models.py:55 xpack/plugins/cloud/models.py:127
msgid "Created by"
msgstr "创建者"
......@@ -424,7 +437,7 @@ msgstr "创建日期"
#: assets/models/domain.py:53 assets/models/group.py:23
#: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:72
#: assets/templates/assets/admin_user_list.html:32
#: assets/templates/assets/asset_detail.html:133
#: assets/templates/assets/asset_detail.html:136
#: assets/templates/assets/cmd_filter_detail.html:65
#: assets/templates/assets/cmd_filter_list.html:27
#: assets/templates/assets/cmd_filter_rule_list.html:62
......@@ -433,16 +446,17 @@ msgstr "创建日期"
#: assets/templates/assets/domain_list.html:28
#: assets/templates/assets/system_user_detail.html:104
#: assets/templates/assets/system_user_list.html:37
#: assets/templates/assets/user_asset_list.html:159 ops/models/adhoc.py:43
#: assets/templates/assets/user_asset_list.html:165 ops/models/adhoc.py:43
#: orgs/models.py:17 perms/models.py:39 perms/models.py:92
#: perms/templates/perms/asset_permission_detail.html:102 settings/models.py:34
#: terminal/models.py:32 terminal/templates/terminal/terminal_detail.html:63
#: users/models/group.py:15 users/models/user.py:88
#: users/models/group.py:15 users/models/user.py:87
#: users/templates/users/user_detail.html:127
#: users/templates/users/user_group_detail.html:67
#: users/templates/users/user_group_list.html:14
#: users/templates/users/user_profile.html:134 xpack/plugins/cloud/models.py:54
#: xpack/plugins/cloud/models.py:125
#: users/templates/users/user_profile.html:134
#: xpack/plugins/change_asset_password_plan/models.py:26
#: xpack/plugins/cloud/models.py:54 xpack/plugins/cloud/models.py:125
#: xpack/plugins/cloud/templates/cloud/account_detail.html:72
#: xpack/plugins/cloud/templates/cloud/account_list.html:15
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:71
......@@ -461,6 +475,7 @@ msgstr "不可达"
#: assets/models/asset.py:118 assets/models/base.py:35
#: assets/templates/assets/admin_user_assets.html:51
#: assets/templates/assets/admin_user_list.html:29
#: assets/templates/assets/asset_asset_user_list.html:50
#: assets/templates/assets/asset_list.html:95
#: assets/templates/assets/system_user_asset.html:53
#: assets/templates/assets/system_user_list.html:34
......@@ -469,10 +484,26 @@ msgid "Reachable"
msgstr "可连接"
#: assets/models/asset.py:119 assets/models/base.py:36
#: xpack/plugins/license/models.py:78
#: authentication/utils.py:9 xpack/plugins/license/models.py:78
msgid "Unknown"
msgstr "未知"
#: assets/models/authbook.py:28 ops/templates/ops/task_detail.html:72
msgid "Latest version"
msgstr "最新版本"
#: assets/models/authbook.py:29
#: assets/templates/assets/asset_asset_user_list.html:49
#: 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
msgid "AuthBook"
msgstr ""
#: assets/models/base.py:25
msgid "SSH private key"
msgstr "ssh密钥"
......@@ -489,7 +520,7 @@ msgstr "带宽"
msgid "Contact"
msgstr "联系人"
#: assets/models/cluster.py:22 users/models/user.py:74
#: assets/models/cluster.py:22 users/models/user.py:73
#: users/templates/users/user_detail.html:76
msgid "Phone"
msgstr "手机"
......@@ -515,7 +546,7 @@ msgid "Default"
msgstr "默认"
#: assets/models/cluster.py:36 assets/models/label.py:14
#: users/models/user.py:441
#: users/models/user.py:457
msgid "System"
msgstr "系统"
......@@ -596,6 +627,7 @@ msgstr "每行一个命令"
#: assets/models/cmd_filter.py:54
#: assets/templates/assets/admin_user_assets.html:52
#: assets/templates/assets/admin_user_list.html:33
#: assets/templates/assets/asset_asset_user_list.html:52
#: assets/templates/assets/asset_list.html:96
#: assets/templates/assets/cmd_filter_list.html:28
#: assets/templates/assets/cmd_filter_rule_list.html:63
......@@ -607,7 +639,7 @@ msgstr "每行一个命令"
#: audits/templates/audits/operate_log_list.html:41
#: audits/templates/audits/operate_log_list.html:67
#: ops/templates/ops/adhoc_history.html:59 ops/templates/ops/task_adhoc.html:64
#: ops/templates/ops/task_history.html:65 ops/templates/ops/task_list.html:42
#: ops/templates/ops/task_history.html:65 ops/templates/ops/task_list.html:34
#: perms/templates/perms/asset_permission_list.html:60
#: settings/templates/settings/terminal_setting.html:82
#: settings/templates/settings/terminal_setting.html:104
......@@ -657,8 +689,8 @@ msgstr "默认资产组"
#: terminal/templates/terminal/command_list.html:32
#: terminal/templates/terminal/command_list.html:72
#: terminal/templates/terminal/session_list.html:33
#: terminal/templates/terminal/session_list.html:71 users/forms.py:303
#: users/models/user.py:33 users/models/user.py:429
#: terminal/templates/terminal/session_list.html:71 users/forms.py:283
#: users/models/user.py:32 users/models/user.py:445
#: users/templates/users/user_group_detail.html:78
#: users/templates/users/user_group_list.html:13 users/views/user.py:386
#: xpack/plugins/orgs/forms.py:26
......@@ -680,7 +712,7 @@ msgstr "分类"
msgid "Key"
msgstr "键"
#: assets/models/node.py:127
#: assets/models/node.py:128
msgid "New node"
msgstr "新节点"
......@@ -699,18 +731,19 @@ msgstr "手动登录"
#: 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:50
#: assets/views/asset.py:89 assets/views/asset.py:133 assets/views/asset.py:150
#: assets/views/asset.py:174 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
#: assets/views/asset.py:66 assets/views/asset.py:103 assets/views/asset.py:147
#: assets/views/asset.py:164 assets/views/asset.py:188
#: 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
msgid "Assets"
msgstr "资产管理"
......@@ -733,7 +766,7 @@ msgstr "Shell"
msgid "Login mode"
msgstr "登录模式"
#: assets/models/user.py:247 assets/templates/assets/user_asset_list.html:156
#: assets/models/user.py:247 assets/templates/assets/user_asset_list.html:162
#: audits/models.py:19 audits/templates/audits/ftp_log_list.html:49
#: audits/templates/audits/ftp_log_list.html:72 perms/forms.py:48
#: perms/models.py:33 perms/models.py:87
......@@ -755,71 +788,85 @@ msgstr "系统用户"
msgid "%(value)s is not an even number"
msgstr "%(value)s is not an even number"
#: assets/tasks.py:33
#: assets/serializers/asset_user.py:23 users/forms.py:230
#: users/models/user.py:84 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
#: users/templates/users/user_pubkey_update.html:43
#: xpack/plugins/change_asset_password_plan/models.py:21
msgid "Public key"
msgstr "ssh公钥"
#: assets/tasks.py:31
msgid "Asset has been disabled, skipped: {}"
msgstr "资产或许不支持ansible, 跳过: {}"
#: assets/tasks.py:37
#: assets/tasks.py:35
msgid "Asset may not be support ansible, skipped: {}"
msgstr "资产或许不支持ansible, 跳过: {}"
#: assets/tasks.py:42
#: assets/tasks.py:48
msgid "No assets matched, stop task"
msgstr "没有匹配到资产,结束任务"
#: assets/tasks.py:67
#: assets/tasks.py:73
msgid "Get asset info failed: {}"
msgstr "获取资产信息失败:{}"
#: assets/tasks.py:117
#: assets/tasks.py:123
msgid "Update some assets hardware info"
msgstr "更新资产硬件信息"
#: assets/tasks.py:134
#: assets/tasks.py:140
msgid "Update asset hardware info: {}"
msgstr "更新资产硬件信息: {}"
#: assets/tasks.py:159
#: assets/tasks.py:165
msgid "Test assets connectivity"
msgstr "测试资产可连接性"
#: assets/tasks.py:183
#: assets/tasks.py:189
msgid "Test assets connectivity: {}"
msgstr "测试资产可连接性: {}"
#: assets/tasks.py:225
#: assets/tasks.py:231
msgid "Test admin user connectivity period: {}"
msgstr "定期测试管理账号可连接性: {}"
#: assets/tasks.py:232
#: assets/tasks.py:238
msgid "Test admin user connectivity: {}"
msgstr "测试管理行号可连接性: {}"
#: assets/tasks.py:271
#: assets/tasks.py:277
msgid "Test system user connectivity: {}"
msgstr "测试系统用户可连接性: {}"
#: assets/tasks.py:278
#: assets/tasks.py:284
msgid "Test system user connectivity: {} => {}"
msgstr "测试系统用户可连接性: {} => {}"
#: assets/tasks.py:291
#: assets/tasks.py:297
msgid "Test system user connectivity period: {}"
msgstr "定期测试系统用户可连接性: {}"
#: assets/tasks.py:368
#: assets/tasks.py:374
msgid ""
"Push system user task skip, auto push not enable or protocol is not ssh: {}"
msgstr "推送系统用户任务跳过,自动推送没有打开,或协议不是ssh: {}"
#: assets/tasks.py:388 assets/tasks.py:402
#: assets/tasks.py:396 assets/tasks.py:410
msgid "Push system users to assets: {}"
msgstr "推送系统用户到入资产: {}"
#: assets/tasks.py:394
#: assets/tasks.py:402
msgid "Push system users to asset: {} => {}"
msgstr "推送系统用户到入资产: {} => {}"
#: assets/tasks.py:459
msgid "Test asset user connectivity: {}"
msgstr "测试资产用户可连接性: {}"
#: assets/templates/assets/_asset_group_bulk_update_modal.html:5
msgid "Update asset group"
msgstr "更新用户组"
......@@ -835,7 +882,7 @@ msgstr "选择资产"
#: assets/templates/assets/_asset_group_bulk_update_modal.html:21
#: assets/templates/assets/cmd_filter_detail.html:89
#: assets/templates/assets/cmd_filter_list.html:26
#: assets/templates/assets/user_asset_list.html:48
#: assets/templates/assets/user_asset_list.html:47
#: users/templates/users/user_granted_asset.html:47
msgid "System users"
msgstr "系统用户"
......@@ -875,6 +922,14 @@ msgstr "如果设置了id,则会使用该行信息更新该id的资产"
msgid "Asset list"
msgstr "资产列表"
#: assets/templates/assets/_asset_user_auth_modal.html:4
msgid "Update asset user auth"
msgstr "更新资产用户认证信息"
#: assets/templates/assets/_asset_user_auth_modal.html:23
msgid "Please input password"
msgstr "请输入密码"
#: assets/templates/assets/_system_user.html:37
#: assets/templates/assets/asset_create.html:16
#: assets/templates/assets/asset_update.html:21
......@@ -971,7 +1026,8 @@ msgid "Submit"
msgstr "提交"
#: assets/templates/assets/_user_asset_detail_modal.html:11
#: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:175
#: assets/templates/assets/asset_asset_user_list.html:17
#: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:189
msgid "Asset detail"
msgstr "资产详情"
......@@ -1015,22 +1071,47 @@ msgid "Quick update"
msgstr "快速更新"
#: assets/templates/assets/admin_user_assets.html:70
#: assets/templates/assets/asset_detail.html:176
#: assets/templates/assets/asset_asset_user_list.html:71
#: assets/templates/assets/asset_detail.html:179
msgid "Test connective"
msgstr "测试可连接性"
#: assets/templates/assets/admin_user_assets.html:73
#: assets/templates/assets/admin_user_assets.html:113
#: assets/templates/assets/asset_detail.html:179
#: assets/templates/assets/admin_user_assets.html:114
#: assets/templates/assets/asset_asset_user_list.html:74
#: assets/templates/assets/asset_asset_user_list.html:122
#: assets/templates/assets/asset_detail.html:182
#: assets/templates/assets/system_user_asset.html:75
#: assets/templates/assets/system_user_asset.html:163
#: assets/templates/assets/system_user_asset.html:164
#: assets/templates/assets/system_user_detail.html:151
msgid "Test"
msgstr "测试"
#: assets/templates/assets/admin_user_assets.html:115
#: assets/templates/assets/asset_asset_user_list.html:120
#: assets/templates/assets/system_user_asset.html:166
msgid "Update auth"
msgstr "更新认证"
#: assets/templates/assets/admin_user_assets.html:191
#: assets/templates/assets/asset_asset_user_list.html:176
#: assets/templates/assets/asset_detail.html:311
#: assets/templates/assets/system_user_asset.html:350
#: users/templates/users/user_detail.html:307
#: users/templates/users/user_detail.html:334
#: xpack/plugins/interface/views.py:31
msgid "Update successfully!"
msgstr "更新成功"
#: assets/templates/assets/admin_user_assets.html:194
#: assets/templates/assets/asset_asset_user_list.html:179
#: assets/templates/assets/system_user_asset.html:353
msgid "Update failed!"
msgstr "更新失败"
#: assets/templates/assets/admin_user_detail.html:24
#: assets/templates/assets/admin_user_list.html:88
#: assets/templates/assets/asset_detail.html:24
#: assets/templates/assets/asset_detail.html:27
#: assets/templates/assets/asset_list.html:177
#: assets/templates/assets/cmd_filter_detail.html:29
#: assets/templates/assets/cmd_filter_list.html:57
......@@ -1062,7 +1143,7 @@ msgstr "更新"
#: assets/templates/assets/admin_user_detail.html:28
#: assets/templates/assets/admin_user_list.html:89
#: assets/templates/assets/asset_detail.html:28
#: assets/templates/assets/asset_detail.html:31
#: assets/templates/assets/asset_list.html:178
#: assets/templates/assets/cmd_filter_detail.html:33
#: assets/templates/assets/cmd_filter_list.html:58
......@@ -1074,7 +1155,7 @@ msgstr "更新"
#: assets/templates/assets/label_list.html:39
#: assets/templates/assets/system_user_detail.html:30
#: assets/templates/assets/system_user_list.html:93 audits/models.py:33
#: ops/templates/ops/task_list.html:72
#: ops/templates/ops/task_list.html:64
#: perms/templates/perms/asset_permission_detail.html:34
#: perms/templates/perms/asset_permission_list.html:178
#: settings/templates/settings/terminal_setting.html:90
......@@ -1104,7 +1185,7 @@ msgid "Select nodes"
msgstr "选择节点"
#: assets/templates/assets/admin_user_detail.html:100
#: assets/templates/assets/asset_detail.html:208
#: assets/templates/assets/asset_detail.html:211
#: assets/templates/assets/asset_list.html:636
#: assets/templates/assets/cmd_filter_detail.html:106
#: assets/templates/assets/system_user_asset.html:112
......@@ -1155,6 +1236,29 @@ msgstr "创建管理用户"
msgid "Ratio"
msgstr "比例"
#: assets/templates/assets/asset_asset_user_list.html:20
#: assets/templates/assets/asset_detail.html:23 assets/views/asset.py:67
msgid "Asset user list"
msgstr "资产用户列表"
#: assets/templates/assets/asset_asset_user_list.html:28
msgid "Asset users of"
msgstr "资产用户"
#: assets/templates/assets/asset_asset_user_list.html:51
#: assets/templates/assets/cmd_filter_detail.html:73
msgid "Date updated"
msgstr "更新日期"
#: assets/templates/assets/asset_asset_user_list.html:64
#: assets/templates/assets/asset_detail.html:148
#: terminal/templates/terminal/session_detail.html:81
#: users/templates/users/user_detail.html:138
#: users/templates/users/user_profile.html:146
#: xpack/plugins/license/templates/license/license_detail.html:93
msgid "Quick modify"
msgstr "快速修改"
#: assets/templates/assets/asset_bulk_update.html:8
#: users/templates/users/user_bulk_update.html:8
msgid "Select properties that need to be modified"
......@@ -1165,30 +1269,22 @@ msgstr "选择需要修改属性"
msgid "Select all"
msgstr "全选"
#: assets/templates/assets/asset_detail.html:93
#: assets/templates/assets/asset_detail.html:96
msgid "CPU"
msgstr "CPU"
#: assets/templates/assets/asset_detail.html:101
#: assets/templates/assets/asset_detail.html:104
msgid "Disk"
msgstr "硬盘"
#: assets/templates/assets/asset_detail.html:129
#: assets/templates/assets/asset_detail.html:132
#: users/templates/users/user_detail.html:115
#: users/templates/users/user_profile.html:104
msgid "Date joined"
msgstr "创建日期"
#: assets/templates/assets/asset_detail.html:145
#: terminal/templates/terminal/session_detail.html:81
#: users/templates/users/user_detail.html:138
#: users/templates/users/user_profile.html:146
#: xpack/plugins/license/templates/license/license_detail.html:93
msgid "Quick modify"
msgstr "快速修改"
#: assets/templates/assets/asset_detail.html:151
#: assets/templates/assets/user_asset_list.html:47 perms/models.py:34
#: assets/templates/assets/asset_detail.html:154
#: assets/templates/assets/user_asset_list.html:46 perms/models.py:34
#: perms/models.py:88
#: perms/templates/perms/asset_permission_create_update.html:52
#: perms/templates/perms/asset_permission_detail.html:120
......@@ -1201,21 +1297,14 @@ msgstr "快速修改"
msgid "Active"
msgstr "激活中"
#: assets/templates/assets/asset_detail.html:168
#: assets/templates/assets/asset_detail.html:171
msgid "Refresh hardware"
msgstr "更新硬件信息"
#: assets/templates/assets/asset_detail.html:171
#: assets/templates/assets/asset_detail.html:174
msgid "Refresh"
msgstr "刷新"
#: assets/templates/assets/asset_detail.html:308
#: users/templates/users/user_detail.html:307
#: users/templates/users/user_detail.html:334
#: xpack/plugins/interface/views.py:31
msgid "Update successfully!"
msgstr "更新成功"
#: assets/templates/assets/asset_list.html:8
msgid ""
"The left side is the asset tree, right click to create, delete, and change "
......@@ -1225,7 +1314,7 @@ msgstr ""
"左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织的,"
"右侧是属于该节点下的资产"
#: assets/templates/assets/asset_list.html:69 assets/views/asset.py:90
#: assets/templates/assets/asset_list.html:69 assets/views/asset.py:104
msgid "Create asset"
msgstr "创建资产"
......@@ -1378,10 +1467,6 @@ msgstr "配置"
msgid "Rules"
msgstr "规则"
#: assets/templates/assets/cmd_filter_detail.html:73
msgid "Date updated"
msgstr "更新日期"
#: assets/templates/assets/cmd_filter_detail.html:97
msgid "Binding to system user"
msgstr "绑定到系统用户"
......@@ -1500,7 +1585,7 @@ msgid "Push system user now"
msgstr "立刻推送系统"
#: assets/templates/assets/system_user_asset.html:84
#: assets/templates/assets/system_user_asset.html:161
#: assets/templates/assets/system_user_asset.html:162
#: assets/templates/assets/system_user_detail.html:142
msgid "Push"
msgstr "推送"
......@@ -1585,23 +1670,23 @@ msgstr "更新管理用户"
msgid "Admin user detail"
msgstr "管理用户详情"
#: assets/views/asset.py:64 templates/_nav_user.html:4
#: assets/views/asset.py:78 templates/_nav_user.html:4
msgid "My assets"
msgstr "我的资产"
#: assets/views/asset.py:104
#: assets/views/asset.py:118
msgid "Bulk update asset success"
msgstr "批量更新资产成功"
#: assets/views/asset.py:134
#: assets/views/asset.py:148
msgid "Bulk update asset"
msgstr "批量更新资产"
#: assets/views/asset.py:151
#: assets/views/asset.py:165
msgid "Update asset"
msgstr "更新资产"
#: assets/views/asset.py:292
#: assets/views/asset.py:306
msgid "already exists"
msgstr "已经存在"
......@@ -1695,9 +1780,10 @@ msgstr "操作"
msgid "Filename"
msgstr "文件名"
#: audits/models.py:22 audits/templates/audits/ftp_log_list.html:76
#: audits/models.py:22 audits/models.py:89
#: audits/templates/audits/ftp_log_list.html:76
#: ops/templates/ops/command_execution_list.html:64
#: ops/templates/ops/task_list.html:39 users/models/authentication.py:73
#: ops/templates/ops/task_list.html:31
#: users/templates/users/user_detail.html:458 xpack/plugins/cloud/api.py:62
msgid "Success"
msgstr "成功"
......@@ -1719,6 +1805,79 @@ msgstr "资源"
msgid "Change by"
msgstr "修改者"
#: audits/models.py:69 users/templates/users/user_detail.html:98
msgid "Disabled"
msgstr "禁用"
#: audits/models.py:70 settings/models.py:33
#: users/templates/users/user_detail.html:96
msgid "Enabled"
msgstr "启用"
#: audits/models.py:71 audits/models.py:81
msgid "-"
msgstr ""
#: audits/models.py:82
msgid "Username/password check failed"
msgstr "用户名/密码 校验失败"
#: audits/models.py:83
msgid "MFA authentication failed"
msgstr "MFA 认证失败"
#: audits/models.py:84
msgid "Username does not exist"
msgstr "用户名不存在"
#: audits/models.py:85
msgid "Password expired"
msgstr "密码过期"
#: audits/models.py:90 xpack/plugins/cloud/models.py:164
#: xpack/plugins/cloud/models.py:178
msgid "Failed"
msgstr "失败"
#: audits/models.py:94
msgid "Login type"
msgstr "登录方式"
#: audits/models.py:95
msgid "Login ip"
msgstr "登录IP"
#: audits/models.py:96
msgid "Login city"
msgstr "登录城市"
#: audits/models.py:97
msgid "User agent"
msgstr "Agent"
#: audits/models.py:98 audits/templates/audits/login_log_list.html:54
#: users/forms.py:142 users/models/user.py:76
#: users/templates/users/first_login.html:45
msgid "MFA"
msgstr "MFA"
#: audits/models.py:99 audits/templates/audits/login_log_list.html:55
#: xpack/plugins/cloud/models.py:172
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:69
msgid "Reason"
msgstr "原因"
#: audits/models.py:100 audits/templates/audits/login_log_list.html:56
#: xpack/plugins/cloud/models.py:171 xpack/plugins/cloud/models.py:188
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:70
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:67
msgid "Status"
msgstr "状态"
#: audits/models.py:101
msgid "Date login"
msgstr "登录日期"
#: audits/templates/audits/ftp_log_list.html:77
#: ops/templates/ops/adhoc_history.html:52
#: ops/templates/ops/adhoc_history_detail.html:61
......@@ -1740,7 +1899,7 @@ msgstr "选择用户"
#: audits/templates/audits/password_change_log_list.html:42
#: ops/templates/ops/command_execution_list.html:42
#: ops/templates/ops/command_execution_list.html:47
#: ops/templates/ops/task_list.html:21 ops/templates/ops/task_list.html:26
#: ops/templates/ops/task_list.html:13 ops/templates/ops/task_list.html:18
#: templates/_base_list.html:43 templates/_header_bar.html:8
#: terminal/templates/terminal/command_list.html:60
#: terminal/templates/terminal/session_list.html:61
......@@ -1767,28 +1926,8 @@ msgstr "Agent"
msgid "City"
msgstr "城市"
#: audits/templates/audits/login_log_list.html:54 users/forms.py:162
#: users/models/authentication.py:82 users/models/user.py:77
#: users/templates/users/first_login.html:45
msgid "MFA"
msgstr "MFA"
#: audits/templates/audits/login_log_list.html:55
#: users/models/authentication.py:83 xpack/plugins/cloud/models.py:172
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:69
msgid "Reason"
msgstr "原因"
#: audits/templates/audits/login_log_list.html:56
#: users/models/authentication.py:84 xpack/plugins/cloud/models.py:171
#: xpack/plugins/cloud/models.py:188
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:70
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:67
msgid "Status"
msgstr "状态"
#: audits/templates/audits/login_log_list.html:57
#: ops/templates/ops/task_list.html:40
#: ops/templates/ops/task_list.html:32
msgid "Date"
msgstr "日期"
......@@ -1800,31 +1939,128 @@ msgstr "日期"
msgid "Datetime"
msgstr "日期"
#: audits/views.py:71 audits/views.py:115 audits/views.py:151
#: audits/views.py:195 audits/views.py:226 templates/_nav.html:72
#: audits/views.py:70 audits/views.py:114 audits/views.py:150
#: audits/views.py:194 audits/views.py:225 templates/_nav.html:72
msgid "Audits"
msgstr "日志审计"
#: audits/views.py:72 templates/_nav.html:76
#: audits/views.py:71 templates/_nav.html:76
msgid "FTP log"
msgstr "FTP日志"
#: audits/views.py:116 templates/_nav.html:77
#: audits/views.py:115 templates/_nav.html:77
msgid "Operate log"
msgstr "操作日志"
#: audits/views.py:152 templates/_nav.html:78
#: audits/views.py:151 templates/_nav.html:78
msgid "Password change log"
msgstr "改密日志"
#: audits/views.py:196 templates/_nav.html:75
#: audits/views.py:195 templates/_nav.html:75
msgid "Login log"
msgstr "登录日志"
#: audits/views.py:227 ops/views/command.py:44
#: audits/views.py:226 ops/views/command.py:44
msgid "Command execution list"
msgstr "命令执行列表"
#: authentication/api/auth.py:46 users/templates/users/login.html:52
#: users/templates/users/new_login.html:71
msgid "Log in frequently and try again later"
msgstr "登录频繁, 稍后重试"
#: authentication/api/auth.py:64
msgid "The user {} password has expired, please update."
msgstr "用户 {} 密码已经过期,请更新。"
#: authentication/api/auth.py:83
msgid "Please carry seed value and conduct MFA secondary certification"
msgstr "请携带seed值, 进行MFA二次认证"
#: authentication/api/auth.py:163
msgid "Please verify the user name and password first"
msgstr "请先进行用户名和密码验证"
#: authentication/api/auth.py:168
msgid "MFA certification failed"
msgstr "MFA认证失败"
#: authentication/backends/api.py:52
msgid "Invalid signature header. No credentials provided."
msgstr ""
#: authentication/backends/api.py:55
msgid "Invalid signature header. Signature string should not contain spaces."
msgstr ""
#: authentication/backends/api.py:62
msgid "Invalid signature header. Format like AccessKeyId:Signature"
msgstr ""
#: authentication/backends/api.py:66
msgid ""
"Invalid signature header. Signature string should not contain invalid "
"characters."
msgstr ""
#: authentication/backends/api.py:86 authentication/backends/api.py:102
msgid "Invalid signature."
msgstr ""
#: authentication/backends/api.py:93
msgid "HTTP header: Date not provide or not %a, %d %b %Y %H:%M:%S GMT"
msgstr ""
#: authentication/backends/api.py:98
msgid "Expired, more than 15 minutes"
msgstr ""
#: authentication/backends/api.py:105
msgid "User disabled."
msgstr "用户已禁用"
#: authentication/backends/api.py:120
msgid "Invalid token header. No credentials provided."
msgstr ""
#: authentication/backends/api.py:123
msgid "Invalid token header. Sign string should not contain spaces."
msgstr ""
#: authentication/backends/api.py:130
msgid ""
"Invalid token header. Sign string should not contain invalid characters."
msgstr ""
#: authentication/backends/api.py:140
msgid "Invalid token or cache refreshed."
msgstr ""
#: authentication/forms.py:29 users/forms.py:21
msgid "MFA code"
msgstr "MFA 验证码"
#: authentication/models.py:33
msgid "Private Token"
msgstr "ssh密钥"
#: authentication/views/login.py:75
msgid "Please enable cookies and try again."
msgstr "设置你的浏览器支持cookie"
#: authentication/views/login.py:167 users/views/user.py:532
#: users/views/user.py:557
msgid "MFA code invalid, or ntp sync server time"
msgstr "MFA验证码不正确,或者服务器端时间不对"
#: authentication/views/login.py:198
msgid "Logout success"
msgstr "退出登录成功"
#: authentication/views/login.py:199
msgid "Logout success, return login page"
msgstr "退出登录成功,返回到登录页面"
#: common/const.py:6
#, python-format
msgid "<b>%(name)s</b> was created successfully"
......@@ -1873,19 +2109,20 @@ msgstr ""
msgid "Waiting task start"
msgstr "等待任务开始"
#: ops/models/adhoc.py:38
#: ops/models/adhoc.py:38 xpack/plugins/change_asset_password_plan/models.py:22
msgid "Interval"
msgstr "间隔"
#: ops/models/adhoc.py:38 settings/forms.py:150
#: ops/models/adhoc.py:38 settings/forms.py:151
#: xpack/plugins/change_asset_password_plan/models.py:22
msgid "Units: seconds"
msgstr "单位: 秒"
#: ops/models/adhoc.py:39
#: ops/models/adhoc.py:39 xpack/plugins/change_asset_password_plan/models.py:23
msgid "Crontab"
msgstr "Crontab"
#: ops/models/adhoc.py:39
#: ops/models/adhoc.py:39 xpack/plugins/change_asset_password_plan/models.py:23
msgid "5 * * * *"
msgstr ""
......@@ -1908,7 +2145,7 @@ msgstr "选项"
#: ops/models/adhoc.py:161 ops/templates/ops/adhoc_detail.html:53
#: ops/templates/ops/command_execution_list.html:58
#: ops/templates/ops/task_adhoc.html:59 ops/templates/ops/task_list.html:38
#: ops/templates/ops/task_adhoc.html:59 ops/templates/ops/task_list.html:30
#: settings/templates/settings/command_storage_create.html:49
msgid "Hosts"
msgstr "主机"
......@@ -1951,7 +2188,7 @@ msgid "End time"
msgstr "完成时间"
#: ops/models/adhoc.py:325 ops/templates/ops/adhoc_history.html:57
#: ops/templates/ops/task_history.html:63 ops/templates/ops/task_list.html:41
#: ops/templates/ops/task_history.html:63 ops/templates/ops/task_list.html:33
msgid "Time"
msgstr "时间"
......@@ -1997,7 +2234,7 @@ msgid "Version detail"
msgstr "版本详情"
#: ops/templates/ops/adhoc_detail.html:22
#: ops/templates/ops/adhoc_history.html:22 ops/views/adhoc.py:127
#: ops/templates/ops/adhoc_history.html:22 ops/views/adhoc.py:122
msgid "Version run history"
msgstr "执行历史"
......@@ -2008,7 +2245,7 @@ msgstr "执行历史"
msgid "Run as"
msgstr "运行用户"
#: ops/templates/ops/adhoc_detail.html:94 ops/templates/ops/task_list.html:36
#: ops/templates/ops/adhoc_detail.html:94 ops/templates/ops/task_list.html:28
msgid "Run times"
msgstr "执行次数"
......@@ -2055,13 +2292,7 @@ msgstr "执行历史"
msgid "F/S/T"
msgstr "失败/成功/总"
#: 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 "版本"
#: ops/templates/ops/adhoc_history_detail.html:19 ops/views/adhoc.py:140
#: ops/templates/ops/adhoc_history_detail.html:19 ops/views/adhoc.py:135
msgid "Run history detail"
msgstr "执行历史详情"
......@@ -2137,12 +2368,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:75
#: ops/templates/ops/task_history.html:19 ops/views/adhoc.py:70
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:88
#: ops/templates/ops/task_history.html:22 ops/views/adhoc.py:83
msgid "Task versions"
msgstr "任务各版本"
......@@ -2164,24 +2395,20 @@ msgstr "版本"
msgid "Total versions"
msgstr "版本数量"
#: ops/templates/ops/task_detail.html:72
msgid "Latest version"
msgstr "最新版本"
#: ops/templates/ops/task_detail.html:92
msgid "Contents"
msgstr "内容"
#: ops/templates/ops/task_list.html:37
#: ops/templates/ops/task_list.html:29
msgid "Versions"
msgstr "版本"
#: ops/templates/ops/task_list.html:71
#: ops/templates/ops/task_list.html:63
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:52
msgid "Run"
msgstr "执行"
#: ops/templates/ops/task_list.html:125
#: ops/templates/ops/task_list.html:117
msgid "Task start: "
msgstr "任务开始: "
......@@ -2189,17 +2416,17 @@ msgstr "任务开始: "
msgid "Update task content: {}"
msgstr "更新任务内容: {}"
#: ops/views/adhoc.py:49 ops/views/adhoc.py:74 ops/views/adhoc.py:87
#: ops/views/adhoc.py:100 ops/views/adhoc.py:113 ops/views/adhoc.py:126
#: ops/views/adhoc.py:139 ops/views/command.py:43 ops/views/command.py:67
#: 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:43 ops/views/command.py:67
msgid "Ops"
msgstr "作业中心"
#: ops/views/adhoc.py:50 templates/_nav.html:66
#: ops/views/adhoc.py:45 templates/_nav.html:66
msgid "Task list"
msgstr "任务列表"
#: ops/views/adhoc.py:101
#: ops/views/adhoc.py:96
msgid "Task run history"
msgstr "执行历史"
......@@ -2216,7 +2443,7 @@ msgstr "组织管理"
#: perms/templates/perms/asset_permission_list.html:55
#: perms/templates/perms/asset_permission_list.html:75
#: perms/templates/perms/asset_permission_list.html:122 templates/_nav.html:14
#: users/forms.py:273 users/models/group.py:26 users/models/user.py:61
#: users/forms.py:253 users/models/group.py:26 users/models/user.py:60
#: users/templates/users/_select_user_modal.html:16
#: users/templates/users/user_detail.html:213
#: users/templates/users/user_list.html:26
......@@ -2234,7 +2461,7 @@ msgstr "资产和节点至少选一个"
#: perms/models.py:36 perms/models.py:89
#: perms/templates/perms/asset_permission_detail.html:90
#: users/models/user.py:93 users/templates/users/user_detail.html:107
#: users/models/user.py:92 users/templates/users/user_detail.html:107
#: users/templates/users/user_profile.html:116
msgid "Date expired"
msgstr "失效日期"
......@@ -2435,7 +2662,7 @@ msgstr "SMTP密码"
msgid "Some provider use token except password"
msgstr "一些邮件提供商需要输入的是Token"
#: settings/forms.py:86 settings/forms.py:124
#: settings/forms.py:86 settings/forms.py:125
msgid "Use SSL"
msgstr "使用SSL"
......@@ -2467,20 +2694,20 @@ msgstr "用户OU"
msgid "Use | split User OUs"
msgstr "使用|分隔各OU"
#: settings/forms.py:111
#: settings/forms.py:112
msgid "User search filter"
msgstr "用户过滤器"
#: settings/forms.py:112
#: settings/forms.py:113
#, python-format
msgid "Choice may be (cn|uid|sAMAccountName)=%(user)s)"
msgstr "可能的选项是(cn或uid或sAMAccountName=%(user)s)"
#: settings/forms.py:115
#: settings/forms.py:116
msgid "User attr map"
msgstr "LDAP属性映射"
#: settings/forms.py:117
#: settings/forms.py:118
msgid ""
"User attr map present how to map LDAP user attr to jumpserver, username,name,"
"email is jumpserver attr"
......@@ -2488,43 +2715,43 @@ msgstr ""
"用户属性映射代表怎样将LDAP中用户属性映射到jumpserver用户上,username, name,"
"email 是jumpserver的属性"
#: settings/forms.py:126
#: settings/forms.py:127
msgid "Enable LDAP auth"
msgstr "启用LDAP认证"
#: settings/forms.py:135
#: settings/forms.py:136
msgid "All"
msgstr "全部"
#: settings/forms.py:136
#: settings/forms.py:137
msgid "Auto"
msgstr "自动"
#: settings/forms.py:143
#: settings/forms.py:144
msgid "Password auth"
msgstr "密码认证"
#: settings/forms.py:146
#: settings/forms.py:147
msgid "Public key auth"
msgstr "密钥认证"
#: settings/forms.py:149
#: settings/forms.py:150
msgid "Heartbeat interval"
msgstr "心跳间隔"
#: settings/forms.py:153
#: settings/forms.py:154
msgid "List sort by"
msgstr "资产列表排序"
#: settings/forms.py:156
#: settings/forms.py:157
msgid "List page size"
msgstr "资产分页每页数量"
#: settings/forms.py:159
#: settings/forms.py:160
msgid "Session keep duration"
msgstr "会话保留时长"
#: settings/forms.py:160
#: settings/forms.py:161
msgid ""
"Units: days, Session, record, command will be delete if more than duration, "
"only in database"
......@@ -2532,54 +2759,54 @@ msgstr ""
"单位:天。 会话、录像、命令记录超过该时长将会被删除(仅影响数据库存储, oss等不"
"受影响)"
#: settings/forms.py:164
#: settings/forms.py:165
msgid "Telnet login regex"
msgstr "Telnet 成功正则表达式"
#: settings/forms.py:165
#: settings/forms.py:166
msgid "ex: Last\\s*login|success|成功"
msgstr ""
"登录telnet服务器成功后的提示正则表达式,如: Last\\s*login|success|成功 "
#: settings/forms.py:176
#: settings/forms.py:177
msgid "MFA Secondary certification"
msgstr "MFA 二次认证"
#: settings/forms.py:178
#: settings/forms.py:179
msgid ""
"After opening, the user login must use MFA secondary authentication (valid "
"for all users, including administrators)"
msgstr "开启后,用户登录必须使用MFA二次认证(对所有用户有效,包括管理员)"
#: settings/forms.py:184
#: settings/forms.py:185
msgid "Limit the number of login failures"
msgstr "限制登录失败次数"
#: settings/forms.py:188
#: settings/forms.py:189
msgid "No logon interval"
msgstr "禁止登录时间间隔"
#: settings/forms.py:190
#: settings/forms.py:191
msgid ""
"Tip: (unit/minute) if the user has failed to log in for a limited number of "
"times, no login is allowed during this time interval."
msgstr ""
"提示:(单位:分)当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录"
#: settings/forms.py:196
#: settings/forms.py:197
msgid "Connection max idle time"
msgstr "SSH最大空闲时间"
#: settings/forms.py:198
#: settings/forms.py:199
msgid ""
"If idle time more than it, disconnect connection(only ssh now) Unit: minute"
msgstr "提示:(单位:分)如果超过该配置没有操作,连接会被断开(仅ssh)"
#: settings/forms.py:204
#: settings/forms.py:205
msgid "Password expiration time"
msgstr "密码过期时间"
#: settings/forms.py:207
#: settings/forms.py:208
msgid ""
"Tip: (unit: day) If the user does not update the password during the time, "
"the user password will expire failure;The password expiration reminder mail "
......@@ -2589,55 +2816,50 @@ msgstr ""
"提示:(单位:天)如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期"
"提醒邮件将在密码过期前5天内由系统(每天)自动发送给用户"
#: settings/forms.py:216
#: settings/forms.py:217
msgid "Password minimum length"
msgstr "密码最小长度 "
#: settings/forms.py:220
#: settings/forms.py:221
msgid "Must contain capital letters"
msgstr "必须包含大写字母"
#: settings/forms.py:222
#: settings/forms.py:223
msgid ""
"After opening, the user password changes and resets must contain uppercase "
"letters"
msgstr "开启后,用户密码修改、重置必须包含大写字母"
#: settings/forms.py:227
#: settings/forms.py:228
msgid "Must contain lowercase letters"
msgstr "必须包含小写字母"
#: settings/forms.py:228
#: settings/forms.py:229
msgid ""
"After opening, the user password changes and resets must contain lowercase "
"letters"
msgstr "开启后,用户密码修改、重置必须包含小写字母"
#: settings/forms.py:233
#: settings/forms.py:234
msgid "Must contain numeric characters"
msgstr "必须包含数字字符"
#: settings/forms.py:234
#: settings/forms.py:235
msgid ""
"After opening, the user password changes and resets must contain numeric "
"characters"
msgstr "开启后,用户密码修改、重置必须包含数字字符"
#: settings/forms.py:239
#: settings/forms.py:240
msgid "Must contain special characters"
msgstr "必须包含特殊字符"
#: settings/forms.py:240
#: settings/forms.py:241
msgid ""
"After opening, the user password changes and resets must contain special "
"characters"
msgstr "开启后,用户密码修改、重置必须包含特殊字符"
#: settings/models.py:33 users/models/authentication.py:54
#: users/templates/users/user_detail.html:96
msgid "Enabled"
msgstr "启用"
#: settings/models.py:126 users/templates/users/reset_password.html:68
#: users/templates/users/user_profile.html:20
msgid "Setting"
......@@ -2832,7 +3054,7 @@ msgstr "文档"
msgid "Commercial support"
msgstr "商业支持"
#: templates/_header_bar.html:89 templates/_nav_user.html:14 users/forms.py:141
#: templates/_header_bar.html:89 templates/_nav_user.html:14 users/forms.py:121
#: users/templates/users/_user.html:43
#: users/templates/users/first_login.html:39
#: users/templates/users/user_password_update.html:40
......@@ -2926,7 +3148,7 @@ msgstr ""
#: 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:360 users/views/user.py:68 users/views/user.py:83
#: users/views/login.py:151 users/views/user.py:68 users/views/user.py:83
#: users/views/user.py:113 users/views/user.py:194 users/views/user.py:355
#: users/views/user.py:405 users/views/user.py:445
msgid "Users"
......@@ -3374,87 +3596,11 @@ msgid ""
"You should use your ssh client tools connect terminal: {} <br /> <br />{}"
msgstr "你可以使用ssh客户端工具连接终端"
#: users/api/auth.py:40 users/templates/users/login.html:52
#: users/templates/users/new_login.html:71
msgid "Log in frequently and try again later"
msgstr "登录频繁, 稍后重试"
#: users/api/auth.py:67
msgid "The user {} password has expired, please update."
msgstr "用户 {} 密码已经过期,请更新。"
#: users/api/auth.py:92
msgid "Please carry seed value and conduct MFA secondary certification"
msgstr "请携带seed值, 进行MFA二次认证"
#: users/api/auth.py:204
msgid "Please verify the user name and password first"
msgstr "请先进行用户名和密码验证"
#: users/api/auth.py:216
msgid "MFA certification failed"
msgstr "MFA认证失败"
#: users/api/user.py:145
msgid "Could not reset self otp, use profile reset instead"
msgstr "不能再该页面重置MFA, 请去个人信息页面重置"
#: users/authentication.py:53
msgid "Invalid signature header. No credentials provided."
msgstr ""
#: users/authentication.py:56
msgid "Invalid signature header. Signature string should not contain spaces."
msgstr ""
#: users/authentication.py:63
msgid "Invalid signature header. Format like AccessKeyId:Signature"
msgstr ""
#: users/authentication.py:67
msgid ""
"Invalid signature header. Signature string should not contain invalid "
"characters."
msgstr ""
#: users/authentication.py:87 users/authentication.py:103
msgid "Invalid signature."
msgstr ""
#: users/authentication.py:94
msgid "HTTP header: Date not provide or not %a, %d %b %Y %H:%M:%S GMT"
msgstr ""
#: users/authentication.py:99
msgid "Expired, more than 15 minutes"
msgstr ""
#: users/authentication.py:106
msgid "User disabled."
msgstr "用户已禁用"
#: users/authentication.py:121
msgid "Invalid token header. No credentials provided."
msgstr ""
#: users/authentication.py:124
msgid "Invalid token header. Sign string should not contain spaces."
msgstr ""
#: users/authentication.py:131
msgid ""
"Invalid token header. Sign string should not contain invalid characters."
msgstr ""
#: users/authentication.py:142
msgid "Invalid token or cache refreshed."
msgstr ""
#: users/forms.py:41
msgid "MFA code"
msgstr "MFA 验证码"
#: users/forms.py:52 users/models/user.py:65
#: users/forms.py:32 users/models/user.py:64
#: users/templates/users/_select_user_modal.html:15
#: users/templates/users/user_detail.html:87
#: users/templates/users/user_list.html:25
......@@ -3462,31 +3608,31 @@ msgstr "MFA 验证码"
msgid "Role"
msgstr "角色"
#: users/forms.py:55 users/forms.py:220
#: users/forms.py:35 users/forms.py:200
msgid "ssh public key"
msgstr "ssh公钥"
#: users/forms.py:56 users/forms.py:221
#: users/forms.py:36 users/forms.py:201
msgid "ssh-rsa AAAA..."
msgstr ""
#: users/forms.py:57
#: users/forms.py:37
msgid "Paste user id_rsa.pub here."
msgstr "复制用户公钥到这里"
#: users/forms.py:71 users/templates/users/user_detail.html:221
#: users/forms.py:51 users/templates/users/user_detail.html:221
msgid "Join user groups"
msgstr "添加到用户组"
#: users/forms.py:105 users/forms.py:235
#: users/forms.py:85 users/forms.py:215
msgid "Public key should not be the same as your old one."
msgstr "不能和原来的密钥相同"
#: users/forms.py:109 users/forms.py:239 users/serializers/v1.py:38
#: users/forms.py:89 users/forms.py:219 users/serializers/v1.py:38
msgid "Not a valid ssh public key"
msgstr "ssh密钥不合法"
#: users/forms.py:147
#: users/forms.py:127
msgid ""
"Tip: when enabled, you will enter the MFA binding process the next time you "
"log in. you can also directly bind in \"personal information -> quick "
......@@ -3495,11 +3641,11 @@ msgstr ""
"提示:启用之后您将会在下次登录时进入MFA绑定流程;您也可以在(个人信息->快速修"
"改->更改MFA设置)中直接绑定!"
#: users/forms.py:157
#: users/forms.py:137
msgid "* Enable MFA authentication to make the account more secure."
msgstr "* 启用MFA认证,使账号更加安全."
#: users/forms.py:167
#: users/forms.py:147
msgid ""
"In order to protect you and your company, please keep your account, password "
"and key sensitive information properly. (for example: setting complex "
......@@ -3508,163 +3654,101 @@ msgstr ""
"为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:"
"设置复杂密码,启用MFA认证)"
#: users/forms.py:174 users/templates/users/first_login.html:48
#: users/forms.py:154 users/templates/users/first_login.html:48
#: users/templates/users/first_login.html:107
#: users/templates/users/first_login.html:130
msgid "Finish"
msgstr "完成"
#: users/forms.py:180
#: users/forms.py:160
msgid "Old password"
msgstr "原来密码"
#: users/forms.py:185
#: users/forms.py:165
msgid "New password"
msgstr "新密码"
#: users/forms.py:190
#: users/forms.py:170
msgid "Confirm password"
msgstr "确认密码"
#: users/forms.py:200
#: users/forms.py:180
msgid "Old password error"
msgstr "原来密码错误"
#: users/forms.py:208
#: users/forms.py:188
msgid "Password does not match"
msgstr "密码不一致"
#: users/forms.py:218
#: users/forms.py:198
msgid "Automatically configure and download the SSH key"
msgstr "自动配置并下载SSH密钥"
#: users/forms.py:222
#: users/forms.py:202
msgid "Paste your id_rsa.pub here."
msgstr "复制你的公钥到这里"
#: users/forms.py:250 users/models/user.py:85
#: 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
#: users/templates/users/user_pubkey_update.html:43
msgid "Public key"
msgstr "ssh公钥"
#: users/forms.py:256 users/forms.py:261 users/forms.py:307
#: users/forms.py:236 users/forms.py:241 users/forms.py:287
#: xpack/plugins/orgs/forms.py:30
msgid "Select users"
msgstr "选择用户"
#: users/models/authentication.py:39
msgid "Private Token"
msgstr "ssh密钥"
#: users/models/authentication.py:53 users/templates/users/user_detail.html:98
msgid "Disabled"
msgstr "禁用"
#: users/models/authentication.py:55 users/models/authentication.py:65
msgid "-"
msgstr ""
#: users/models/authentication.py:66
msgid "Username/password check failed"
msgstr "用户名/密码 校验失败"
#: users/models/authentication.py:67
msgid "MFA authentication failed"
msgstr "MFA 认证失败"
#: users/models/authentication.py:68
msgid "Username does not exist"
msgstr "用户名不存在"
#: users/models/authentication.py:69
msgid "Password expired"
msgstr "密码过期"
#: users/models/authentication.py:74 xpack/plugins/cloud/models.py:164
#: xpack/plugins/cloud/models.py:178
msgid "Failed"
msgstr "失败"
#: users/models/authentication.py:78
msgid "Login type"
msgstr "登录方式"
#: users/models/authentication.py:79
msgid "Login ip"
msgstr "登录IP"
#: users/models/authentication.py:80
msgid "Login city"
msgstr "登录城市"
#: users/models/authentication.py:81
msgid "User agent"
msgstr "Agent"
#: users/models/authentication.py:85
msgid "Date login"
msgstr "登录日期"
#: users/models/user.py:32 users/models/user.py:437
#: users/models/user.py:31 users/models/user.py:453
msgid "Administrator"
msgstr "管理员"
#: users/models/user.py:34
#: users/models/user.py:33
msgid "Application"
msgstr "应用程序"
#: users/models/user.py:37 users/templates/users/user_profile.html:92
#: users/models/user.py:36 users/templates/users/user_profile.html:92
#: users/templates/users/user_profile.html:159
#: users/templates/users/user_profile.html:162
msgid "Disable"
msgstr "禁用"
#: users/models/user.py:38 users/templates/users/user_profile.html:90
#: users/models/user.py:37 users/templates/users/user_profile.html:90
#: users/templates/users/user_profile.html:166
msgid "Enable"
msgstr "启用"
#: users/models/user.py:39 users/templates/users/user_profile.html:88
#: users/models/user.py:38 users/templates/users/user_profile.html:88
msgid "Force enable"
msgstr "强制启用"
#: users/models/user.py:57 users/templates/users/user_detail.html:71
#: users/models/user.py:56 users/templates/users/user_detail.html:71
#: users/templates/users/user_profile.html:59
msgid "Email"
msgstr "邮件"
#: users/models/user.py:68
#: users/models/user.py:67
msgid "Avatar"
msgstr "头像"
#: users/models/user.py:71 users/templates/users/user_detail.html:82
#: users/models/user.py:70 users/templates/users/user_detail.html:82
msgid "Wechat"
msgstr "微信"
#: users/models/user.py:100 users/templates/users/user_detail.html:103
#: users/models/user.py:99 users/templates/users/user_detail.html:103
#: users/templates/users/user_list.html:27
#: users/templates/users/user_profile.html:100
msgid "Source"
msgstr "用户来源"
#: users/models/user.py:104
#: users/models/user.py:103
msgid "Date password last updated"
msgstr "最后更新密码日期"
#: users/models/user.py:128 users/templates/users/user_update.html:22
#: users/views/login.py:254 users/views/login.py:313 users/views/user.py:418
#: users/models/user.py:129 users/templates/users/user_update.html:22
#: users/views/login.py:45 users/views/login.py:104 users/views/user.py:418
msgid "User auth from {}, go there change password"
msgstr "用户认证源来自 {}, 请去相应系统修改密码"
#: users/models/user.py:440
#: users/models/user.py:456
msgid "Administrator is the super user of system"
msgstr "Administrator是初始的超级管理员"
#: users/serializers/v2.py:40
#: users/serializers/v2.py:34
msgid "name not unique"
msgstr "名称重复"
......@@ -3880,7 +3964,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:78
#: users/templates/users/user_detail.html:373 users/utils.py:77
msgid "Reset password"
msgstr "重置密码"
......@@ -4204,11 +4288,11 @@ msgstr "新的公钥已设置成功,请下载对应的私钥"
msgid "Update user"
msgstr "更新用户"
#: users/utils.py:39
#: users/utils.py:38
msgid "Create account successfully"
msgstr "创建账户成功"
#: users/utils.py:41
#: users/utils.py:40
#, python-format
msgid ""
"\n"
......@@ -4253,7 +4337,7 @@ msgstr ""
" </br>\n"
" "
#: users/utils.py:80
#: users/utils.py:79
#, python-format
msgid ""
"\n"
......@@ -4297,11 +4381,11 @@ msgstr ""
" </br>\n"
" "
#: users/utils.py:111
#: users/utils.py:110
msgid "Security notice"
msgstr "安全通知"
#: users/utils.py:113
#: users/utils.py:112
#, python-format
msgid ""
"\n"
......@@ -4350,11 +4434,11 @@ msgstr ""
" </br>\n"
" "
#: users/utils.py:149
#: users/utils.py:148
msgid "SSH Key Reset"
msgstr "重置ssh密钥"
#: users/utils.py:151
#: users/utils.py:150
#, python-format
msgid ""
"\n"
......@@ -4379,15 +4463,15 @@ msgstr ""
" </br>\n"
" "
#: users/utils.py:184
#: users/utils.py:183
msgid "User not exist"
msgstr "用户不存在"
#: users/utils.py:186
#: users/utils.py:185
msgid "Disabled or expired"
msgstr "禁用或失效"
#: users/utils.py:199
#: users/utils.py:198
msgid "Password or SSH public key invalid"
msgstr "密码或密钥不合法"
......@@ -4403,56 +4487,40 @@ msgstr "更新用户组"
msgid "User group granted asset"
msgstr "用户组授权资产"
#: users/views/login.py:80
msgid "Please enable cookies and try again."
msgstr "设置你的浏览器支持cookie"
#: users/views/login.py:202 users/views/user.py:532 users/views/user.py:557
msgid "MFA code invalid, or ntp sync server time"
msgstr "MFA验证码不正确,或者服务器端时间不对"
#: users/views/login.py:234
msgid "Logout success"
msgstr "退出登录成功"
#: users/views/login.py:235
msgid "Logout success, return login page"
msgstr "退出登录成功,返回到登录页面"
#: users/views/login.py:251
#: users/views/login.py:42
msgid "Email address invalid, please input again"
msgstr "邮箱地址错误,重新输入"
#: users/views/login.py:267
#: users/views/login.py:58
msgid "Send reset password message"
msgstr "发送重置密码邮件"
#: users/views/login.py:268
#: users/views/login.py:59
msgid "Send reset password mail success, login your mail box and follow it "
msgstr ""
"发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)"
#: users/views/login.py:281
#: users/views/login.py:72
msgid "Reset password success"
msgstr "重置密码成功"
#: users/views/login.py:282
#: users/views/login.py:73
msgid "Reset password success, return to login page"
msgstr "重置密码成功,返回到登录页面"
#: users/views/login.py:297 users/views/login.py:316
#: users/views/login.py:88 users/views/login.py:107
msgid "Token invalid or expired"
msgstr "Token错误或失效"
#: users/views/login.py:309
#: users/views/login.py:100
msgid "Password not same"
msgstr "密码不一致"
#: users/views/login.py:322 users/views/user.py:128 users/views/user.py:428
#: users/views/login.py:113 users/views/user.py:128 users/views/user.py:428
msgid "* Your password does not meet the requirements"
msgstr "* 您的密码不符合要求"
#: users/views/login.py:360
#: users/views/login.py:151
msgid "First login"
msgstr "首次登录"
......
......@@ -4,11 +4,16 @@
from .ansible.inventory import BaseInventory
from assets.utils import get_assets_by_id_list, get_system_user_by_id
from common.utils import get_logger
__all__ = [
'JMSInventory'
]
logger = get_logger(__file__)
class JMSInventory(BaseInventory):
"""
JMS Inventory is the manager with jumpserver assets, so you can
......@@ -18,7 +23,7 @@ class JMSInventory(BaseInventory):
"""
:param host_id_list: ["test1", ]
:param run_as_admin: True 是否使用管理用户去执行, 每台服务器的管理用户可能不同
:param run_as: 是否统一使用某个系统用户去执行
:param run_as: 用户名(添加了统一的资产用户管理器之后AssetUserManager加上之后修改为username)
:param become_info: 是否become成某个用户去执行
"""
self.assets = assets
......@@ -33,8 +38,8 @@ class JMSInventory(BaseInventory):
host_list.append(info)
if run_as:
run_user_info = self.get_run_user_info()
for host in host_list:
run_user_info = self.get_run_user_info(host)
host.update(run_user_info)
if become_info:
......@@ -69,12 +74,20 @@ class JMSInventory(BaseInventory):
info["groups"].append("domain_"+asset.domain.name)
return info
def get_run_user_info(self):
system_user = self.run_as
if not system_user:
def get_run_user_info(self, host):
from assets.backends.multi import AssetUserManager
if not self.run_as:
return {}
try:
asset = self.assets.get(id=host.get('id'))
run_user = AssetUserManager.get(self.run_as, asset)
except Exception as e:
logger.error(e, exc_info=True)
return {}
else:
return system_user._to_secret_json()
return run_user._to_secret_json()
@staticmethod
def make_proxy_command(asset):
......
......@@ -149,7 +149,7 @@ class AdHoc(models.Model):
_options: ansible options, more see ops.ansible.runner.Options
_hosts: ["hostname1", "hostname2"], hostname must be unique key of cmdb
run_as_admin: if true, then need get every host admin user run it, because every host may be have different admin user, so we choise host level
run_as: if not run as admin, it run it as a system/common user from cmdb
run_as: username(Add the uniform AssetUserManager <AssetUserManager> and change it to username)
_become: May be using become [sudo, su] options. {method: "sudo", user: "user", pass: "pass"]
pattern: Even if we set _hosts, We only use that to make inventory, We also can set `patter` to run task on match hosts
"""
......@@ -161,7 +161,7 @@ class AdHoc(models.Model):
_hosts = models.TextField(blank=True, verbose_name=_('Hosts')) # ['hostname1', 'hostname2']
hosts = models.ManyToManyField('assets.Asset', verbose_name=_("Host"))
run_as_admin = models.BooleanField(default=False, verbose_name=_('Run as admin'))
run_as = models.ForeignKey('assets.SystemUser', null=True, on_delete=models.CASCADE)
run_as = models.CharField(max_length=64, default='', null=True, verbose_name=_('Username'))
_become = models.CharField(max_length=1024, default='', verbose_name=_("Become"))
created_by = models.CharField(max_length=64, default='', null=True, verbose_name=_('Create by'))
date_created = models.DateTimeField(auto_now_add=True, db_index=True)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment