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

Merge pull request #2642 from jumpserver/dev

Dev
parents cf2455c0 990c78e7
......@@ -201,8 +201,7 @@ Jumpserver 采纳分布式架构,支持多机房跨区域部署,中心节点
### License & Copyright
----
Copyright (c) 2014-2019 Beijing Duizhan Tech, Inc., All rights reserved.
Copyright (c) 2014-2019 飞致云 FIT2CLOUD, All rights reserved.
Licensed under The GNU General Public License version 2 (GPLv2) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
......
# -*- coding: utf-8 -*-
#
import uuid
import random
from rest_framework import generics
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
from rest_framework.pagination import LimitOffsetPagination
from django.utils.translation import ugettext_lazy as _
from django.shortcuts import get_object_or_404
from django.urls import reverse_lazy
from django.core.cache import cache
from django.db.models import Q
from common.mixins import IDInFilterMixin
from common.utils import get_logger
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX
from ..models import Asset, AdminUser, Node
from .. import serializers
from ..tasks import update_asset_hardware_info_manual, \
......@@ -25,7 +31,7 @@ logger = get_logger(__file__)
__all__ = [
'AssetViewSet', 'AssetListUpdateApi',
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi',
'AssetGatewayApi'
'AssetGatewayApi', 'AssetBulkUpdateSelectAPI'
]
......@@ -92,6 +98,21 @@ class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
permission_classes = (IsOrgAdmin,)
class AssetBulkUpdateSelectAPI(APIView):
permission_classes = (IsOrgAdmin,)
def post(self, request, *args, **kwargs):
assets_id = request.data.get('assets_id', '')
if assets_id:
spm = uuid.uuid4().hex
key = CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX.format(spm)
cache.set(key, assets_id, 300)
url = reverse_lazy('assets:asset-bulk-update') + '?spm=%s' % spm
return Response({'url': url})
error = _('Please select assets that need to be updated')
return Response({'error': error}, status=400)
class AssetRefreshHardwareApi(generics.RetrieveAPIView):
"""
Refresh asset hardware info
......
......@@ -48,3 +48,6 @@ TASK_OPTIONS = {
'timeout': 10,
'forks': 10,
}
CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX = '_KEY_ASSET_BULK_UPDATE_ID_{}'
......@@ -3,6 +3,8 @@
from django.core.cache import cache
from rest_framework import serializers
from common.serializers import AdaptedBulkListSerializer
from ..models import Node, AdminUser
from ..const import ADMIN_USER_CONN_CACHE_KEY
......@@ -18,6 +20,7 @@ class AdminUserSerializer(serializers.ModelSerializer):
reachable_amount = serializers.SerializerMethodField()
class Meta:
list_serializer_class = AdaptedBulkListSerializer
model = AdminUser
fields = '__all__'
......
# -*- coding: utf-8 -*-
#
from rest_framework import serializers
from rest_framework_bulk.serializers import BulkListSerializer
from common.mixins import BulkSerializerMixin
from common.serializers import AdaptedBulkListSerializer
from ..models import Asset
from .system_user import AssetSystemUserSerializer
......@@ -19,7 +19,7 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
"""
class Meta:
model = Asset
list_serializer_class = BulkListSerializer
list_serializer_class = AdaptedBulkListSerializer
fields = '__all__'
validators = []
......
......@@ -3,6 +3,7 @@
from rest_framework import serializers
from common.fields import ChoiceDisplayField
from common.serializers import AdaptedBulkListSerializer
from ..models import CommandFilter, CommandFilterRule, SystemUser
......@@ -12,6 +13,7 @@ class CommandFilterSerializer(serializers.ModelSerializer):
class Meta:
model = CommandFilter
list_serializer_class = AdaptedBulkListSerializer
fields = '__all__'
......@@ -21,3 +23,4 @@ class CommandFilterRuleSerializer(serializers.ModelSerializer):
class Meta:
model = CommandFilterRule
fields = '__all__'
list_serializer_class = AdaptedBulkListSerializer
......@@ -2,6 +2,8 @@
#
from rest_framework import serializers
from common.serializers import AdaptedBulkListSerializer
from ..models import Domain, Gateway
......@@ -12,6 +14,7 @@ class DomainSerializer(serializers.ModelSerializer):
class Meta:
model = Domain
fields = '__all__'
list_serializer_class = AdaptedBulkListSerializer
@staticmethod
def get_asset_count(obj):
......@@ -25,6 +28,7 @@ class DomainSerializer(serializers.ModelSerializer):
class GatewaySerializer(serializers.ModelSerializer):
class Meta:
model = Gateway
list_serializer_class = AdaptedBulkListSerializer
fields = [
'id', 'name', 'ip', 'port', 'protocol', 'username',
'domain', 'is_active', 'date_created', 'date_updated',
......
# -*- coding: utf-8 -*-
#
from rest_framework import serializers
from rest_framework_bulk.serializers import BulkListSerializer
from common.serializers import AdaptedBulkListSerializer
from ..models import Label
......@@ -12,7 +13,7 @@ class LabelSerializer(serializers.ModelSerializer):
class Meta:
model = Label
fields = '__all__'
list_serializer_class = BulkListSerializer
list_serializer_class = AdaptedBulkListSerializer
@staticmethod
def get_asset_count(obj):
......
from rest_framework import serializers
from common.serializers import AdaptedBulkListSerializer
from ..models import SystemUser, Asset
from .base import AuthSerializer
......@@ -17,6 +19,7 @@ class SystemUserSerializer(serializers.ModelSerializer):
class Meta:
model = SystemUser
exclude = ('_password', '_private_key', '_public_key')
list_serializer_class = AdaptedBulkListSerializer
def get_field_names(self, declared_fields, info):
fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info)
......@@ -61,13 +64,19 @@ class AssetSystemUserSerializer(serializers.ModelSerializer):
"""
查看授权的资产系统用户的数据结构,这个和AssetSerializer不同,字段少
"""
actions = serializers.SerializerMethodField()
class Meta:
model = SystemUser
fields = (
'id', 'name', 'username', 'priority',
'protocol', 'comment', 'login_mode'
'protocol', 'comment', 'login_mode', 'actions',
)
@staticmethod
def get_actions(obj):
return [action.name for action in obj.actions]
class SystemUserSimpleSerializer(serializers.ModelSerializer):
"""
......
......@@ -98,6 +98,7 @@ function initTable() {
order: [],
columnDefs: [
{targets: 0, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
var detail_btn = '<a href="{% url "assets:asset-detail" pk=DEFAULT_PK %}" data-aid="'+rowData.id+'">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
......
......@@ -44,9 +44,10 @@ $(document).ready(function(){
var options = {
ele: $('#admin_user_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
{targets: 1, render: function (cellData, tp, rowData, meta) {
cellData = htmlEscape(cellData);
var detail_btn = '<a href="{% url "assets:admin-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
return detail_btn.replace('{{ DEFAULT_PK }}', rowData.id);
}},
{targets: 4, createdCell: function (td, cellData) {
var innerHtml = "";
......@@ -82,7 +83,6 @@ $(document).ready(function(){
innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>";
}
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
}},
{targets: 8, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:admin-user-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
......@@ -90,8 +90,8 @@ $(document).ready(function(){
$(td).html(update_btn + del_btn)
}}],
ajax_url: '{% url "api-assets:admin-user-list" %}',
columns: [{data: function(){return ""}}, {data: "name" }, {data: "username" }, {data: "assets_amount" },
{data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment" }, {data: "id" }]
columns: [{data: function(){return ""}}, {data: "name"}, {data: "username" }, {data: "assets_amount" },
{data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment"}, {data: "id"}]
};
jumpserver.initServerSideDataTable(options)
})
......
......@@ -156,6 +156,7 @@ function initTable() {
ele: $('#asset_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
{% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %}
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
......@@ -657,9 +658,23 @@ $(document).ready(function(){
});
}
function doUpdate() {
var id_list_string = id_list.join(',');
var url = "{% url 'assets:asset-bulk-update' %}?assets_id=" + id_list_string;
location.href = url
var data = {
'assets_id':id_list
};
function error(data) {
toastr.error(JSON.parse(data).error)
}
function success(data) {
location.href = data.url;
}
APIUpdateAttr({
'url': "{% url 'api-assets:asset-bulk-update-select' %}",
'method': 'POST',
'body': JSON.stringify(data),
'flash_message': false,
'success': success,
'error': error,
})
}
function doRemove() {
......
......@@ -40,6 +40,7 @@ function initTable() {
ele: $('#cmd_filter_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
var detail_btn = '<a href="{% url 'assets:cmd-filter-detail' pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
......
......@@ -41,6 +41,7 @@ function initTable() {
ele: $('#domain_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
var detail_btn = '<a href="{% url "assets:domain-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
......
......@@ -30,6 +30,7 @@ function initTable() {
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
{# var detail_btn = '<a href="{% url "assets:label-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';#}
cellData = htmlEscape(cellData);
var detail_btn = '<a>' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
......
......@@ -144,6 +144,7 @@ function initAssetsTable() {
order: [],
columnDefs: [
{targets: 0, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
var detail_btn = '<a href="{% url "assets:asset-detail" pk=DEFAULT_PK %}" data-aid="'+rowData.id+'">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
......
......@@ -49,6 +49,7 @@ function initTable() {
ele: $('#system_user_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
var detail_btn = '<a href="{% url "assets:system-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
......
......@@ -25,6 +25,8 @@ cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-r
urlpatterns = [
path('assets-bulk/', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'),
path('asset/update/select/',
api.AssetBulkUpdateSelectAPI.as_view(), name='asset-bulk-update-select'),
path('assets/<uuid:pk>/refresh/',
api.AssetRefreshHardwareApi.as_view(), name='asset-refresh'),
path('assets/<uuid:pk>/alive/',
......
......@@ -28,6 +28,7 @@ from common.mixins import JSONResponseMixin
from common.utils import get_object_or_none, get_logger
from common.permissions import AdminUserRequiredMixin
from common.const import create_success_msg, update_success_msg
from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX
from orgs.utils import current_org
from .. import forms
from ..models import Asset, AdminUser, SystemUser, Label, Node, Domain
......@@ -120,15 +121,12 @@ class AssetBulkUpdateView(AdminUserRequiredMixin, ListView):
form = None
def get(self, request, *args, **kwargs):
assets_id = self.request.GET.get('assets_id', '')
self.id_list = [i for i in assets_id.split(',')]
spm = request.GET.get('spm', '')
assets_id = cache.get(CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX.format(spm))
if kwargs.get('form'):
self.form = kwargs['form']
elif assets_id:
self.form = self.form_class(
initial={'assets': self.id_list}
)
self.form = self.form_class(initial={'assets': assets_id})
else:
self.form = self.form_class()
return super().get(request, *args, **kwargs)
......
......@@ -23,15 +23,15 @@ class OpenIDAuthenticationMiddleware(MiddlewareMixin):
def process_request(self, request):
# Don't need openid auth if AUTH_OPENID is False
if not settings.AUTH_OPENID:
logger.info("Not settings.AUTH_OPENID")
logger.debug("Not settings.AUTH_OPENID")
return
# Don't need check single logout if user not authenticated
if not request.user.is_authenticated:
logger.info("User is not authenticated")
logger.debug("User is not authenticated")
return
elif not request.session[BACKEND_SESSION_KEY].endswith(
BACKEND_OPENID_AUTH_CODE):
logger.info("BACKEND_SESSION_KEY is not BACKEND_OPENID_AUTH_CODE")
logger.debug("BACKEND_SESSION_KEY is not BACKEND_OPENID_AUTH_CODE")
return
# Check openid user single logout or not with access_token
......@@ -40,7 +40,6 @@ class OpenIDAuthenticationMiddleware(MiddlewareMixin):
client.openid_connect_client.userinfo(
token=request.session.get(OIDT_ACCESS_TOKEN)
)
except Exception as e:
logout(request)
logger.error(e)
......@@ -14,6 +14,14 @@ class UserLoginForm(AuthenticationForm):
max_length=128, strip=False
)
error_messages = {
'invalid_login': _(
"Please enter a correct username and password. Note that both "
"fields may be case-sensitive."
),
'inactive': _("This account is inactive."),
}
def confirm_login_allowed(self, user):
if not user.is_staff:
raise forms.ValidationError(
......
......@@ -52,7 +52,7 @@
</style>
</head>
<body style="height: 100%">
<body style="height: 100%;font-size: 13px">
<div>
<div class="box-1">
<div class="box-2">
......@@ -60,19 +60,19 @@
</div>
<div class="box-3">
<div style="background-color: white">
<div style="margin-top: 40px;padding-top: 50px;">
<span style="font-size: 24px;font-weight:400;color: #151515;letter-spacing: 0;">{{ JMS_TITLE }}</span>
<div style="margin-top: 30px;padding-top: 40px;padding-left: 20px;padding-right: 20px;height: 80px">
<span style="font-size: 21px;font-weight:400;color: #151515;letter-spacing: 0;">{{ JMS_TITLE }}</span>
</div>
<div style="font-size: 12px;color: #999999;letter-spacing: 0;line-height: 18px;margin-top: 10px">
<div style="font-size: 12px;color: #999999;letter-spacing: 0;line-height: 18px;margin-top: 18px">
{% trans 'Welcome back, please enter username and password to login' %}
</div>
<div style="margin-bottom: 10px">
<div>
<div class="col-md-1"></div>
<div class="contact-form col-md-10" style="margin-top: 20px;height: 35px">
<div class="contact-form col-md-10" style="margin-top: 10px;height: 35px">
<form id="contact-form" action="" method="post" role="form" novalidate="novalidate">
{% csrf_token %}
<div style="height: 48px;color: red">
<div style="height: 45px;color: red;line-height: 17px;">
{% if block_login %}
<p class="red-fonts">{% trans 'Log in frequently and try again later' %}</p>
{% elif password_expired %}
......@@ -92,7 +92,7 @@
<div class="form-group">
<input type="password" class="form-control" name="{{ form.password.html_name }}" placeholder="{% trans 'Password' %}" required="">
</div>
<div class="form-group" style="height: 50px;margin-bottom: 0">
<div class="form-group" style="height: 50px;margin-bottom: 0;font-size: 13px">
{{ form.captcha }}
</div>
<div class="form-group" style="margin-top: 10px">
......
......@@ -59,6 +59,11 @@ class UserLoginView(FormView):
return redirect(redirect_user_first_login_or_index(
request, self.redirect_field_name)
)
# show jumpserver login page if request http://{JUMP-SERVER}/?admin=1
if settings.AUTH_OPENID and not self.request.GET.get('admin', 0):
query_string = request.GET.urlencode()
login_url = "{}?{}".format(settings.LOGIN_URL, query_string)
return redirect(login_url)
request.session.set_test_cookie()
return super().get(request, *args, **kwargs)
......
......@@ -3,7 +3,7 @@
from django.utils.translation import ugettext_lazy as _
create_success_msg = _("<b>%(name)s</b> was created successfully")
update_success_msg = _("<b>%(name)s</b> was updated successfully")
create_success_msg = _("%(name)s was created successfully")
update_success_msg = _("%(name)s was updated successfully")
FILE_END_GUARD = ">>> Content End <<<"
celery_task_pre_key = "CELERY_"
......@@ -4,6 +4,10 @@ from django.db import models
from django.http import JsonResponse
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from rest_framework.utils import html
from rest_framework.settings import api_settings
from rest_framework.exceptions import ValidationError
from rest_framework.fields import SkipField
class NoDeleteQuerySet(models.query.QuerySet):
......@@ -89,6 +93,60 @@ class BulkSerializerMixin(object):
return ret
class BulkListSerializerMixin(object):
"""
Become rest_framework_bulk doing bulk update raise Exception:
'QuerySet' object has no attribute 'pk' when doing bulk update
so rewrite it .
https://github.com/miki725/django-rest-framework-bulk/issues/68
"""
def to_internal_value(self, data):
"""
List of dicts of native values <- List of dicts of primitive datatypes.
"""
if html.is_html_input(data):
data = html.parse_html_list(data)
if not isinstance(data, list):
message = self.error_messages['not_a_list'].format(
input_type=type(data).__name__
)
raise ValidationError({
api_settings.NON_FIELD_ERRORS_KEY: [message]
}, code='not_a_list')
if not self.allow_empty and len(data) == 0:
if self.parent and self.partial:
raise SkipField()
message = self.error_messages['empty']
raise ValidationError({
api_settings.NON_FIELD_ERRORS_KEY: [message]
}, code='empty')
ret = []
errors = []
for item in data:
try:
# prepare child serializer to only handle one instance
self.child.instance = self.instance.get(id=item['id']) if self.instance else None
self.child.initial_data = item
# raw
validated = self.child.run_validation(item)
except ValidationError as exc:
errors.append(exc.detail)
else:
ret.append(validated)
errors.append({})
if any(errors):
raise ValidationError(errors)
return ret
class DatetimeSearchMixin:
date_format = '%Y-%m-%d'
date_from = date_to = None
......
......@@ -35,7 +35,7 @@ class IsSuperUser(IsValidUser):
class IsSuperUserOrAppUser(IsSuperUser):
def has_permission(self, request, view):
return super(IsSuperUserOrAppUser, self).has_permission(request, view) \
and (request.user.is_superuser or request.user.is_app)
or request.user.is_app
class IsOrgAdmin(IsValidUser):
......
# -*- coding: utf-8 -*-
#
from .mixins import BulkListSerializerMixin
from rest_framework_bulk.serializers import BulkListSerializer
class AdaptedBulkListSerializer(BulkListSerializerMixin, BulkListSerializer):
pass
# -*- coding: utf-8 -*-
#
VERSION = '1.4.9'
VERSION = '1.4.10'
......@@ -15,7 +15,7 @@ def jumpserver_processor(request):
'FAVICON_URL': static('img/facio.ico'),
'JMS_TITLE': 'Jumpserver',
'VERSION': settings.VERSION,
'COPYRIGHT': _('Beijing Duizhan Tech, Inc.') + ' © 2014-2019'
'COPYRIGHT': 'FIT2CLOUD 飞致云' + ' © 2014-2019'
}
return context
......
......@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Jumpserver 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-03-27 17:33+0800\n"
"POT-Creation-Date: 2019-04-29 12:22+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"
......@@ -17,6 +17,10 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: assets/api/asset.py:112
msgid "Please select assets that need to be updated"
msgstr "请选择需要更新的资产"
#: assets/api/node.py:58
msgid "You can't update the root node name"
msgstr "不能修改根节点名称"
......@@ -32,7 +36,7 @@ msgstr "测试节点下资产是否可连接: {}"
#: assets/forms/asset.py:27 assets/models/asset.py:80 assets/models/user.py:133
#: 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:31
#: assets/templates/assets/system_user_asset.html:95 perms/models.py:51
#: xpack/plugins/change_auth_plan/models.py:69
msgid "Nodes"
msgstr "节点管理"
......@@ -69,7 +73,7 @@ msgstr "网域"
#: assets/forms/asset.py:112 assets/models/node.py:31
#: assets/templates/assets/asset_create.html:30
#: assets/templates/assets/asset_update.html:35 perms/forms.py:45
#: perms/forms.py:52 perms/models.py:84
#: perms/forms.py:55 perms/models.py:105
#: perms/templates/perms/asset_permission_list.html:57
#: perms/templates/perms/asset_permission_list.html:78
#: perms/templates/perms/asset_permission_list.html:128
......@@ -116,7 +120,7 @@ msgstr "选择资产"
#: assets/templates/assets/system_user_list.html:33 audits/models.py:19
#: audits/templates/audits/ftp_log_list.html:41
#: audits/templates/audits/ftp_log_list.html:71 perms/forms.py:42
#: perms/models.py:30
#: perms/models.py:50
#: perms/templates/perms/asset_permission_create_update.html:45
#: perms/templates/perms/asset_permission_list.html:56
#: perms/templates/perms/asset_permission_list.html:125
......@@ -126,7 +130,7 @@ msgstr "选择资产"
#: terminal/templates/terminal/session_list.html:41
#: terminal/templates/terminal/session_list.html:72
#: xpack/plugins/change_auth_plan/forms.py:114
#: xpack/plugins/change_auth_plan/models.py:408
#: xpack/plugins/change_auth_plan/models.py:409
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:46
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:54
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:13
......@@ -160,7 +164,7 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC"
#: 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:27
#: orgs/models.py:12 perms/models.py:27
#: orgs/models.py:12 perms/models.py:17 perms/models.py:47
#: perms/templates/perms/asset_permission_detail.html:62
#: perms/templates/perms/asset_permission_list.html:53
#: perms/templates/perms/asset_permission_list.html:72
......@@ -172,7 +176,7 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC"
#: settings/templates/settings/terminal_setting.html:102 terminal/models.py:22
#: terminal/models.py:241 terminal/templates/terminal/terminal_detail.html:43
#: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14
#: users/models/user.py:54 users/templates/users/_select_user_modal.html:13
#: users/models/user.py:61 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
......@@ -208,13 +212,13 @@ msgstr "名称"
#: ops/models/adhoc.py:164 perms/templates/perms/asset_permission_list.html:74
#: perms/templates/perms/asset_permission_user.html:55
#: settings/templates/settings/_ldap_list_users_modal.html:37 users/forms.py:13
#: users/models/user.py:52 users/templates/users/_select_user_modal.html:14
#: users/models/user.py:59 users/templates/users/_select_user_modal.html:14
#: users/templates/users/user_detail.html:67
#: users/templates/users/user_list.html:24
#: users/templates/users/user_profile.html:47
#: xpack/plugins/change_auth_plan/forms.py:99
#: xpack/plugins/change_auth_plan/models.py:60
#: xpack/plugins/change_auth_plan/models.py:404
#: xpack/plugins/change_auth_plan/models.py:405
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:65
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:53
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:12
......@@ -241,12 +245,12 @@ msgstr "密码或密钥密码"
#: users/templates/users/user_pubkey_update.html:40
#: users/templates/users/user_update.html:20
#: xpack/plugins/change_auth_plan/models.py:90
#: xpack/plugins/change_auth_plan/models.py:259
#: xpack/plugins/change_auth_plan/models.py:260
msgid "Password"
msgstr "密码"
#: assets/forms/user.py:29 assets/serializers/asset_user.py:27
#: users/models/user.py:81
#: users/models/user.py:88
msgid "Private key"
msgstr "ssh私钥"
......@@ -380,7 +384,7 @@ msgid "CPU model"
msgstr "CPU型号"
#: assets/models/asset.py:96
#: xpack/plugins/license/templates/license/license_detail.html:71
#: xpack/plugins/license/templates/license/license_detail.html:80
msgid "CPU count"
msgstr "CPU数量"
......@@ -435,9 +439,9 @@ msgstr "标签管理"
#: 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:36
#: perms/models.py:89 perms/templates/perms/asset_permission_detail.html:98
#: users/models/user.py:95 users/templates/users/user_detail.html:111
#: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:15 perms/models.py:57
#: perms/models.py:110 perms/templates/perms/asset_permission_detail.html:98
#: users/models/user.py:102 users/templates/users/user_detail.html:111
#: xpack/plugins/change_auth_plan/models.py:103
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113
#: xpack/plugins/cloud/models.py:55 xpack/plugins/cloud/models.py:127
......@@ -451,7 +455,7 @@ msgstr "创建者"
#: assets/templates/assets/domain_detail.html:68
#: assets/templates/assets/system_user_detail.html:96
#: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:64
#: orgs/models.py:16 perms/models.py:37 perms/models.py:90
#: orgs/models.py:16 perms/models.py:58 perms/models.py:111
#: perms/templates/perms/asset_permission_detail.html:94
#: terminal/templates/terminal/terminal_detail.html:59 users/models/group.py:17
#: users/templates/users/user_group_detail.html:63
......@@ -479,10 +483,10 @@ msgstr "创建日期"
#: assets/templates/assets/system_user_detail.html:104
#: assets/templates/assets/system_user_list.html:37
#: assets/templates/assets/user_asset_list.html:171 ops/models/adhoc.py:43
#: orgs/models.py:17 perms/models.py:38 perms/models.py:91
#: orgs/models.py:17 perms/models.py:59 perms/models.py:112
#: 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:87
#: users/models/group.py:15 users/models/user.py:94
#: users/templates/users/user_detail.html:127
#: users/templates/users/user_group_detail.html:67
#: users/templates/users/user_group_list.html:14
......@@ -537,12 +541,12 @@ msgid "AuthBook"
msgstr ""
#: assets/models/base.py:29 xpack/plugins/change_auth_plan/models.py:94
#: xpack/plugins/change_auth_plan/models.py:266
#: xpack/plugins/change_auth_plan/models.py:267
msgid "SSH private key"
msgstr "ssh密钥"
#: assets/models/base.py:30 xpack/plugins/change_auth_plan/models.py:97
#: xpack/plugins/change_auth_plan/models.py:262
#: xpack/plugins/change_auth_plan/models.py:263
msgid "SSH public key"
msgstr "ssh公钥"
......@@ -554,7 +558,7 @@ msgstr "带宽"
msgid "Contact"
msgstr "联系人"
#: assets/models/cluster.py:22 users/models/user.py:73
#: assets/models/cluster.py:22 users/models/user.py:80
#: users/templates/users/user_detail.html:76
msgid "Phone"
msgstr "手机"
......@@ -580,7 +584,7 @@ msgid "Default"
msgstr "默认"
#: assets/models/cluster.py:36 assets/models/label.py:14
#: users/models/user.py:457
#: users/models/user.py:475
msgid "System"
msgstr "系统"
......@@ -675,7 +679,10 @@ msgstr "每行一个命令"
#: 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:34
#: perms/forms.py:51 perms/models.py:21 perms/models.py:53
#: perms/templates/perms/asset_permission_create_update.html:50
#: perms/templates/perms/asset_permission_list.html:60
#: perms/templates/perms/asset_permission_list.html:134
#: settings/templates/settings/terminal_setting.html:82
#: settings/templates/settings/terminal_setting.html:104
#: terminal/templates/terminal/session_list.html:81
......@@ -719,7 +726,7 @@ msgstr "默认资产组"
#: audits/templates/audits/password_change_log_list.html:50
#: ops/templates/ops/command_execution_list.html:35
#: ops/templates/ops/command_execution_list.html:60 perms/forms.py:36
#: perms/models.py:28
#: perms/models.py:48
#: perms/templates/perms/asset_permission_create_update.html:41
#: perms/templates/perms/asset_permission_list.html:54
#: perms/templates/perms/asset_permission_list.html:119 templates/index.html:87
......@@ -728,9 +735,9 @@ msgstr "默认资产组"
#: terminal/templates/terminal/command_list.html:72
#: terminal/templates/terminal/session_list.html:33
#: terminal/templates/terminal/session_list.html:71 users/forms.py:283
#: users/models/user.py:32 users/models/user.py:445
#: users/models/user.py:36 users/models/user.py:463
#: users/templates/users/user_group_detail.html:78
#: users/templates/users/user_group_list.html:13 users/views/user.py:386
#: users/templates/users/user_group_list.html:13 users/views/user.py:395
#: xpack/plugins/orgs/forms.py:26
#: xpack/plugins/orgs/templates/orgs/org_detail.html:113
#: xpack/plugins/orgs/templates/orgs/org_list.html:14
......@@ -768,9 +775,9 @@ msgstr "手动登录"
#: assets/templates/assets/system_user_detail.html:22
#: assets/views/admin_user.py:29 assets/views/admin_user.py:47
#: assets/views/admin_user.py:63 assets/views/admin_user.py:78
#: assets/views/admin_user.py:102 assets/views/asset.py:50
#: 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/admin_user.py:102 assets/views/asset.py:51
#: assets/views/asset.py:67 assets/views/asset.py:104 assets/views/asset.py:145
#: assets/views/asset.py:162 assets/views/asset.py:186
#: 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
......@@ -807,7 +814,7 @@ msgstr "登录模式"
#: assets/models/user.py:247 assets/templates/assets/user_asset_list.html:168
#: audits/models.py:20 audits/templates/audits/ftp_log_list.html:49
#: audits/templates/audits/ftp_log_list.html:72 perms/forms.py:48
#: perms/models.py:32 perms/models.py:86
#: perms/models.py:52 perms/models.py:107
#: perms/templates/perms/asset_permission_detail.html:140
#: perms/templates/perms/asset_permission_list.html:58
#: perms/templates/perms/asset_permission_list.html:79
......@@ -827,7 +834,7 @@ msgid "%(value)s is not an even number"
msgstr "%(value)s is not an even number"
#: assets/serializers/asset_user.py:23 users/forms.py:230
#: users/models/user.py:84 users/templates/users/first_login.html:42
#: users/models/user.py:91 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
......@@ -954,7 +961,7 @@ msgstr "资产csv文件"
msgid "If set id, will use this id update asset existed"
msgstr "如果设置了id,则会使用该行信息更新该id的资产"
#: assets/templates/assets/_asset_list_modal.html:7 assets/views/asset.py:51
#: assets/templates/assets/_asset_list_modal.html:7 assets/views/asset.py:52
#: templates/_nav.html:22 xpack/plugins/change_auth_plan/views.py:110
msgid "Asset list"
msgstr "资产列表"
......@@ -1005,7 +1012,7 @@ msgstr "自动生成密钥"
#: assets/templates/assets/asset_create.html:60
#: assets/templates/assets/asset_update.html:64
#: assets/templates/assets/gateway_create_update.html:53
#: perms/templates/perms/asset_permission_create_update.html:50
#: perms/templates/perms/asset_permission_create_update.html:53
#: terminal/templates/terminal/terminal_update.html:40
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:67
msgid "Other"
......@@ -1021,12 +1028,12 @@ msgstr "其它"
#: assets/templates/assets/domain_create_update.html:16
#: assets/templates/assets/gateway_create_update.html:58
#: assets/templates/assets/label_create_update.html:18
#: perms/templates/perms/asset_permission_create_update.html:80
#: perms/templates/perms/asset_permission_create_update.html:83
#: settings/templates/settings/basic_setting.html:61
#: settings/templates/settings/command_storage_create.html:79
#: settings/templates/settings/email_setting.html:62
#: settings/templates/settings/ldap_setting.html:61
#: settings/templates/settings/replay_storage_create.html:151
#: settings/templates/settings/replay_storage_create.html:152
#: settings/templates/settings/security_setting.html:70
#: settings/templates/settings/terminal_setting.html:68
#: terminal/templates/terminal/terminal_update.html:45
......@@ -1057,12 +1064,12 @@ msgstr "重置"
#: assets/templates/assets/gateway_create_update.html:59
#: assets/templates/assets/label_create_update.html:19
#: audits/templates/audits/login_log_list.html:89
#: perms/templates/perms/asset_permission_create_update.html:81
#: perms/templates/perms/asset_permission_create_update.html:84
#: settings/templates/settings/basic_setting.html:62
#: settings/templates/settings/command_storage_create.html:80
#: settings/templates/settings/email_setting.html:63
#: settings/templates/settings/ldap_setting.html:64
#: settings/templates/settings/replay_storage_create.html:152
#: settings/templates/settings/replay_storage_create.html:153
#: settings/templates/settings/security_setting.html:71
#: settings/templates/settings/terminal_setting.html:70
#: terminal/templates/terminal/command_list.html:103
......@@ -1076,13 +1083,13 @@ msgstr "重置"
#: users/templates/users/user_profile_update.html:64
#: users/templates/users/user_pubkey_update.html:77
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:72
#: xpack/plugins/interface/templates/interface/interface.html:73
#: xpack/plugins/interface/templates/interface/interface.html:74
msgid "Submit"
msgstr "提交"
#: assets/templates/assets/_user_asset_detail_modal.html:11
#: assets/templates/assets/asset_asset_user_list.html:17
#: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:189
#: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:187
msgid "Asset detail"
msgstr "资产详情"
......@@ -1139,60 +1146,61 @@ msgid "Test connective"
msgstr "测试可连接性"
#: assets/templates/assets/admin_user_assets.html:73
#: assets/templates/assets/admin_user_assets.html:114
#: assets/templates/assets/admin_user_assets.html:115
#: 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:164
#: assets/templates/assets/system_user_asset.html:165
#: assets/templates/assets/system_user_detail.html:151
msgid "Test"
msgstr "测试"
#: assets/templates/assets/admin_user_assets.html:115
#: assets/templates/assets/admin_user_assets.html:116
#: assets/templates/assets/asset_asset_user_list.html:120
#: assets/templates/assets/system_user_asset.html:166
#: assets/templates/assets/system_user_asset.html:167
msgid "Update auth"
msgstr "更新认证"
#: assets/templates/assets/admin_user_assets.html:191
#: assets/templates/assets/admin_user_assets.html:192
#: assets/templates/assets/asset_asset_user_list.html:176
#: assets/templates/assets/asset_detail.html:311
#: assets/templates/assets/system_user_asset.html:350
#: assets/templates/assets/system_user_asset.html:351
#: users/templates/users/user_detail.html:307
#: users/templates/users/user_detail.html:334
#: xpack/plugins/interface/views.py:31
#: xpack/plugins/interface/views.py:34
msgid "Update successfully!"
msgstr "更新成功"
#: assets/templates/assets/admin_user_assets.html:194
#: assets/templates/assets/admin_user_assets.html:195
#: assets/templates/assets/asset_asset_user_list.html:179
#: assets/templates/assets/system_user_asset.html:353
#: assets/templates/assets/system_user_asset.html:354
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:27
#: assets/templates/assets/asset_list.html:177
#: assets/templates/assets/asset_list.html:178
#: assets/templates/assets/cmd_filter_detail.html:29
#: assets/templates/assets/cmd_filter_list.html:57
#: assets/templates/assets/cmd_filter_list.html:58
#: assets/templates/assets/cmd_filter_rule_list.html:86
#: assets/templates/assets/domain_detail.html:24
#: assets/templates/assets/domain_detail.html:103
#: assets/templates/assets/domain_gateway_list.html:97
#: assets/templates/assets/domain_list.html:53
#: assets/templates/assets/label_list.html:38
#: assets/templates/assets/domain_list.html:54
#: assets/templates/assets/label_list.html:39
#: assets/templates/assets/system_user_detail.html:26
#: assets/templates/assets/system_user_list.html:92 audits/models.py:33
#: assets/templates/assets/system_user_list.html:93 audits/models.py:33
#: perms/templates/perms/asset_permission_detail.html:30
#: perms/templates/perms/asset_permission_list.html:177
#: perms/templates/perms/asset_permission_list.html:181
#: terminal/templates/terminal/terminal_detail.html:16
#: terminal/templates/terminal/terminal_list.html:71
#: terminal/templates/terminal/terminal_list.html:72
#: users/templates/users/user_detail.html:25
#: users/templates/users/user_group_detail.html:28
#: users/templates/users/user_group_list.html:43
#: users/templates/users/user_list.html:80
#: users/templates/users/user_group_list.html:45
#: users/templates/users/user_list.html:83
#: users/templates/users/user_list.html:86
#: users/templates/users/user_profile.html:177
#: users/templates/users/user_profile.html:187
#: users/templates/users/user_profile.html:196
......@@ -1208,28 +1216,28 @@ msgstr "更新"
#: assets/templates/assets/admin_user_detail.html:28
#: assets/templates/assets/admin_user_list.html:89
#: assets/templates/assets/asset_detail.html:31
#: assets/templates/assets/asset_list.html:178
#: assets/templates/assets/asset_list.html:179
#: assets/templates/assets/cmd_filter_detail.html:33
#: assets/templates/assets/cmd_filter_list.html:58
#: assets/templates/assets/cmd_filter_list.html:59
#: assets/templates/assets/cmd_filter_rule_list.html:87
#: assets/templates/assets/domain_detail.html:28
#: assets/templates/assets/domain_detail.html:104
#: assets/templates/assets/domain_gateway_list.html:98
#: assets/templates/assets/domain_list.html:54
#: assets/templates/assets/label_list.html:39
#: assets/templates/assets/domain_list.html:55
#: assets/templates/assets/label_list.html:40
#: assets/templates/assets/system_user_detail.html:30
#: assets/templates/assets/system_user_list.html:93 audits/models.py:34
#: assets/templates/assets/system_user_list.html:94 audits/models.py:34
#: ops/templates/ops/task_list.html:64
#: perms/templates/perms/asset_permission_detail.html:34
#: perms/templates/perms/asset_permission_list.html:178
#: perms/templates/perms/asset_permission_list.html:182
#: settings/templates/settings/terminal_setting.html:90
#: settings/templates/settings/terminal_setting.html:112
#: terminal/templates/terminal/terminal_list.html:73
#: terminal/templates/terminal/terminal_list.html:74
#: users/templates/users/user_detail.html:30
#: users/templates/users/user_group_detail.html:32
#: users/templates/users/user_group_list.html:45
#: users/templates/users/user_list.html:84
#: users/templates/users/user_list.html:88
#: users/templates/users/user_group_list.html:47
#: users/templates/users/user_list.html:91
#: users/templates/users/user_list.html:95
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:33
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:56
#: xpack/plugins/cloud/templates/cloud/account_detail.html:29
......@@ -1254,11 +1262,11 @@ msgstr "选择节点"
#: assets/templates/assets/admin_user_detail.html:100
#: assets/templates/assets/asset_detail.html:211
#: assets/templates/assets/asset_list.html:636
#: assets/templates/assets/asset_list.html:637
#: assets/templates/assets/cmd_filter_detail.html:106
#: assets/templates/assets/system_user_asset.html:112
#: assets/templates/assets/system_user_detail.html:182
#: assets/templates/assets/system_user_list.html:143
#: assets/templates/assets/system_user_list.html:144
#: settings/templates/settings/terminal_setting.html:165
#: templates/_modal.html:23 terminal/templates/terminal/session_detail.html:108
#: users/templates/users/user_detail.html:388
......@@ -1266,11 +1274,12 @@ msgstr "选择节点"
#: users/templates/users/user_detail.html:437
#: users/templates/users/user_detail.html:482
#: users/templates/users/user_group_create_update.html:32
#: users/templates/users/user_group_list.html:88
#: users/templates/users/user_list.html:208
#: users/templates/users/user_group_list.html:90
#: users/templates/users/user_list.html:215
#: users/templates/users/user_profile.html:238
#: xpack/plugins/cloud/templates/cloud/account_create_update.html:34
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:36
#: xpack/plugins/interface/templates/interface/interface.html:103
#: xpack/plugins/orgs/templates/orgs/org_create_update.html:33
msgid "Confirm"
msgstr "确认"
......@@ -1305,7 +1314,7 @@ msgid "Ratio"
msgstr "比例"
#: assets/templates/assets/asset_asset_user_list.html:20
#: assets/templates/assets/asset_detail.html:23 assets/views/asset.py:67
#: assets/templates/assets/asset_detail.html:23 assets/views/asset.py:68
msgid "Asset user list"
msgstr "资产用户列表"
......@@ -1329,7 +1338,7 @@ msgstr "更新日期"
#: users/templates/users/user_detail.html:138
#: users/templates/users/user_profile.html:146
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:128
#: xpack/plugins/license/templates/license/license_detail.html:93
#: xpack/plugins/license/templates/license/license_detail.html:102
msgid "Quick modify"
msgstr "快速修改"
......@@ -1358,9 +1367,9 @@ msgid "Date joined"
msgstr "创建日期"
#: assets/templates/assets/asset_detail.html:154
#: assets/templates/assets/user_asset_list.html:46 perms/models.py:33
#: perms/models.py:87
#: perms/templates/perms/asset_permission_create_update.html:52
#: assets/templates/assets/user_asset_list.html:46 perms/models.py:54
#: perms/models.py:108
#: perms/templates/perms/asset_permission_create_update.html:55
#: perms/templates/perms/asset_permission_detail.html:120
#: terminal/templates/terminal/terminal_list.html:34
#: users/templates/users/_select_user_modal.html:18
......@@ -1388,14 +1397,14 @@ msgstr ""
"左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织的,"
"右侧是属于该节点下的资产"
#: assets/templates/assets/asset_list.html:69 assets/views/asset.py:104
#: assets/templates/assets/asset_list.html:69 assets/views/asset.py:105
msgid "Create asset"
msgstr "创建资产"
#: assets/templates/assets/asset_list.html:73
#: settings/templates/settings/_ldap_list_users_modal.html:97
#: users/templates/users/user_list.html:7
#: xpack/plugins/license/templates/license/license_detail.html:101
#: xpack/plugins/license/templates/license/license_detail.html:110
msgid "Import"
msgstr "导入"
......@@ -1473,63 +1482,65 @@ msgstr "仅显示当前节点资产"
msgid "Displays all child node assets"
msgstr "显示所有子节点资产"
#: assets/templates/assets/asset_list.html:216
#: assets/templates/assets/asset_list.html:217
msgid "Create node failed"
msgstr "创建节点失败"
#: assets/templates/assets/asset_list.html:228
#: assets/templates/assets/asset_list.html:229
msgid "Have child node, cancel"
msgstr "存在子节点,不能删除"
#: assets/templates/assets/asset_list.html:230
#: assets/templates/assets/asset_list.html:231
msgid "Have assets, cancel"
msgstr "存在资产,不能删除"
#: assets/templates/assets/asset_list.html:301
#: assets/templates/assets/asset_list.html:302
msgid "Rename success"
msgstr "重命名成功"
#: assets/templates/assets/asset_list.html:302
#: assets/templates/assets/asset_list.html:303
msgid "Rename failed, do not change the root node name"
msgstr "重命名失败,不能更改root节点的名称"
#: assets/templates/assets/asset_list.html:630
#: assets/templates/assets/system_user_list.html:137
#: assets/templates/assets/asset_list.html:631
#: assets/templates/assets/system_user_list.html:138
#: users/templates/users/user_detail.html:382
#: users/templates/users/user_detail.html:408
#: users/templates/users/user_detail.html:476
#: users/templates/users/user_group_list.html:82
#: users/templates/users/user_list.html:202
#: users/templates/users/user_group_list.html:84
#: users/templates/users/user_list.html:209
#: xpack/plugins/interface/templates/interface/interface.html:97
msgid "Are you sure?"
msgstr "你确认吗?"
#: assets/templates/assets/asset_list.html:631
#: assets/templates/assets/asset_list.html:632
msgid "This will delete the selected assets !!!"
msgstr "删除选择资产"
#: assets/templates/assets/asset_list.html:634
#: assets/templates/assets/system_user_list.html:141
#: assets/templates/assets/asset_list.html:635
#: assets/templates/assets/system_user_list.html:142
#: settings/templates/settings/terminal_setting.html:163
#: users/templates/users/user_detail.html:386
#: users/templates/users/user_detail.html:412
#: users/templates/users/user_detail.html:480
#: users/templates/users/user_group_create_update.html:31
#: users/templates/users/user_group_list.html:86
#: users/templates/users/user_list.html:206
#: users/templates/users/user_group_list.html:88
#: users/templates/users/user_list.html:213
#: xpack/plugins/interface/templates/interface/interface.html:101
#: xpack/plugins/orgs/templates/orgs/org_create_update.html:32
msgid "Cancel"
msgstr "取消"
#: assets/templates/assets/asset_list.html:640
#: assets/templates/assets/asset_list.html:641
msgid "Asset Deleted."
msgstr "已被删除"
#: assets/templates/assets/asset_list.html:641
#: assets/templates/assets/asset_list.html:646
#: assets/templates/assets/asset_list.html:642
#: assets/templates/assets/asset_list.html:647
msgid "Asset Delete"
msgstr "删除"
#: assets/templates/assets/asset_list.html:645
#: assets/templates/assets/asset_list.html:646
msgid "Asset Deleting failed."
msgstr "删除失败"
......@@ -1661,7 +1672,7 @@ msgid "Push system user now"
msgstr "立刻推送系统"
#: assets/templates/assets/system_user_asset.html:84
#: assets/templates/assets/system_user_asset.html:162
#: assets/templates/assets/system_user_asset.html:163
#: assets/templates/assets/system_user_detail.html:142
msgid "Push"
msgstr "推送"
......@@ -1717,24 +1728,24 @@ msgstr ""
msgid "Create system user"
msgstr "创建系统用户"
#: assets/templates/assets/system_user_list.html:138
#: assets/templates/assets/system_user_list.html:139
msgid "This will delete the selected System Users !!!"
msgstr "删除选择系统用户"
#: assets/templates/assets/system_user_list.html:147
#: assets/templates/assets/system_user_list.html:148
msgid "System Users Deleted."
msgstr "已被删除"
#: assets/templates/assets/system_user_list.html:148
#: assets/templates/assets/system_user_list.html:153
#: assets/templates/assets/system_user_list.html:149
#: assets/templates/assets/system_user_list.html:154
msgid "System Users Delete"
msgstr "删除系统用户"
#: assets/templates/assets/system_user_list.html:152
#: assets/templates/assets/system_user_list.html:153
msgid "System Users Deleting failed."
msgstr "系统用户删除失败"
#: assets/templates/assets/user_asset_list.html:100
#: assets/templates/assets/user_asset_list.html:100 perms/const.py:19
msgid "Connect"
msgstr "连接"
......@@ -1750,23 +1761,23 @@ msgstr "更新管理用户"
msgid "Admin user detail"
msgstr "管理用户详情"
#: assets/views/asset.py:78 templates/_nav_user.html:4
#: assets/views/asset.py:79 templates/_nav_user.html:4
msgid "My assets"
msgstr "我的资产"
#: assets/views/asset.py:118
#: assets/views/asset.py:119
msgid "Bulk update asset success"
msgstr "批量更新资产成功"
#: assets/views/asset.py:148
#: assets/views/asset.py:146
msgid "Bulk update asset"
msgstr "批量更新资产"
#: assets/views/asset.py:165
#: assets/views/asset.py:163
msgid "Update asset"
msgstr "更新资产"
#: assets/views/asset.py:306
#: assets/views/asset.py:304
msgid "already exists"
msgstr "已经存在"
......@@ -1938,13 +1949,13 @@ msgid "User agent"
msgstr "Agent"
#: audits/models.py:99 audits/templates/audits/login_log_list.html:56
#: users/forms.py:142 users/models/user.py:76
#: users/forms.py:142 users/models/user.py:83
#: users/templates/users/first_login.html:45
msgid "MFA"
msgstr "MFA"
#: audits/models.py:100 audits/templates/audits/login_log_list.html:57
#: xpack/plugins/change_auth_plan/models.py:412
#: xpack/plugins/change_auth_plan/models.py:413
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:15
#: xpack/plugins/cloud/models.py:172
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:69
......@@ -1966,11 +1977,11 @@ msgstr "登录日期"
#: ops/templates/ops/adhoc_history.html:52
#: ops/templates/ops/adhoc_history_detail.html:61
#: ops/templates/ops/command_execution_list.html:66
#: ops/templates/ops/task_history.html:58 perms/models.py:34
#: ops/templates/ops/task_history.html:58 perms/models.py:55
#: perms/templates/perms/asset_permission_detail.html:86 terminal/models.py:165
#: terminal/templates/terminal/session_list.html:78
#: xpack/plugins/change_auth_plan/models.py:245
#: xpack/plugins/change_auth_plan/models.py:415
#: xpack/plugins/change_auth_plan/models.py:246
#: xpack/plugins/change_auth_plan/models.py:416
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:59
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:17
msgid "Date start"
......@@ -1988,7 +1999,7 @@ msgstr "选择用户"
#: ops/templates/ops/command_execution_list.html:43
#: ops/templates/ops/command_execution_list.html:48
#: ops/templates/ops/task_list.html:13 ops/templates/ops/task_list.html:18
#: templates/_base_list.html:43 templates/_header_bar.html:8
#: templates/_base_list.html:41 templates/_header_bar.html:8
#: terminal/templates/terminal/command_list.html:60
#: terminal/templates/terminal/session_list.html:61
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:52
......@@ -2125,7 +2136,17 @@ msgstr ""
msgid "Invalid token or cache refreshed."
msgstr ""
#: authentication/forms.py:29 users/forms.py:21
#: authentication/forms.py:19
msgid ""
"Please enter a correct username and password. Note that both fields may be "
"case-sensitive."
msgstr "请输入正确的用户名和密码. 注意它们是区分大小写."
#: authentication/forms.py:22
msgid "This account is inactive."
msgstr "此账户无效"
#: authentication/forms.py:37 users/forms.py:21
msgid "MFA code"
msgstr "MFA 验证码"
......@@ -2249,32 +2270,32 @@ msgstr "如果不能提供MFA验证码,请联系管理员!"
msgid "Welcome back, please enter username and password to login"
msgstr "欢迎回来,请输入用户名和密码登录"
#: authentication/views/login.py:75
#: authentication/views/login.py:80
msgid "Please enable cookies and try again."
msgstr "设置你的浏览器支持cookie"
#: authentication/views/login.py:167 users/views/user.py:532
#: users/views/user.py:557
#: authentication/views/login.py:172 users/views/user.py:541
#: users/views/user.py:566
msgid "MFA code invalid, or ntp sync server time"
msgstr "MFA验证码不正确,或者服务器端时间不对"
#: authentication/views/login.py:198
#: authentication/views/login.py:203
msgid "Logout success"
msgstr "退出登录成功"
#: authentication/views/login.py:199
#: authentication/views/login.py:204
msgid "Logout success, return login page"
msgstr "退出登录成功,返回到登录页面"
#: common/const.py:6
#, python-format
msgid "<b>%(name)s</b> was created successfully"
msgstr "<b>%(name)s</b> 创建成功"
msgid "%(name)s was created successfully"
msgstr "%(name)s 创建成功"
#: common/const.py:7
#, python-format
msgid "<b>%(name)s</b> was updated successfully"
msgstr "<b>%(name)s</b> 更新成功"
msgid "%(name)s was updated successfully"
msgstr "%(name)s 更新成功"
#: common/fields/form.py:34
msgid "Not a valid json"
......@@ -2312,11 +2333,11 @@ msgstr ""
msgid "Encrypt field using Secret Key"
msgstr ""
#: common/mixins.py:28
#: common/mixins.py:32
msgid "is discard"
msgstr ""
#: common/mixins.py:29
#: common/mixins.py:33
msgid "discard time"
msgstr ""
......@@ -2324,10 +2345,6 @@ msgstr ""
msgid "Special char not allowed"
msgstr "不能包含特殊字符"
#: jumpserver/context_processor.py:18
msgid "Beijing Duizhan Tech, Inc."
msgstr "北京堆栈科技有限公司"
#: jumpserver/views.py:185
msgid ""
"<div>Luna is a separately deployed program, you need to deploy Luna, coco, "
......@@ -2421,8 +2438,8 @@ msgstr "完成时间"
#: ops/models/adhoc.py:326 ops/templates/ops/adhoc_history.html:57
#: ops/templates/ops/task_history.html:63 ops/templates/ops/task_list.html:33
#: xpack/plugins/change_auth_plan/models.py:248
#: xpack/plugins/change_auth_plan/models.py:418
#: xpack/plugins/change_auth_plan/models.py:249
#: xpack/plugins/change_auth_plan/models.py:419
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:58
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:16
msgid "Time"
......@@ -2572,33 +2589,33 @@ msgstr "任务列表"
msgid "Go"
msgstr ""
#: ops/templates/ops/command_execution_create.html:148
#: ops/templates/ops/command_execution_create.html:152
msgid "Selected assets"
msgstr "已选择资产"
#: ops/templates/ops/command_execution_create.html:151
#: ops/templates/ops/command_execution_create.html:155
msgid "In total"
msgstr "总共"
#: ops/templates/ops/command_execution_create.html:186
#: ops/templates/ops/command_execution_create.html:190
msgid ""
"Select the left asset, select the running system user, execute command in "
"batch"
msgstr "选择左侧资产, 选择运行的系统用户,批量执行命令"
#: ops/templates/ops/command_execution_create.html:204
#: ops/templates/ops/command_execution_create.html:208
msgid "Unselected assets"
msgstr "没有选中资产"
#: ops/templates/ops/command_execution_create.html:208
#: ops/templates/ops/command_execution_create.html:212
msgid "No input command"
msgstr "没有输入命令"
#: ops/templates/ops/command_execution_create.html:212
#: ops/templates/ops/command_execution_create.html:216
msgid "No system user was selected"
msgstr "没有选择系统用户"
#: ops/templates/ops/command_execution_create.html:257
#: ops/templates/ops/command_execution_create.html:261
msgid "Pending"
msgstr "等待"
......@@ -2683,11 +2700,23 @@ msgstr "命令执行"
msgid "Organization"
msgstr "组织管理"
#: perms/forms.py:39 perms/models.py:29 perms/models.py:85
#: perms/const.py:18 settings/forms.py:136
msgid "All"
msgstr "全部"
#: perms/const.py:20
msgid "Upload file"
msgstr "上传文件"
#: perms/const.py:21
msgid "Download file"
msgstr "下载文件"
#: perms/forms.py:39 perms/models.py:49 perms/models.py:106
#: 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:253 users/models/group.py:26 users/models/user.py:60
#: users/forms.py:253 users/models/group.py:26 users/models/user.py:67
#: users/templates/users/_select_user_modal.html:16
#: users/templates/users/user_detail.html:213
#: users/templates/users/user_list.html:26
......@@ -2695,22 +2724,28 @@ msgstr "组织管理"
msgid "User group"
msgstr "用户组"
#: perms/forms.py:61
#: perms/forms.py:58
msgid ""
"Tips: The RDP protocol does not support separate controls for uploading or "
"downloading files"
msgstr "提示:RDP 协议不支持单独控制上传或下载文件"
#: perms/forms.py:68
msgid "User or group at least one required"
msgstr "用户和用户组至少选一个"
#: perms/forms.py:70
#: perms/forms.py:77
msgid "Asset or group at least one required"
msgstr "资产和节点至少选一个"
#: perms/models.py:35 perms/models.py:88
#: perms/models.py:56 perms/models.py:109
#: perms/templates/perms/asset_permission_detail.html:90
#: users/models/user.py:92 users/templates/users/user_detail.html:107
#: users/models/user.py:99 users/templates/users/user_detail.html:107
#: users/templates/users/user_profile.html:116
msgid "Date expired"
msgstr "失效日期"
#: perms/models.py:44 perms/models.py:97 templates/_nav.html:34
#: perms/models.py:65 perms/models.py:118 templates/_nav.html:34
msgid "Asset permission"
msgstr "资产授权"
......@@ -2756,12 +2791,12 @@ msgstr "添加节点"
msgid "Join"
msgstr "加入"
#: perms/templates/perms/asset_permission_create_update.html:58
#: perms/templates/perms/asset_permission_create_update.html:61
msgid "Validity period"
msgstr "有效期"
#: perms/templates/perms/asset_permission_detail.html:66
#: xpack/plugins/license/templates/license/license_detail.html:67
#: xpack/plugins/license/templates/license/license_detail.html:76
msgid "User count"
msgstr "用户数量"
......@@ -2770,7 +2805,7 @@ msgid "User group count"
msgstr "用户组列表"
#: perms/templates/perms/asset_permission_detail.html:74
#: xpack/plugins/license/templates/license/license_detail.html:63
#: xpack/plugins/license/templates/license/license_detail.html:72
msgid "Asset count"
msgstr "资产数量"
......@@ -2814,29 +2849,29 @@ msgstr "添加用户组"
msgid "Select user groups"
msgstr "选择用户组"
#: perms/views.py:23 perms/views.py:53 perms/views.py:68 perms/views.py:83
#: perms/views.py:118 perms/views.py:150 templates/_nav.html:31
#: perms/views.py:24 perms/views.py:56 perms/views.py:71 perms/views.py:86
#: perms/views.py:121 perms/views.py:153 templates/_nav.html:31
#: xpack/plugins/orgs/templates/orgs/org_list.html:21
msgid "Perms"
msgstr "权限管理"
#: perms/views.py:24
#: perms/views.py:25
msgid "Asset permission list"
msgstr "资产授权列表"
#: perms/views.py:54
#: perms/views.py:57
msgid "Create asset permission"
msgstr "创建权限规则"
#: perms/views.py:69 perms/views.py:84
#: perms/views.py:72 perms/views.py:87
msgid "Update asset permission"
msgstr "更新资产授权"
#: perms/views.py:119
#: perms/views.py:122
msgid "Asset permission user list"
msgstr "资产授权用户列表"
#: perms/views.py:151
#: perms/views.py:154
msgid "Asset permission asset list"
msgstr "资产授权资产列表"
......@@ -2968,10 +3003,6 @@ msgstr ""
msgid "Enable LDAP auth"
msgstr "启用LDAP认证"
#: settings/forms.py:136
msgid "All"
msgstr "全部"
#: settings/forms.py:137
msgid "Auto"
msgstr "自动"
......@@ -3123,7 +3154,7 @@ msgid "Please submit the LDAP configuration before import"
msgstr "请先提交LDAP配置再进行导入"
#: settings/templates/settings/_ldap_list_users_modal.html:39
#: users/models/user.py:56 users/templates/users/user_detail.html:71
#: users/models/user.py:63 users/templates/users/user_detail.html:71
#: users/templates/users/user_profile.html:59
msgid "Email"
msgstr "邮件"
......@@ -3349,7 +3380,7 @@ msgstr "商业支持"
#: users/templates/users/user_profile.html:17
#: users/templates/users/user_profile_update.html:37
#: users/templates/users/user_profile_update.html:57
#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:368
#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:377
msgid "Profile"
msgstr "个人信息"
......@@ -3431,9 +3462,9 @@ 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: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
#: users/views/login.py:154 users/views/user.py:68 users/views/user.py:83
#: users/views/user.py:122 users/views/user.py:203 users/views/user.py:364
#: users/views/user.py:414 users/views/user.py:454
msgid "Users"
msgstr "用户管理"
......@@ -3822,11 +3853,11 @@ msgstr "地址"
msgid "Alive"
msgstr "在线"
#: terminal/templates/terminal/terminal_list.html:76
#: terminal/templates/terminal/terminal_list.html:77
msgid "Accept"
msgstr "接受"
#: terminal/templates/terminal/terminal_list.html:78
#: terminal/templates/terminal/terminal_list.html:79
msgid "Reject"
msgstr "拒绝"
......@@ -3867,11 +3898,15 @@ msgid ""
"You should use your ssh client tools connect terminal: {} <br /> <br />{}"
msgstr "你可以使用ssh客户端工具连接终端"
#: users/api/user.py:146
#: users/api/user.py:69 users/api/user.py:80 users/api/user.py:106
msgid "You do not have permission."
msgstr "你没有权限"
#: users/api/user.py:210
msgid "Could not reset self otp, use profile reset instead"
msgstr "不能再该页面重置MFA, 请去个人信息页面重置"
#: users/forms.py:32 users/models/user.py:64
#: users/forms.py:32 users/models/user.py:71
#: users/templates/users/_select_user_modal.html:15
#: users/templates/users/user_detail.html:87
#: users/templates/users/user_list.html:25
......@@ -3964,53 +3999,53 @@ msgstr "复制你的公钥到这里"
msgid "Select users"
msgstr "选择用户"
#: users/models/user.py:31 users/models/user.py:453
#: users/models/user.py:35 users/models/user.py:471
msgid "Administrator"
msgstr "管理员"
#: users/models/user.py:33
#: users/models/user.py:37
msgid "Application"
msgstr "应用程序"
#: users/models/user.py:36 users/templates/users/user_profile.html:92
#: users/models/user.py:40 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:37 users/templates/users/user_profile.html:90
#: users/models/user.py:41 users/templates/users/user_profile.html:90
#: users/templates/users/user_profile.html:166
msgid "Enable"
msgstr "启用"
#: users/models/user.py:38 users/templates/users/user_profile.html:88
#: users/models/user.py:42 users/templates/users/user_profile.html:88
msgid "Force enable"
msgstr "强制启用"
#: users/models/user.py:67
#: users/models/user.py:74
msgid "Avatar"
msgstr "头像"
#: users/models/user.py:70 users/templates/users/user_detail.html:82
#: users/models/user.py:77 users/templates/users/user_detail.html:82
msgid "Wechat"
msgstr "微信"
#: users/models/user.py:99 users/templates/users/user_detail.html:103
#: users/models/user.py:106 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:103
#: users/models/user.py:110
msgid "Date password last updated"
msgstr "最后更新密码日期"
#: 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
#: users/models/user.py:136 users/templates/users/user_update.html:22
#: users/views/login.py:47 users/views/login.py:108 users/views/user.py:427
msgid "User auth from {}, go there change password"
msgstr "用户认证源来自 {}, 请去相应系统修改密码"
#: users/models/user.py:456
#: users/models/user.py:474
msgid "Administrator is the super user of system"
msgstr "Administrator是初始的超级管理员"
......@@ -4201,7 +4236,7 @@ msgid "Reset link will be generated and sent to the user. "
msgstr "生成重置密码连接,通过邮件发送给用户"
#: users/templates/users/user_detail.html:19
#: users/templates/users/user_granted_asset.html:18 users/views/user.py:195
#: users/templates/users/user_granted_asset.html:18 users/views/user.py:204
msgid "User detail"
msgstr "用户详情"
......@@ -4317,45 +4352,45 @@ msgstr "添加用户"
msgid "Create user group"
msgstr "创建用户组"
#: users/templates/users/user_group_list.html:83
#: users/templates/users/user_group_list.html:85
msgid "This will delete the selected groups !!!"
msgstr "删除选择组"
#: users/templates/users/user_group_list.html:92
#: users/templates/users/user_group_list.html:94
msgid "UserGroups Deleted."
msgstr "用户组删除"
#: users/templates/users/user_group_list.html:93
#: users/templates/users/user_group_list.html:98
#: users/templates/users/user_group_list.html:95
#: users/templates/users/user_group_list.html:100
msgid "UserGroups Delete"
msgstr "用户组删除"
#: users/templates/users/user_group_list.html:97
#: users/templates/users/user_group_list.html:99
msgid "UserGroup Deleting failed."
msgstr "用户组删除失败"
#: users/templates/users/user_list.html:203
#: users/templates/users/user_list.html:210
msgid "This will delete the selected users !!!"
msgstr "删除选中用户 !!!"
#: users/templates/users/user_list.html:212
#: users/templates/users/user_list.html:219
msgid "User Deleted."
msgstr "已被删除"
#: users/templates/users/user_list.html:213
#: users/templates/users/user_list.html:218
#: users/templates/users/user_list.html:220
#: users/templates/users/user_list.html:225
msgid "User Delete"
msgstr "删除"
#: users/templates/users/user_list.html:217
#: users/templates/users/user_list.html:224
msgid "User Deleting failed."
msgstr "用户删除失败"
#: users/templates/users/user_list.html:253
#: users/templates/users/user_list.html:260
msgid "User is expired"
msgstr "用户已失效"
#: users/templates/users/user_list.html:256
#: users/templates/users/user_list.html:263
msgid "User is inactive"
msgstr "用户已禁用"
......@@ -4404,8 +4439,8 @@ msgstr "安装完成后点击下一步进入绑定页面(如已安装,直接
msgid "Administrator Settings force MFA login"
msgstr "管理员设置强制使用MFA登录"
#: users/templates/users/user_profile.html:120 users/views/user.py:231
#: users/views/user.py:285
#: users/templates/users/user_profile.html:120 users/views/user.py:240
#: users/views/user.py:294
msgid "User groups"
msgstr "用户组"
......@@ -4455,7 +4490,7 @@ msgid ""
"corresponding private key."
msgstr "新的公钥已设置成功,请下载对应的私钥"
#: users/templates/users/user_update.html:4 users/views/user.py:114
#: users/templates/users/user_update.html:4 users/views/user.py:123
msgid "Update user"
msgstr "更新用户"
......@@ -4658,88 +4693,88 @@ msgstr "更新用户组"
msgid "User group granted asset"
msgstr "用户组授权资产"
#: users/views/login.py:42
#: users/views/login.py:44
msgid "Email address invalid, please input again"
msgstr "邮箱地址错误,重新输入"
#: users/views/login.py:58
#: users/views/login.py:60
msgid "Send reset password message"
msgstr "发送重置密码邮件"
#: users/views/login.py:59
#: users/views/login.py:61
msgid "Send reset password mail success, login your mail box and follow it "
msgstr ""
"发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)"
#: users/views/login.py:72
#: users/views/login.py:74
msgid "Reset password success"
msgstr "重置密码成功"
#: users/views/login.py:73
#: users/views/login.py:75
msgid "Reset password success, return to login page"
msgstr "重置密码成功,返回到登录页面"
#: users/views/login.py:88 users/views/login.py:107
#: users/views/login.py:90 users/views/login.py:106
msgid "Token invalid or expired"
msgstr "Token错误或失效"
#: users/views/login.py:100
#: users/views/login.py:102
msgid "Password not same"
msgstr "密码不一致"
#: users/views/login.py:113 users/views/user.py:128 users/views/user.py:428
#: users/views/login.py:115 users/views/user.py:137 users/views/user.py:437
msgid "* Your password does not meet the requirements"
msgstr "* 您的密码不符合要求"
#: users/views/login.py:151
#: users/views/login.py:154
msgid "First login"
msgstr "首次登录"
#: users/views/user.py:145
#: users/views/user.py:154
msgid "Bulk update user success"
msgstr "批量更新用户成功"
#: users/views/user.py:175
#: users/views/user.py:184
msgid "Bulk update user"
msgstr "批量更新用户"
#: users/views/user.py:260
#: users/views/user.py:269
msgid "Invalid file."
msgstr "文件不合法"
#: users/views/user.py:356
#: users/views/user.py:365
msgid "User granted assets"
msgstr "用户授权资产"
#: users/views/user.py:387
#: users/views/user.py:396
msgid "Profile setting"
msgstr "个人信息设置"
#: users/views/user.py:406
#: users/views/user.py:415
msgid "Password update"
msgstr "密码更新"
#: users/views/user.py:446
#: users/views/user.py:455
msgid "Public key update"
msgstr "密钥更新"
#: users/views/user.py:487
#: users/views/user.py:496
msgid "Password invalid"
msgstr "用户名或密码无效"
#: users/views/user.py:587
#: users/views/user.py:596
msgid "MFA enable success"
msgstr "MFA 绑定成功"
#: users/views/user.py:588
#: users/views/user.py:597
msgid "MFA enable success, return login page"
msgstr "MFA 绑定成功,返回到登录页面"
#: users/views/user.py:590
#: users/views/user.py:599
msgid "MFA disable success"
msgstr "MFA 解绑成功"
#: users/views/user.py:591
#: users/views/user.py:600
msgid "MFA disable success, return login page"
msgstr "MFA 解绑成功,返回登录页面"
......@@ -4796,8 +4831,8 @@ msgstr ""
"具</a>) <br>注意: 如果同时设置了定期执行和周期执行,优先使用定期执行"
#: xpack/plugins/change_auth_plan/meta.py:9
#: xpack/plugins/change_auth_plan/models.py:110
#: xpack/plugins/change_auth_plan/models.py:252
#: xpack/plugins/change_auth_plan/models.py:111
#: xpack/plugins/change_auth_plan/models.py:253
#: xpack/plugins/change_auth_plan/views.py:31
#: xpack/plugins/change_auth_plan/views.py:47
#: xpack/plugins/change_auth_plan/views.py:68
......@@ -4821,13 +4856,13 @@ msgid "All assets use different random password"
msgstr "所有资产使用不同的随机密码"
#: xpack/plugins/change_auth_plan/models.py:73
#: xpack/plugins/change_auth_plan/models.py:141
#: xpack/plugins/change_auth_plan/models.py:142
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:100
msgid "Cycle perform"
msgstr "周期执行"
#: xpack/plugins/change_auth_plan/models.py:78
#: xpack/plugins/change_auth_plan/models.py:139
#: xpack/plugins/change_auth_plan/models.py:140
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:92
msgid "Regularly perform"
msgstr "定期执行"
......@@ -4845,32 +4880,32 @@ msgstr "密码策略"
msgid "Password rules"
msgstr "密码规则"
#: xpack/plugins/change_auth_plan/models.py:209
#: xpack/plugins/change_auth_plan/models.py:210
msgid "For security, do not change root user's password"
msgstr "为了安全,禁止更改root用户的密码"
#: xpack/plugins/change_auth_plan/models.py:212
#: xpack/plugins/change_auth_plan/models.py:213
msgid "Assets is empty, please add the asset"
msgstr "资产为空,请添加资产"
#: xpack/plugins/change_auth_plan/models.py:256
#: xpack/plugins/change_auth_plan/models.py:257
msgid "Change auth plan snapshot"
msgstr "改密计划快照"
#: xpack/plugins/change_auth_plan/models.py:271
#: xpack/plugins/change_auth_plan/models.py:422
#: xpack/plugins/change_auth_plan/models.py:272
#: xpack/plugins/change_auth_plan/models.py:423
msgid "Change auth plan execution"
msgstr "改密计划执行"
#: xpack/plugins/change_auth_plan/models.py:431
#: xpack/plugins/change_auth_plan/models.py:432
msgid "Change auth plan execution subtask"
msgstr "改密计划执行子任务"
#: xpack/plugins/change_auth_plan/models.py:449
#: xpack/plugins/change_auth_plan/models.py:450
msgid "Authentication failed"
msgstr "认证失败"
#: xpack/plugins/change_auth_plan/models.py:451
#: xpack/plugins/change_auth_plan/models.py:452
msgid "Connection timeout"
msgstr "连接超时"
......@@ -5217,30 +5252,57 @@ msgid "Interface settings"
msgstr "界面设置"
#: xpack/plugins/interface/templates/interface/interface.html:15
#: xpack/plugins/interface/views.py:21
#: xpack/plugins/interface/views.py:24
msgid "Interface setting"
msgstr "界面设置"
#: xpack/plugins/interface/views.py:20
#: xpack/plugins/interface/templates/interface/interface.html:73
#: xpack/plugins/interface/templates/interface/interface.html:108
#: xpack/plugins/interface/templates/interface/interface.html:115
msgid "Restore Default"
msgstr "恢复默认"
#: xpack/plugins/interface/templates/interface/interface.html:98
msgid "This will restore default Settings of the interface !!!"
msgstr "您确定要恢复默认初始化吗?"
#: xpack/plugins/interface/templates/interface/interface.html:107
msgid "Restore default successfully."
msgstr "恢复默认成功!"
#: xpack/plugins/interface/templates/interface/interface.html:114
msgid "Restore default failed."
msgstr "恢复默认失败!"
#: xpack/plugins/interface/views.py:23
msgid "Interface"
msgstr "界面"
#: xpack/plugins/interface/views.py:49
msgid "It is already in the default setting state!"
msgstr "当前已经是初始化状态!"
#: xpack/plugins/interface/views.py:53
msgid "Restore default successfully!"
msgstr "恢复默认成功!"
#: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:94
#: xpack/plugins/license/templates/license/license_detail.html:50
#: xpack/plugins/license/templates/license/license_detail.html:55
#: xpack/plugins/license/views.py:31
msgid "License"
msgstr "许可证"
#: xpack/plugins/license/models.py:74
msgid "Standard edition"
msgstr ""
msgstr "标准版"
#: xpack/plugins/license/models.py:76
msgid "Enterprise edition"
msgstr "企业版"
#: xpack/plugins/license/templates/license/_license_import_modal.html:4
#: xpack/plugins/license/templates/license/license_detail.html:99
#: xpack/plugins/license/templates/license/license_detail.html:108
msgid "Import license"
msgstr "导入许可证"
......@@ -5253,6 +5315,7 @@ msgid "Please Import License"
msgstr "请导入许可证"
#: xpack/plugins/license/templates/license/license_detail.html:17
#: xpack/plugins/license/templates/license/license_detail.html:56
msgid "License has expired"
msgstr "许可证已经过期"
......@@ -5273,34 +5336,38 @@ msgstr "许可证详情"
msgid "No license"
msgstr "暂无许可证"
#: xpack/plugins/license/templates/license/license_detail.html:55
#: xpack/plugins/license/templates/license/license_detail.html:60
msgid "Subscription ID"
msgstr "订阅授权ID"
#: xpack/plugins/license/templates/license/license_detail.html:64
msgid "Corporation"
msgstr "公司"
#: xpack/plugins/license/templates/license/license_detail.html:59
#: xpack/plugins/license/templates/license/license_detail.html:68
msgid "Expired"
msgstr "过期时间"
#: xpack/plugins/license/templates/license/license_detail.html:64
#: xpack/plugins/license/templates/license/license_detail.html:68
#: xpack/plugins/license/templates/license/license_detail.html:72
#: xpack/plugins/license/templates/license/license_detail.html:76
#: xpack/plugins/license/templates/license/license_detail.html:73
#: xpack/plugins/license/templates/license/license_detail.html:77
#: xpack/plugins/license/templates/license/license_detail.html:81
#: xpack/plugins/license/templates/license/license_detail.html:85
msgid "Unlimited"
msgstr "无限制"
#: xpack/plugins/license/templates/license/license_detail.html:75
#: xpack/plugins/license/templates/license/license_detail.html:84
msgid "Concurrent connections"
msgstr "并发连接"
#: xpack/plugins/license/templates/license/license_detail.html:80
#: xpack/plugins/license/templates/license/license_detail.html:89
msgid "Edition"
msgstr "版本"
#: xpack/plugins/license/templates/license/license_detail.html:106
#: xpack/plugins/license/templates/license/license_detail.html:115
msgid "Technology consulting"
msgstr "技术咨询"
#: xpack/plugins/license/templates/license/license_detail.html:109
#: xpack/plugins/license/templates/license/license_detail.html:118
msgid "Consult"
msgstr "咨询"
......@@ -5356,6 +5423,9 @@ msgstr "创建组织"
msgid "Update org"
msgstr "更新组织"
#~ msgid "Beijing Duizhan Tech, Inc."
#~ msgstr "北京堆栈科技有限公司"
#~ msgid "Sync User"
#~ msgstr "同步用户"
......
......@@ -82,6 +82,7 @@
<script>
var zTree, show = 0;
var systemUserId = null;
var url = null;
var treeUrl = "{% url 'api-perms:my-nodes-assets-as-tree' %}?cache_policy=1";
function initTree() {
......@@ -114,6 +115,9 @@ function initTree() {
if (systemUserId) {
url = treeUrl + '&system_user=' + systemUserId
}
else{
url = treeUrl
}
$.get(url, function(data, status){
$.fn.zTree.init($("#assetTree"), setting, data);
......
from rest_framework.serializers import ModelSerializer
from rest_framework import serializers
from rest_framework_bulk import BulkListSerializer
from users.models import User, UserGroup
from assets.models import Asset, Domain, AdminUser, SystemUser, Label
from perms.models import AssetPermission
from common.serializers import AdaptedBulkListSerializer
from .utils import set_current_org, get_current_org
from .models import Organization
from .mixins import OrgMembershipSerializerMixin
......@@ -14,7 +14,7 @@ from .mixins import OrgMembershipSerializerMixin
class OrgSerializer(ModelSerializer):
class Meta:
model = Organization
list_serializer_class = BulkListSerializer
list_serializer_class = AdaptedBulkListSerializer
fields = '__all__'
read_only_fields = ['created_by', 'date_created']
......@@ -70,12 +70,12 @@ class OrgReadSerializer(ModelSerializer):
class OrgMembershipAdminSerializer(OrgMembershipSerializerMixin, ModelSerializer):
class Meta:
model = Organization.admins.through
list_serializer_class = BulkListSerializer
list_serializer_class = AdaptedBulkListSerializer
fields = '__all__'
class OrgMembershipUserSerializer(OrgMembershipSerializerMixin, ModelSerializer):
class Meta:
model = Organization.users.through
list_serializer_class = BulkListSerializer
list_serializer_class = AdaptedBulkListSerializer
fields = '__all__'
......@@ -10,7 +10,7 @@ from rest_framework.pagination import LimitOffsetPagination
from common.permissions import IsOrgAdmin
from common.utils import get_object_or_none
from ..models import AssetPermission
from ..models import AssetPermission, Action
from ..hands import (
User, UserGroup, Asset, Node, SystemUser,
)
......@@ -20,10 +20,16 @@ from .. import serializers
__all__ = [
'AssetPermissionViewSet', 'AssetPermissionRemoveUserApi',
'AssetPermissionAddUserApi', 'AssetPermissionRemoveAssetApi',
'AssetPermissionAddAssetApi',
'AssetPermissionAddAssetApi', 'ActionViewSet',
]
class ActionViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Action.objects.all()
serializer_class = serializers.ActionSerializer
permission_classes = (IsOrgAdmin,)
class AssetPermissionViewSet(viewsets.ModelViewSet):
"""
资产授权列表的增删改查api
......
......@@ -16,7 +16,8 @@ from common.tree import TreeNodeSerializer
from common.utils import get_logger
from orgs.utils import set_to_root_org
from ..utils import (
AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node
AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node,
check_system_user_action
)
from ..hands import (
AssetGrantedSerializer, User, Asset, Node,
......@@ -24,6 +25,7 @@ from ..hands import (
)
from .. import serializers
from ..mixins import AssetsFilterMixin
from ..models import Action
logger = get_logger(__name__)
......@@ -31,7 +33,7 @@ __all__ = [
'UserGrantedAssetsApi', 'UserGrantedNodesApi',
'UserGrantedNodesWithAssetsApi', 'UserGrantedNodeAssetsApi',
'ValidateUserAssetPermissionApi', 'UserGrantedNodeChildrenApi',
'UserGrantedNodesWithAssetsAsTreeApi',
'UserGrantedNodesWithAssetsAsTreeApi', 'GetUserAssetPermissionActionsApi',
]
......@@ -403,16 +405,45 @@ class ValidateUserAssetPermissionApi(UserPermissionCacheMixin, APIView):
user_id = request.query_params.get('user_id', '')
asset_id = request.query_params.get('asset_id', '')
system_id = request.query_params.get('system_user_id', '')
action_name = request.query_params.get('action_name', '')
user = get_object_or_404(User, id=user_id)
asset = get_object_or_404(Asset, id=asset_id)
system_user = get_object_or_404(SystemUser, id=system_id)
su = get_object_or_404(SystemUser, id=system_id)
action = get_object_or_404(Action, name=action_name)
util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
assets_granted = util.get_assets()
if system_user in assets_granted.get(asset, []):
return Response({'msg': True}, status=200)
else:
granted_assets = util.get_assets()
granted_system_users = granted_assets.get(asset, [])
if su not in granted_system_users:
return Response({'msg': False}, status=403)
_su = next((s for s in granted_system_users if s.id == su.id), None)
if not check_system_user_action(_su, action):
return Response({'msg': False}, status=403)
return Response({'msg': True}, status=200)
class GetUserAssetPermissionActionsApi(UserPermissionCacheMixin, APIView):
permission_classes = (IsOrgAdminOrAppUser,)
def get(self, request, *args, **kwargs):
user_id = request.query_params.get('user_id', '')
asset_id = request.query_params.get('asset_id', '')
system_id = request.query_params.get('system_user_id', '')
user = get_object_or_404(User, id=user_id)
asset = get_object_or_404(Asset, id=asset_id)
su = get_object_or_404(SystemUser, id=system_id)
util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
granted_assets = util.get_assets()
granted_system_users = granted_assets.get(asset, [])
_su = next((s for s in granted_system_users if s.id == su.id), None)
if not _su:
return Response({'actions': []}, status=403)
actions = [action.name for action in getattr(_su, 'actions', [])]
return Response({'actions': actions}, status=200)
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext_lazy as _
__all__ = [
'PERMS_ACTION_NAME_ALL', 'PERMS_ACTION_NAME_CONNECT',
'PERMS_ACTION_NAME_DOWNLOAD_FILE', 'PERMS_ACTION_NAME_UPLOAD_FILE',
'PERMS_ACTION_NAME_CHOICES'
]
PERMS_ACTION_NAME_ALL = 'all'
PERMS_ACTION_NAME_CONNECT = 'connect'
PERMS_ACTION_NAME_UPLOAD_FILE = 'upload_file'
PERMS_ACTION_NAME_DOWNLOAD_FILE = 'download_file'
PERMS_ACTION_NAME_CHOICES = (
(PERMS_ACTION_NAME_ALL, _('All')),
(PERMS_ACTION_NAME_CONNECT, _('Connect')),
(PERMS_ACTION_NAME_UPLOAD_FILE, _('Upload file')),
(PERMS_ACTION_NAME_DOWNLOAD_FILE, _('Download file')),
)
......@@ -47,10 +47,17 @@ class AssetPermissionForm(OrgModelForm):
'system_users': forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('System user')}
),
'actions': forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('Action')}
)
}
labels = {
'nodes': _("Node"),
}
help_texts = {
'actions': _('Tips: The RDP protocol does not support separate '
'controls for uploading or downloading files')
}
def clean_user_groups(self):
users = self.cleaned_data.get('users')
......
# Generated by Django 2.1.7 on 2019-04-12 07:00
from django.db import migrations, models
import uuid
def add_default_actions(apps, schema_editor):
from ..const import PERMS_ACTION_NAME_CHOICES
action_model = apps.get_model('perms', 'Action')
db_alias = schema_editor.connection.alias
for action, _ in PERMS_ACTION_NAME_CHOICES:
action_model.objects.using(db_alias).update_or_create(name=action)
class Migration(migrations.Migration):
dependencies = [
('perms', '0002_auto_20171228_0025_squashed_0009_auto_20180903_1132'),
]
operations = [
migrations.CreateModel(
name='Action',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(choices=[('all', 'All'), ('connect', 'Connect'), ('upload_file', 'Upload file'), ('download_file', 'Download file')], max_length=128, unique=True, verbose_name='Name')),
],
options={
'verbose_name': 'Action',
},
),
migrations.RunPython(add_default_actions)
]
# Generated by Django 2.1.7 on 2019-04-12 09:17
from django.db import migrations, models
def set_default_action_to_existing_perms(apps, schema_editor):
from orgs.utils import set_to_root_org
from ..models import Action
set_to_root_org()
perm_model = apps.get_model('perms', 'AssetPermission')
db_alias = schema_editor.connection.alias
perms = perm_model.objects.using(db_alias).all()
default_action = Action.get_action_all()
for perm in perms:
perm.actions.add(default_action.id)
class Migration(migrations.Migration):
dependencies = [
('perms', '0003_action'),
]
operations = [
migrations.AddField(
model_name='assetpermission',
name='actions',
field=models.ManyToManyField(blank=True, related_name='permissions', to='perms.Action', verbose_name='Action'),
),
migrations.RunPython(set_default_action_to_existing_perms)
]
......@@ -7,6 +7,26 @@ from django.utils import timezone
from common.utils import date_expired_default, set_or_append_attr_bulk
from orgs.mixins import OrgModelMixin, OrgManager
from .const import PERMS_ACTION_NAME_CHOICES, PERMS_ACTION_NAME_ALL
class Action(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(
max_length=128, unique=True, choices=PERMS_ACTION_NAME_CHOICES,
verbose_name=_('Name')
)
class Meta:
verbose_name = _('Action')
def __str__(self):
return self.get_name_display()
@classmethod
def get_action_all(cls):
return cls.objects.get(name=PERMS_ACTION_NAME_ALL)
class AssetPermissionQuerySet(models.QuerySet):
def active(self):
......@@ -30,6 +50,7 @@ class AssetPermission(OrgModelMixin):
assets = models.ManyToManyField('assets.Asset', related_name='granted_by_permissions', blank=True, verbose_name=_("Asset"))
nodes = models.ManyToManyField('assets.Node', related_name='granted_by_permissions', blank=True, verbose_name=_("Nodes"))
system_users = models.ManyToManyField('assets.SystemUser', related_name='granted_by_permissions', verbose_name=_("System user"))
actions = models.ManyToManyField('Action', related_name='permissions', blank=True, verbose_name=_('Action'))
is_active = models.BooleanField(default=True, verbose_name=_('Active'))
date_start = models.DateTimeField(default=timezone.now, db_index=True, verbose_name=_("Date start"))
date_expired = models.DateTimeField(default=date_expired_default, db_index=True, verbose_name=_('Date expired'))
......
......@@ -4,7 +4,7 @@
from rest_framework import serializers
from common.fields import StringManyToManyField
from .models import AssetPermission
from .models import AssetPermission, Action
from assets.models import Node, Asset, SystemUser
from assets.serializers import AssetGrantedSerializer
......@@ -13,9 +13,16 @@ __all__ = [
'AssetPermissionUpdateUserSerializer', 'AssetPermissionUpdateAssetSerializer',
'AssetPermissionNodeSerializer', 'GrantedNodeSerializer',
'GrantedAssetSerializer', 'GrantedSystemUserSerializer',
'ActionSerializer',
]
class ActionSerializer(serializers.ModelSerializer):
class Meta:
model = Action
fields = '__all__'
class AssetPermissionCreateUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = AssetPermission
......@@ -28,6 +35,7 @@ class AssetPermissionListSerializer(serializers.ModelSerializer):
assets = StringManyToManyField(many=True, read_only=True)
nodes = StringManyToManyField(many=True, read_only=True)
system_users = StringManyToManyField(many=True, read_only=True)
actions = StringManyToManyField(many=True, read_only=True)
is_valid = serializers.BooleanField()
is_expired = serializers.BooleanField()
......
......@@ -2,15 +2,37 @@
#
from django.db.models.signals import m2m_changed, post_save, post_delete
from django.dispatch import receiver
from django.db import transaction
from common.utils import get_logger
from .utils import AssetPermissionUtil
from .models import AssetPermission
from .models import AssetPermission, Action
logger = get_logger(__file__)
def on_transaction_commit(func):
"""
如果不调用on_commit, 对象创建时添加多对多字段值失败
"""
def inner(*args, **kwargs):
transaction.on_commit(lambda: func(*args, **kwargs))
return inner
@receiver(post_save, sender=AssetPermission, dispatch_uid="my_unique_identifier")
@on_transaction_commit
def on_permission_created(sender, instance=None, created=False, **kwargs):
actions = instance.actions.all()
if created and not actions:
default_action = Action.get_action_all()
instance.actions.add(default_action)
logger.debug(
"Set default action to perms: {}".format(default_action, instance)
)
@receiver(post_save, sender=AssetPermission)
def on_permission_update(sender, **kwargs):
AssetPermissionUtil.expire_all_cache()
......
......@@ -47,6 +47,9 @@
{% bootstrap_field form.nodes layout="horizontal" %}
{% bootstrap_field form.system_users layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Action' %}</h3>
{% bootstrap_field form.actions layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Other' %}</h3>
<div class="form-group">
<label for="{{ form.is_active.id_for_label }}" class="col-sm-2 control-label">{% trans 'Active' %}</label>
......
......@@ -130,6 +130,9 @@ function format(d) {
if (d.system_users.length > 0) {
data += makeLabel(["{% trans 'System user' %}", d.system_users.join(", ")])
}
if (d.actions.length > 0) {
data += makeLabel(["{% trans 'Action' %}", d.actions.join(", ")])
}
return data
}
......@@ -143,6 +146,7 @@ function initTable() {
$(td).html("<i class='fa fa-angle-right'></i>");
}},
{targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
var detail_btn = '<a href="{% url "perms:asset-permission-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
......
......@@ -7,6 +7,7 @@ from .. import api
app_name = 'perms'
router = routers.DefaultRouter()
router.register('actions', api.ActionViewSet, 'action')
router.register('asset-permissions', api.AssetPermissionViewSet, 'asset-permission')
urlpatterns = [
......@@ -67,6 +68,8 @@ urlpatterns = [
# 验证用户是否有某个资产和系统用户的权限
path('asset-permission/user/validate/', api.ValidateUserAssetPermissionApi.as_view(),
name='validate-user-asset-permission'),
path('asset-permission/user/actions/', api.GetUserAssetPermissionActionsApi.as_view(),
name='get-user-asset-permission-actions'),
]
urlpatterns += router.urls
......
# coding: utf-8
from __future__ import absolute_import, unicode_literals
import uuid
from collections import defaultdict
import json
......@@ -13,7 +12,7 @@ from django.conf import settings
from common.utils import get_logger
from common.tree import TreeNode
from .models import AssetPermission
from .models import AssetPermission, Action
from .hands import Node
logger = get_logger(__file__)
......@@ -101,7 +100,7 @@ class AssetPermissionUtil:
"UserGroup": get_user_group_permissions,
"Asset": get_asset_permissions,
"Node": get_node_permissions,
"SystemUser": get_node_permissions,
"SystemUser": get_system_user_permissions,
}
CACHE_KEY_PREFIX = '_ASSET_PERM_CACHE_'
......@@ -180,6 +179,24 @@ class AssetPermissionUtil:
)
return assets
def _setattr_actions_to_system_user(self):
"""
动态给system_use设置属性actions
"""
for asset, system_users in self._assets.items():
# 获取资产和资产的祖先节点的所有授权规则
perms = get_asset_permissions(asset, include_node=True)
# 过滤当前self.permission的授权规则
perms = perms.filter(id__in=[perm.id for perm in self.permissions])
for system_user in system_users:
actions = set()
_perms = perms.filter(system_users=system_user).\
prefetch_related('actions')
for _perm in _perms:
actions.update(_perm.actions.all())
setattr(system_user, 'actions', actions)
def get_assets_without_cache(self):
if self._assets:
return self._assets
......@@ -192,6 +209,7 @@ class AssetPermissionUtil:
[s for s in system_users if s.protocol == asset.protocol]
)
self._assets = assets
self._setattr_actions_to_system_user()
return self._assets
def get_cache_key(self, resource):
......@@ -395,6 +413,7 @@ def parse_asset_to_tree_node(node, asset, system_users):
'protocol': system_user.protocol,
'priority': system_user.priority,
'login_mode': system_user.login_mode,
'actions': [action.name for action in system_user.actions],
'comment': system_user.comment,
})
data = {
......@@ -423,3 +442,21 @@ def parse_asset_to_tree_node(node, asset, system_users):
}
tree_node = TreeNode(**data)
return tree_node
#
# actions
#
def check_system_user_action(system_user, action):
"""
:param system_user: SystemUser object (包含动态属性: actions)
:param action: Action object
:return: bool
"""
check_actions = [Action.get_action_all(), action]
granted_actions = getattr(system_user, 'actions', [])
actions = list(set(granted_actions).intersection(set(check_actions)))
return bool(actions)
......@@ -11,8 +11,9 @@ from django.conf import settings
from common.permissions import AdminUserRequiredMixin
from orgs.utils import current_org
from .hands import Node, Asset, SystemUser, User, UserGroup
from .models import AssetPermission
from .models import AssetPermission, Action
from .forms import AssetPermissionForm
from .const import PERMS_ACTION_NAME_ALL
class AssetPermissionListView(AdminUserRequiredMixin, TemplateView):
......@@ -46,6 +47,8 @@ class AssetPermissionCreateView(AdminUserRequiredMixin, CreateView):
assets_id = assets_id.split(",")
assets = Asset.objects.filter(id__in=assets_id)
form['assets'].initial = assets
form['actions'].initial = Action.objects.get(name=PERMS_ACTION_NAME_ALL)
return form
def get_context_data(self, **kwargs):
......
......@@ -538,7 +538,11 @@ jumpserver.initServerSideDataTable = function (options) {
$(td).html('<input type="checkbox" class="text-center ipt_check" id=99991937>'.replace('99991937', cellData));
}
},
{className: 'text-center', targets: '_all'}
{
targets: '_all',
className: 'text-center',
render: $.fn.dataTable.render.text()
}
];
columnDefs = options.columnDefs ? options.columnDefs.concat(columnDefs) : columnDefs;
var select = {
......@@ -946,3 +950,10 @@ function rootNodeAddDom(ztree, callback) {
callback()
})
}
function htmlEscape ( d ) {
return typeof d === 'string' ?
d.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;') :
d;
}
\ No newline at end of file
......@@ -2,10 +2,8 @@
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link href="{% static "css/plugins/datatables/datatables.min.css" %}" rel="stylesheet">
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
<script src="{% static "js/plugins/datatables/datatables.min.js" %}"></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
......
......@@ -47,7 +47,8 @@
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }} help-message" >
{{ message|safe }}
{# {{ message|safe }}#}
{{ message }}
<button aria-hidden="true" data-dismiss="alert" class="close" type="button" style="outline: none;">×</button>
</div>
......
# -*- coding: utf-8 -*-
#
from rest_framework import serializers
from rest_framework_bulk.serializers import BulkListSerializer
from common.mixins import BulkSerializerMixin
from common.serializers import AdaptedBulkListSerializer
from ..models import Terminal, Status, Session, Task
......@@ -29,7 +29,7 @@ class SessionSerializer(BulkSerializerMixin, serializers.ModelSerializer):
class Meta:
model = Session
list_serializer_class = BulkListSerializer
list_serializer_class = AdaptedBulkListSerializer
fields = '__all__'
......@@ -44,7 +44,7 @@ class TaskSerializer(BulkSerializerMixin, serializers.ModelSerializer):
class Meta:
fields = '__all__'
model = Task
list_serializer_class = BulkListSerializer
list_serializer_class = AdaptedBulkListSerializer
class ReplaySerializer(serializers.Serializer):
......
......@@ -50,6 +50,7 @@ function initTable() {
buttons: [],
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
var detail_btn = '<a href="{% url "terminal:terminal-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
......
......@@ -5,6 +5,7 @@ from django.core.cache import cache
from django.contrib.auth import logout
from django.utils.translation import ugettext as _
from rest_framework import status
from rest_framework import generics
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
......@@ -52,9 +53,72 @@ class UserViewSet(IDInFilterMixin, BulkModelViewSet):
self.permission_classes = (IsOrgAdminOrAppUser,)
return super().get_permissions()
def _deny_permission(self, instance):
"""
check current user has permission to handle instance
(update, destroy, bulk_update, bulk destroy)
"""
return not self.request.user.is_superuser and instance.is_superuser
def destroy(self, request, *args, **kwargs):
"""
rewrite because limit org_admin destroy superuser
"""
instance = self.get_object()
if self._deny_permission(instance):
data = {'msg': _("You do not have permission.")}
return Response(data=data, status=status.HTTP_403_FORBIDDEN)
return super().destroy(request, *args, **kwargs)
def update(self, request, *args, **kwargs):
"""
rewrite because limit org_admin update superuser
"""
instance = self.get_object()
if self._deny_permission(instance):
data = {'msg': _("You do not have permission.")}
return Response(data=data, status=status.HTTP_403_FORBIDDEN)
return super().update(request, *args, **kwargs)
def _bulk_deny_permission(self, instances):
deny_instances = [i for i in instances if self._deny_permission(i)]
if len(deny_instances) > 0:
return True
else:
return False
def allow_bulk_destroy(self, qs, filtered):
if self._bulk_deny_permission(filtered):
return False
return qs.count() != filtered.count()
def bulk_update(self, request, *args, **kwargs):
"""
rewrite because limit org_admin update superuser
"""
partial = kwargs.pop('partial', False)
# restrict the update to the filtered queryset
queryset = self.filter_queryset(self.get_queryset())
if self._bulk_deny_permission(queryset):
data = {'msg': _("You do not have permission.")}
return Response(data=data, status=status.HTTP_403_FORBIDDEN)
serializer = self.get_serializer(
queryset, data=request.data, many=True, partial=partial,
)
try:
serializer.is_valid(raise_exception=True)
except Exception as e:
data = {'error': str(e)}
return Response(data=data, status=status.HTTP_400_BAD_REQUEST)
self.perform_bulk_update(serializer)
return Response(serializer.data, status=status.HTTP_200_OK)
class UserChangePasswordApi(generics.RetrieveUpdateAPIView):
permission_classes = (IsOrgAdmin,)
......
......@@ -3,24 +3,28 @@
#
import uuid
import base64
import string
import random
from collections import OrderedDict
from django.conf import settings
from django.contrib.auth.hashers import make_password
from django.contrib.auth.models import AbstractUser
from django.core import signing
from django.core.cache import cache
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from django.shortcuts import reverse
from common.utils import get_signer, date_expired_default
from common.utils import get_signer, date_expired_default, get_logger
__all__ = ['User']
signer = get_signer()
logger = get_logger(__file__)
class User(AbstractUser):
ROLE_ADMIN = 'Admin'
......@@ -47,6 +51,9 @@ class User(AbstractUser):
(SOURCE_OPENID, 'OpenID'),
(SOURCE_RADIUS, 'Radius'),
)
CACHE_KEY_USER_RESET_PASSWORD_PREFIX = "_KEY_USER_RESET_PASSWORD_{}"
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
username = models.CharField(
max_length=128, unique=True, verbose_name=_('Username')
......@@ -346,9 +353,32 @@ class User(AbstractUser):
return user_default
def generate_reset_token(self):
return signer.sign_t(
{'reset': str(self.id), 'email': self.email}, expires_in=3600
)
letter = string.ascii_letters + string.digits
token =''.join([random.choice(letter) for _ in range(50)])
self.set_cache(token)
return token
def set_cache(self, token):
key = self.CACHE_KEY_USER_RESET_PASSWORD_PREFIX.format(token)
cache.set(key, {'id': self.id, 'email': self.email}, 3600)
@classmethod
def validate_reset_password_token(cls, token):
try:
key = cls.CACHE_KEY_USER_RESET_PASSWORD_PREFIX.format(token)
value = cache.get(key)
user_id = value.get('id', '')
email = value.get('email', '')
user = cls.objects.get(id=user_id, email=email)
except (AttributeError, cls.DoesNotExist) as e:
logger.error(e, exc_info=True)
user = None
return user
@classmethod
def expired_reset_password_token(cls, token):
key = cls.CACHE_KEY_USER_RESET_PASSWORD_PREFIX.format(token)
cache.delete(key)
@property
def otp_enabled(self):
......@@ -400,18 +430,6 @@ class User(AbstractUser):
access_key = app.create_access_key()
return app, access_key
@classmethod
def validate_reset_token(cls, token):
try:
data = signer.unsign_t(token)
user_id = data.get('reset', None)
user_email = data.get('email', '')
user = cls.objects.get(id=user_id, email=user_email)
except (signing.BadSignature, cls.DoesNotExist):
user = None
return user
def reset_password(self, new_password):
self.set_password(new_password)
self.date_password_last_updated = timezone.now()
......
......@@ -3,10 +3,10 @@
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from rest_framework_bulk import BulkListSerializer
from common.utils import get_signer, validate_ssh_public_key
from common.mixins import BulkSerializerMixin
from common.serializers import AdaptedBulkListSerializer
from ..models import User, UserGroup
signer = get_signer()
......@@ -16,7 +16,7 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
class Meta:
model = User
list_serializer_class = BulkListSerializer
list_serializer_class = AdaptedBulkListSerializer
fields = [
'id', 'name', 'username', 'email', 'groups', 'groups_display',
'role', 'role_display', 'avatar_url', 'wechat', 'phone',
......@@ -52,7 +52,7 @@ class UserGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer):
class Meta:
model = UserGroup
list_serializer_class = BulkListSerializer
list_serializer_class = AdaptedBulkListSerializer
fields = '__all__'
read_only_fields = ['created_by']
......
......@@ -22,11 +22,11 @@
<a href="{% url 'users:user-granted-asset' pk=user_object.id %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Asset granted' %}</a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-default" href="{% url 'users:user-update' pk=user_object.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
<a class="btn btn-outline {% if user_object.is_superuser and not request.user.is_superuser %} disabled {% else %} btn-default {% endif %}" href="{% url 'users:user-update' pk=user_object.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
</li>
<li class="pull-right">
<a class="btn btn-outline {% if request.user != user_object and user_object.username != "admin" %} btn-danger btn-delete-user {% else %} disabled {% endif %}">
<a class="btn btn-outline {% if request.user == user_object or user_object.username == "admin" or user_object.is_superuser and not request.user.is_superuser %} disabled {% else %} btn-danger btn-delete-user {% endif %}">
<i class="fa fa-trash-o"></i>{% trans 'Delete' %}
</a>
</li>
......
......@@ -77,6 +77,7 @@ function initTable() {
ele: $('#user_assets_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
{% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %}
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
......@@ -91,7 +92,8 @@ function initTable() {
{targets: 4, createdCell: function (td, cellData) {
var users = [];
$.each(cellData, function (id, data) {
users.push(data.name);
var name = htmlEscape(data.name);
users.push(name);
});
$(td).html(users.join(', '))
}}
......
......@@ -77,6 +77,7 @@ function initTable() {
ele: $('#user_assets_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
{% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %}
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
......@@ -91,7 +92,8 @@ function initTable() {
{targets: 4, createdCell: function (td, cellData) {
var users = [];
$.each(cellData, function (id, data) {
users.push(data.name);
var name = htmlEscape(data.name);
users.push(name);
});
$(td).html(users.join(', '))
}}
......
......@@ -28,6 +28,7 @@ $(document).ready(function() {
buttons: [],
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
var detail_btn = '<a href="{% url "users:user-group-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
......@@ -36,6 +37,7 @@ $(document).ready(function() {
$(td).html(html);
}},
{targets: 3, createdCell: function (td, cellData) {
cellData = htmlEscape(cellData);
var innerHtml = cellData.length > 30 ? cellData.substring(0, 30) + '...': cellData;
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
}},
......
......@@ -59,6 +59,7 @@ function initTable() {
ele: $('#user_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
var detail_btn = '<a href="{% url "users:user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace("{{ DEFAULT_PK }}", rowData.id));
}},
......@@ -77,10 +78,16 @@ function initTable() {
}
}},
{targets: 7, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "users:user-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('00000000-0000-0000-0000-000000000000', cellData);
var update_btn = "";
if (rowData.role === 'Admin' && ('{{ request.user.role }}' !== 'Admin')) {
update_btn = '<a class="btn btn-xs disabled btn-info">{% trans "Update" %}</a>';
}
else{
update_btn = '<a href="{% url "users:user-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('00000000-0000-0000-0000-000000000000', cellData);
}
var del_btn = "";
if (rowData.id === 1 || rowData.username === "admin" || rowData.username === "{{ request.user.username }}") {
if (rowData.id === 1 || rowData.username === "admin" || rowData.username === "{{ request.user.username }}" || (rowData.role === 'Admin' && ('{{ request.user.role }}' !== 'Admin'))) {
del_btn = '<a class="btn btn-xs btn-danger m-l-xs" disabled>{% trans "Delete" %}</a>'
.replace('{{ DEFAULT_PK }}', cellData)
.replace('99991938', rowData.name);
......
# ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals
from django.core.cache import cache
from django.shortcuts import render
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import RedirectView
......@@ -84,7 +85,7 @@ class UserResetPasswordView(TemplateView):
def get(self, request, *args, **kwargs):
token = request.GET.get('token', '')
user = User.validate_reset_token(token)
user = User.validate_reset_password_token(token)
if not user:
kwargs.update({'errors': _('Token invalid or expired')})
else:
......@@ -100,12 +101,12 @@ class UserResetPasswordView(TemplateView):
if password != password_confirm:
return self.get(request, errors=_('Password not same'))
user = User.validate_reset_token(token)
user = User.validate_reset_password_token(token)
if not user:
return self.get(request, errors=_('Token invalid or expired'))
if not user.can_update_password():
error = _('User auth from {}, go there change password'.format(user.source))
return self.get(request, errors=error)
if not user:
return self.get(request, errors=_('Token invalid or expired'))
is_ok = check_password_rules(password)
if not is_ok:
......@@ -115,6 +116,7 @@ class UserResetPasswordView(TemplateView):
)
user.reset_password(password)
User.expired_reset_password_token(token)
return HttpResponseRedirect(reverse('users:reset-password-success'))
......
......@@ -107,6 +107,15 @@ class UserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
success_url = reverse_lazy('users:user-list')
success_message = update_success_msg
def _deny_permission(self):
obj = self.get_object()
return not self.request.user.is_superuser and obj.is_superuser
def get(self, request, *args, **kwargs):
if self._deny_permission():
return redirect(self.success_url)
return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
check_rules = get_password_check_rules()
context = {
......
......@@ -7,4 +7,4 @@ if [ ! -d $backup_dir ];then
mkdir $backup_dir
fi
mysqldump -uroot -h127.0.0.1 -p jumpserver > ${backup_dir}/jumpserver_$(date +'%Y-%m-%d_%H:%M:%S').sql
mysqldump -uroot -h127.0.0.1 -p jumpserver -P3307 > ${backup_dir}/jumpserver_$(date +'%Y-%m-%d_%H:%M:%S').sql
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