Commit 22f362aa authored by 八千流's avatar 八千流 Committed by 老广

Dev csv (#2640)

* [Update] 封装JMSCSVRender和JMSCSVParser

* [Update] 更改JMSCSVRender,根据请求参数控制导出csv的字段和下载csv模板的字段

* [Update] 导入空数据,提示错误消息

* [Update] 修改用户导入和导出功能代码

* [Update] 修改导入路由为动态反向解析

* [Update] 修改JMSCSVRender和JMSCSVParser以及用户导入导出代码

* [Update] 优化parsers逻辑

* [Update] 优化parsers csv代码结构

* [Update] 优化renders csv代码逻辑

* [Update] 删除parsers csv多余代码

* [Update] 删除parsers csv多余变量

* [Update] 优化renders csv代码结构

* [Update] 优化renders csv代码结构2

* [Update] 优化renders csv获取header逻辑

* [Update] 优化Cache Resources ID View逻辑

* [Update] 优化ViewSet IDCacheFilterMixin逻辑

* [Update] csv: parser render 添加异常捕获逻辑

* [Update] 删除多余代码

* [Update] 优化前端代码

* [Update] 修改小问题

* [Update] 修改前端导出用户的问题

* [Update] 前端 - 优化数据导出逻辑 APIExportData

* [Update] 修复批量创建用户时发送created信号的bug

* [Update] 优化导入时错误信息展示

* [Update] 优化parser、render时,对于多对多字段的处理

* [Update] 修改前端上传空文件问题

* [Update] 添加IDExportFilter,控制下载模版时的queryset

* [Update] 修改判断导出模版时参数变量名 action => template

* [Update] 修复导入用户数据时,用户组不生效的bug

* [Update] 修改前端导入信息展示

* [Update] 抽象资源导入模版

* [Update] 优化资源导入模版

* [Update] 修改js设置url的params逻辑

* [Update] 修改users序列类控制read_only字段方式

* [Update] 资产列表采用新的导入/导出csv文件逻辑

* [Update] 修改导入资产时设置资产所在节点逻辑

* [Update] 添加用户组导入/导出功能

* [Update] 修改前端变量名

* [Update] 修改下载导入模版,不包含org字段

* [Update] 增加管理用户导入/导出功能

* [Update] 导入模版提供id字段(为了资源备份后导入直接使用); 修复资源导入时联合唯一字段不校验导致创建时报错的bug

* [Update] 增加系统用户导入/导出功能

* [Update] 排序资源导入/导出字段

* [Update] 翻译导入/导出的字段和模版

* [Update] 更改csv导出和导出模版数据的控制在render实现

* [Update] 资产添加 更新导入 功能

* [Update] 用户/用户组/管理用户/系统用户/ 添加导入更新

* [Update] 翻译

* [Update] 优化资源序列化中的label

* [Update] 去掉资源IDInFilterMixin过滤

* [Update] 翻译
parent 49429008
......@@ -20,7 +20,7 @@ from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet
from rest_framework.pagination import LimitOffsetPagination
from common.mixins import IDInFilterMixin
from common.mixins import IDInCacheFilterMixin
from common.utils import get_logger
from ..hands import IsOrgAdmin
from ..models import AdminUser, Asset
......@@ -36,7 +36,7 @@ __all__ = [
]
class AdminUserViewSet(IDInFilterMixin, BulkModelViewSet):
class AdminUserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
"""
Admin user api set, for add,delete,update,list,retrieve resource
"""
......
......@@ -16,8 +16,9 @@ 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.mixins import IDInCacheFilterMixin
from common.utils import get_logger, get_object_or_none
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX
from ..models import Asset, AdminUser, Node
......@@ -35,7 +36,7 @@ __all__ = [
]
class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
class AssetViewSet(IDInCacheFilterMixin, LabelFilter, BulkModelViewSet):
"""
API endpoint that allows Asset to be viewed or edited.
"""
......@@ -47,6 +48,19 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
pagination_class = LimitOffsetPagination
permission_classes = (IsOrgAdminOrAppUser,)
def set_assets_node(self, assets):
if not isinstance(assets, list):
assets = [assets]
node = Node.objects.get(value='Default')
node_id = self.request.query_params.get('node_id')
if node_id:
node = get_object_or_none(Node, pk=node_id)
node.assets.add(*assets)
def perform_create(self, serializer):
assets = serializer.save()
self.set_assets_node(assets)
def filter_node(self, queryset):
node_id = self.request.query_params.get("node_id")
if not node_id:
......@@ -89,7 +103,7 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
return queryset
class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
class AssetListUpdateApi(IDInCacheFilterMixin, ListBulkCreateUpdateDestroyAPIView):
"""
Asset bulk update api
"""
......
......@@ -21,6 +21,7 @@ from rest_framework.pagination import LimitOffsetPagination
from common.utils import get_logger
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from common.mixins import IDInCacheFilterMixin
from ..models import SystemUser, Asset
from .. import serializers
from ..tasks import push_system_user_to_assets_manual, \
......@@ -38,7 +39,7 @@ __all__ = [
]
class SystemUserViewSet(BulkModelViewSet):
class SystemUserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
"""
System user api set, for add,delete,update,list,retrieve resource
"""
......
# -*- coding: utf-8 -*-
#
from django.core.cache import cache
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from common.serializers import AdaptedBulkListSerializer
......@@ -15,14 +16,29 @@ class AdminUserSerializer(serializers.ModelSerializer):
"""
管理用户
"""
assets_amount = serializers.SerializerMethodField()
unreachable_amount = serializers.SerializerMethodField()
reachable_amount = serializers.SerializerMethodField()
password = serializers.CharField(
required=False, write_only=True, label=_('Password')
)
unreachable_amount = serializers.SerializerMethodField(label=_('Unreachable'))
assets_amount = serializers.SerializerMethodField(label=_('Asset'))
reachable_amount = serializers.SerializerMethodField(label=_('Reachable'))
class Meta:
list_serializer_class = AdaptedBulkListSerializer
model = AdminUser
fields = '__all__'
fields = [
'id', 'org_id', 'name', 'username', 'assets_amount',
'reachable_amount', 'unreachable_amount', 'password', 'comment',
'date_created', 'date_updated', 'become', 'become_method',
'become_user', 'created_by',
]
extra_kwargs = {
'date_created': {'label': _('Date created')},
'date_updated': {'label': _('Date updated')},
'become': {'read_only': True}, 'become_method': {'read_only': True},
'become_user': {'read_only': True}, 'created_by': {'read_only': True}
}
def get_field_names(self, declared_fields, info):
fields = super().get_field_names(declared_fields, info)
......
......@@ -2,6 +2,9 @@
#
from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgResourceSerializerMixin
from common.mixins import BulkSerializerMixin
from common.serializers import AdaptedBulkListSerializer
from ..models import Asset
......@@ -13,15 +16,35 @@ __all__ = [
]
class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer, OrgResourceSerializerMixin):
"""
资产的数据结构
"""
class Meta:
model = Asset
list_serializer_class = AdaptedBulkListSerializer
fields = '__all__'
validators = []
# validators = [] # 解决批量导入时unique_together字段校验失败
fields = [
'id', 'org_id', 'org_name', 'ip', 'hostname', 'protocol', 'port',
'platform', 'is_active', 'public_ip', 'domain', 'admin_user',
'nodes', 'labels', 'number', 'vendor', 'model', 'sn',
'cpu_model', 'cpu_count', 'cpu_cores', 'cpu_vcpus', 'memory',
'disk_total', 'disk_info', 'os', 'os_version', 'os_arch',
'hostname_raw', 'comment', 'created_by', 'date_created',
'hardware_info', 'connectivity'
]
read_only_fields = (
'number', 'vendor', 'model', 'sn', 'cpu_model', 'cpu_count',
'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info',
'os', 'os_version', 'os_arch', 'hostname_raw',
'created_by', 'date_created',
)
extra_kwargs = {
'hardware_info': {'label': _('Hardware info')},
'connectivity': {'label': _('Connectivity')},
'org_name': {'label': _('Org name')}
}
@classmethod
def setup_eager_loading(cls, queryset):
......
from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from common.serializers import AdaptedBulkListSerializer
from ..models import SystemUser, Asset
......@@ -10,16 +12,36 @@ class SystemUserSerializer(serializers.ModelSerializer):
"""
系统用户
"""
unreachable_amount = serializers.SerializerMethodField()
reachable_amount = serializers.SerializerMethodField()
unreachable_assets = serializers.SerializerMethodField()
reachable_assets = serializers.SerializerMethodField()
assets_amount = serializers.SerializerMethodField()
password = serializers.CharField(
required=False, write_only=True, label=_('Password')
)
unreachable_amount = serializers.SerializerMethodField(
label=_('Unreachable')
)
unreachable_assets = serializers.SerializerMethodField(
label=_('Unreachable assets')
)
reachable_assets = serializers.SerializerMethodField(
label=_('Reachable assets')
)
reachable_amount = serializers.SerializerMethodField(label=_('Reachable'))
assets_amount = serializers.SerializerMethodField(label=_('Asset'))
class Meta:
model = SystemUser
exclude = ('_password', '_private_key', '_public_key')
list_serializer_class = AdaptedBulkListSerializer
fields = [
'id', 'org_id', 'name', 'username', 'login_mode',
'login_mode_display', 'priority', 'protocol', 'auto_push',
'password', 'assets_amount', 'reachable_amount', 'reachable_assets',
'unreachable_amount', 'unreachable_assets', 'cmd_filters', 'sudo',
'shell', 'comment', 'nodes', 'assets'
]
extra_kwargs = {
'login_mode_display': {'label': _('Login mode display')},
'created_by': {'read_only': True}, 'nodes': {'read_only': True},
'assets': {'read_only': True}
}
def get_field_names(self, declared_fields, info):
fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info)
......
{% extends '_import_modal.html' %}
{% load i18n %}
{% block modal_title%}{% trans "Import admin user" %}{% endblock %}
{% block import_modal_download_template_url %}{% url "api-assets:admin-user-list" %}{% endblock %}
{% extends '_update_modal.html' %}
{% load i18n %}
{% block modal_title%}{% trans "Update admin user" %}{% endblock %}
\ No newline at end of file
{% extends '_modal.html' %}
{% extends '_import_modal.html' %}
{% load i18n %}
{% block modal_id %}asset_import_modal{% endblock %}
{% block modal_title%}{% trans "Import asset" %}{% endblock %}
{% block modal_body %}
<form method="post" action="{% url 'assets:asset-import' %}" id="fm_asset_import" enctype="multipart/form-data">
{% csrf_token %}
<div class="form-group">
<label class="control-label" for="id_assets">{% trans "Template" %}</label>
<a href="{% url 'assets:asset-export' %}" style="display: block">{% trans 'Download' %}</a>
</div>
<div class="form-group">
<label class="control-label" for="id_users">{% trans "Asset csv file" %}</label>
<input id="id_assets" type="file" name="file" />
<span class="help-block red-fonts">
{% trans 'If set id, will use this id update asset existed' %}
</span>
</div>
</form>
<p>
<p class="text-success" id="id_created"></p>
<p id="id_created_detail"></p>
<p class="text-warning" id="id_updated"></p>
<p id="id_updated_detail"></p>
<p class="text-danger" id="id_failed"></p>
<p id="id_failed_detail"></p>
</p>
{% endblock %}
{% block modal_confirm_id %}btn_asset_import{% endblock %}
{% block modal_title%}{% trans "Import assets" %}{% endblock %}
{% block import_modal_download_template_url %}{% url "api-assets:asset-list" %}{% endblock %}
{% extends '_update_modal.html' %}
{% load i18n %}
{% block modal_title%}{% trans "Update assets" %}{% endblock %}
{% extends '_import_modal.html' %}
{% load i18n %}
{% block modal_title%}{% trans "Import system user" %}{% endblock %}
{% block import_modal_download_template_url %}{% url "api-assets:system-user-list" %}{% endblock %}
{% extends '_update_modal.html' %}
{% load i18n %}
{% block modal_title%}{% trans "Update system user" %}{% endblock %}
\ No newline at end of file
{% extends '_base_list.html' %}
{% load i18n static %}
{% block table_search %}
{% endblock %}
{% block help_message %}
<div class="alert alert-info help-message">
{# 管理用户是资产(被控服务器)上的root,或拥有 NOPASSWD: ALL sudo权限的用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。#}
......@@ -12,6 +9,30 @@
{% trans 'You can set any one for Windows or other hardware.' %}
</div>
{% endblock %}
{% block table_search %}
<div class="" style="float: right">
<div class=" btn-group">
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
<ul class="dropdown-menu">
<li>
<a class=" btn_export" tabindex="0">
<span>{% trans "Export" %}</span>
</a>
</li>
<li>
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
<span>{% trans "Import" %}</span>
</a>
</li>
<li>
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
<span>{% trans "Update" %}</span>
</a>
</li>
</ul>
</div>
</div>
{% endblock %}
{% block table_container %}
<div class="uc pull-left m-r-5">
......@@ -36,6 +57,8 @@
<tbody>
</tbody>
</table>
{% include 'assets/_admin_user_import_modal.html' %}
{% include 'assets/_admin_user_update_modal.html' %}
{% endblock %}
{% block content_bottom_left %}{% endblock %}
{% block custom_foot_js %}
......@@ -107,6 +130,82 @@ $(document).ready(function(){
$data_table.ajax.reload();
}, 3000);
});
})
.on('click', '.btn_export', function(){
var data_table = $('#admin_user_list_table').DataTable();
var rows = data_table.rows('.selected').data();
var admin_users = [];
$.each(rows, function (index, obj) {
admin_users.push(obj.id)
});
var data = {
'resources': admin_users
};
var search = $("input[type='search']").val();
var props = {
method: "POST",
body: JSON.stringify(data),
success_url: "{% url 'api-assets:admin-user-list' %}",
format: "csv",
params: {
search: search
}
};
APIExportData(props);
}).on('click', '#btn_import_confirm',function () {
var url = "{% url 'api-assets:admin-user-list' %}";
var file = document.getElementById('id_file').files[0];
if(!file){
toastr.error("{% trans "Please select file" %}");
return
}
var data_table = $('#admin_user_list_table').DataTable();
APIImportData({
url: url,
method: "POST",
body: file,
data_table: data_table
});
})
.on('click', '#download_update_template', function () {
var $data_table = $('#admin_user_list_table').DataTable();
var rows = $data_table.rows('.selected').data();
var admin_users = [];
$.each(rows, function (index, obj) {
admin_users.push(obj.id)
});
var data = {
'resources': admin_users
};
var search = $("input[type='search']").val();
var props = {
method: "POST",
body: JSON.stringify(data),
success_url: "{% url 'api-assets:admin-user-list' %}?format=csv&template=update",
format: 'csv',
params: {
search: search
}
};
APIExportData(props);
})
.on('click', '#btn_update_confirm', function () {
var file = document.getElementById('update_file').files[0];
if(!file){
toastr.error("{% trans "Please select file" %}");
return
}
var url = "{% url 'api-assets:admin-user-list' %}";
var data_table = $('#admin_user_list_table').DataTable();
APIImportData({
url: url,
method: "PUT",
body: file,
data_table: data_table
});
})
</script>
{% endblock %}
......@@ -67,14 +67,26 @@
</div>
<div class="mail-box-header">
<div class="uc pull-left m-r-5"><a class="btn btn-sm btn-primary btn-create-asset"> {% trans "Create asset" %} </a></div>
<div class="html5buttons">
<div class="dt-buttons btn-group">
<a class="btn btn-default btn_import" data-toggle="modal" data-target="#asset_import_modal" tabindex="0">
<span>{% trans "Import" %}</span>
</a>
<a class="btn btn-default btn_export" tabindex="0">
<span>{% trans "Export" %}</span>
</a>
<div class="" style="float: right">
<div class=" btn-group">
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
<ul class="dropdown-menu">
<li>
<a class=" btn_export" tabindex="0">
<span>{% trans "Export" %}</span>
</a>
</li>
<li>
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
<span>{% trans "Import" %}</span>
</a>
</li>
<li>
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
<span>{% trans "Update" %}</span>
</a>
</li>
</ul>
</div>
</div>
<div class="btn-group" style="float: right">
......@@ -140,7 +152,7 @@
{# <li id="fresh_tree" class="btn-refresh-tree" tabindex="-1"><a><i class="fa fa-refresh"></i> {% trans 'Refresh' %}</a></li>#}
</ul>
</div>
{% include 'assets/_asset_update_modal.html' %}
{% include 'assets/_asset_import_modal.html' %}
{% include 'assets/_asset_list_modal.html' %}
{% endblock %}
......@@ -464,42 +476,85 @@ $(document).ready(function(){
$.each(rows, function (index, obj) {
assets.push(obj.id)
});
$.ajax({
url: "{% url "assets:asset-export" %}",
method: 'POST',
data: JSON.stringify({assets_id: assets, node_id: current_node_id}),
dataType: "json",
success: function (data, textStatus) {
window.open(data.redirect)
},
error: function () {
toastr.error('Export failed');
var data = {
'resources': assets
};
var search = $("input[type='search']").val();
var props = {
method: "POST",
body: JSON.stringify(data),
success_url: "{% url 'api-assets:asset-list' %}",
format: 'csv',
params: {
search: search,
node_id: current_node_id || ''
}
})
};
APIExportData(props);
})
.on('click', '#btn_asset_import', function () {
var $form = $('#fm_asset_import');
var action = $form.attr("action");
.on('click', '#btn_import_confirm', function () {
var file = document.getElementById('id_file').files[0];
if(!file){
toastr.error("{% trans "Please select file" %}");
return
}
var url = "{% url 'api-assets:asset-list' %}";
if (current_node_id){
action = setUrlParam(action, 'node_id', current_node_id);
$form.attr("action", action)
url = setUrlParam(url, 'node_id', current_node_id);
}
$form.find('.help-block').remove();
function success (data) {
if (data.valid === false) {
$('<span />', {class: 'help-block text-danger'}).html(data.msg).insertAfter($('#id_assets'));
} else {
$('#id_created').html(data.created_info);
$('#id_created_detail').html(data.created.join(', '));
$('#id_updated').html(data.updated_info);
$('#id_updated_detail').html(data.updated.join(', '));
$('#id_failed').html(data.failed_info);
$('#id_failed_detail').html(data.failed.join(', '));
var $data_table = $('#asset_list_table').DataTable();
$data_table.ajax.reload();
var data_table = $('#asset_list_table').DataTable();
APIImportData({
url: url,
method: "POST",
body: file,
data_table: data_table
});
})
.on('click', '#download_update_template', function () {
var $data_table = $('#asset_list_table').DataTable();
var rows = $data_table.rows('.selected').data();
var assets = [];
$.each(rows, function (index, obj) {
assets.push(obj.id)
});
var data = {
'resources': assets
};
var search = $("input[type='search']").val();
var props = {
method: "POST",
body: JSON.stringify(data),
success_url: "{% url 'api-assets:asset-list' %}?format=csv&template=update",
format: 'csv',
params: {
search: search,
node_id: current_node_id || ''
}
};
APIExportData(props);
})
.on('click', '#btn_update_confirm', function () {
var file = document.getElementById('update_file').files[0];
if(!file){
toastr.error("{% trans "Please select file" %}");
return
}
var url = "{% url 'api-assets:asset-list' %}";
if (current_node_id){
url = setUrlParam(url, 'node_id', current_node_id);
}
$form.ajaxSubmit({success: success});
var data_table = $('#asset_list_table').DataTable();
APIImportData({
url: url,
method: "PUT",
body: file,
data_table: data_table
});
})
.on('click', '.btn-create-asset', function () {
var url = "{% url 'assets:asset-create' %}";
......@@ -593,6 +648,12 @@ $(document).ready(function(){
return false;
}
var the_url = "{% url 'api-assets:asset-list' %}";
var data = {
'resources': id_list
};
function refreshTag() {
$('#asset_list_table').DataTable().ajax.reload();
}
function doDeactive() {
var data = [];
......@@ -601,7 +662,8 @@ $(document).ready(function(){
data.push(obj);
});
function success() {
asset_table.ajax.reload()
setTimeout( function () {
window.location.reload();}, 500);
}
APIUpdateAttr({
url: the_url,
......@@ -617,7 +679,8 @@ $(document).ready(function(){
data.push(obj);
});
function success() {
asset_table.ajax.reload()
setTimeout( function () {
window.location.reload();}, 300);
}
APIUpdateAttr({
url: the_url,
......@@ -636,68 +699,72 @@ $(document).ready(function(){
confirmButtonColor: "#DD6B55",
confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: false
}, function() {
var success = function() {
},function () {
function success(data) {
url = setUrlParam(the_url, 'spm', data.spm);
APIUpdateAttr({
url:url,
method:'DELETE',
success:refreshTag,
flash_message:false,
});
var msg = "{% trans 'Asset Deleted.' %}";
swal("{% trans 'Asset Delete' %}", msg, "success");
$('#asset_list_table').DataTable().ajax.reload();
};
var fail = function() {
}
function fail() {
var msg = "{% trans 'Asset Deleting failed.' %}";
swal("{% trans 'Asset Delete' %}", msg, "error");
};
var url_delete = the_url + '?id__in=' + JSON.stringify(id_list);
}
APIUpdateAttr({
url: url_delete,
method: 'DELETE',
success: success,
error: fail
});
$data_table.ajax.reload();
jumpserver.checked = false;
});
url: "{% url 'api-common:resources-cache' %}",
method:'POST',
body:JSON.stringify(data),
success:success,
error:fail
})
})
}
function doUpdate() {
var data = {
'assets_id':id_list
};
function error(data) {
toastr.error(JSON.parse(data).error)
function fail(data) {
toastr.error(JSON.parse(data))
}
function success(data) {
location.href = data.url;
var url = "{% url 'assets:asset-bulk-update' %}";
location.href= setUrlParam(url, 'spm', data.spm);
}
APIUpdateAttr({
'url': "{% url 'api-assets:asset-bulk-update-select' %}",
'method': 'POST',
'body': JSON.stringify(data),
'flash_message': false,
'success': success,
'error': error,
url: "{% url 'api-common:resources-cache' %}",
method:'POST',
body:JSON.stringify(data),
flash_message:false,
success:success,
error:fail
})
}
}
function doRemove() {
var nodes = zTree.getSelectedNodes();
if (!current_node_id) {
return
}
var data = {
'assets': id_list
};
var success = function () {
asset_table.ajax.reload()
};
APIUpdateAttr({
'url': '/api/assets/v1/nodes/' + current_node_id + '/assets/remove/',
'method': 'PUT',
'body': JSON.stringify(data),
'success': success
})
var nodes = zTree.getSelectedNodes();
if (!current_node_id) {
return
}
var data = {
'assets': id_list
};
var success = function () {
asset_table.ajax.reload()
};
APIUpdateAttr({
'url': '/api/assets/v1/nodes/' + current_node_id + '/assets/remove/',
'method': 'PUT',
'body': JSON.stringify(data),
'success': success
})
}
switch(action) {
case 'deactive':
doDeactive();
......
......@@ -14,6 +14,28 @@
{% endblock %}
{% block table_search %}
<div class="" style="float: right">
<div class=" btn-group">
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
<ul class="dropdown-menu">
<li>
<a class=" btn_export" tabindex="0">
<span>{% trans "Export" %}</span>
</a>
</li>
<li>
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
<span>{% trans "Import" %}</span>
</a>
</li>
<li>
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
<span>{% trans "Update" %}</span>
</a>
</li>
</ul>
</div>
</div>
{% endblock %}
{% block table_container %}
......@@ -41,6 +63,8 @@
<tbody>
</tbody>
</table>
{% include 'assets/_system_user_import_modal.html' %}
{% include 'assets/_system_user_update_modal.html' %}
{% endblock %}
{% block custom_foot_js %}
<script>
......@@ -173,6 +197,81 @@ $(document).ready(function(){
break;
}
})
.on('click', '.btn_export', function () {
var data_table = $('#system_user_list_table').DataTable();
var rows = data_table.rows('.selected').data();
var system_users = [];
$.each(rows, function (index, obj) {
system_users.push(obj.id)
});
var data = {
'resources': system_users
};
var search = $("input[type='search']").val();
var props = {
method: "POST",
body: JSON.stringify(data),
success_url: "{% url 'api-assets:system-user-list' %}",
format: "csv",
params:{
search:search
}
};
APIExportData(props);
})
.on('click', '#btn_import_confirm', function () {
var url = "{% url 'api-assets:system-user-list' %}";
var file = document.getElementById('id_file').files[0];
if(!file){
toastr.error("{% trans 'Please select file' %}");
return
}
var data_table = $('#system_user_list_table').DataTable();
APIImportData({
url: url,
method: "POST",
body: file,
data_table: data_table
});
})
.on('click', '#download_update_template', function () {
var data_table = $('#system_user_list_table').DataTable();
var rows = data_table.rows('.selected').data();
var system_users = [];
$.each(rows, function (index, obj) {
system_users.push(obj.id)
});
var data = {
'resources': system_users
};
var search = $("input[type='search']").val();
var props = {
method: "POST",
body: JSON.stringify(data),
success_url: "{% url 'api-assets:system-user-list' %}?format=csv&template=update",
format: "csv",
params:{
search:search
}
};
APIExportData(props);
})
.on('click', '#btn_update_confirm', function () {
var file = document.getElementById('update_file').files[0];
if(!file){
toastr.error("{% trans "Please select file" %}");
return
}
var url = "{% url 'api-assets:system-user-list' %}";
var data_table = $('#system_user_list_table').DataTable();
APIImportData({
url: url,
method: "PUT",
body: file,
data_table: data_table
});
})
</script>
{% endblock %}
......
......@@ -27,7 +27,9 @@ from django.contrib.messages.views import SuccessMessageMixin
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 common.const import (
create_success_msg, update_success_msg, KEY_CACHE_RESOURCES_ID
)
from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX
from orgs.utils import current_org
from .. import forms
......@@ -122,7 +124,7 @@ class AssetBulkUpdateView(AdminUserRequiredMixin, ListView):
def get(self, request, *args, **kwargs):
spm = request.GET.get('spm', '')
assets_id = cache.get(CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX.format(spm))
assets_id = cache.get(KEY_CACHE_RESOURCES_ID.format(spm))
if kwargs.get('form'):
self.form = kwargs['form']
elif assets_id:
......
......@@ -3,10 +3,18 @@
import os
import uuid
from rest_framework.views import Response
from rest_framework import generics, serializers
from django.core.cache import cache
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import generics, serializers
from .const import KEY_CACHE_RESOURCES_ID
__all__ = [
'LogTailApi', 'ResourcesIDCacheApi',
]
class OutputSerializer(serializers.Serializer):
output = serializers.CharField()
......@@ -68,3 +76,14 @@ class LogTailApi(generics.RetrieveAPIView):
data, end, new_mark = self.read_from_file()
return Response({"data": data, 'end': end, 'mark': new_mark})
class ResourcesIDCacheApi(APIView):
def post(self, request, *args, **kwargs):
spm = str(uuid.uuid4())
resources_id = request.data.get('resources')
if resources_id:
cache_key = KEY_CACHE_RESOURCES_ID.format(spm)
cache.set(cache_key, resources_id, 300)
return Response({'spm': spm})
......@@ -7,3 +7,4 @@ 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_"
KEY_CACHE_RESOURCES_ID = "RESOURCES_ID_{}"
......@@ -3,12 +3,15 @@
from django.db import models
from django.http import JsonResponse
from django.utils import timezone
from django.core.cache import cache
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
from .const import KEY_CACHE_RESOURCES_ID
class NoDeleteQuerySet(models.query.QuerySet):
......@@ -65,6 +68,27 @@ class IDInFilterMixin(object):
return queryset
class IDInCacheFilterMixin(object):
def filter_queryset(self, queryset):
queryset = super(IDInCacheFilterMixin, self).filter_queryset(queryset)
spm = self.request.query_params.get('spm')
cache_key = KEY_CACHE_RESOURCES_ID.format(spm)
resources_id = cache.get(cache_key)
if resources_id and isinstance(resources_id, list):
queryset = queryset.filter(id__in=resources_id)
return queryset
class IDExportFilterMixin(object):
def filter_queryset(self, queryset):
# 下载导入模版
if self.request.query_params.get('template') == 'import':
return []
else:
return super(IDExportFilterMixin, self).filter_queryset(queryset)
class BulkSerializerMixin(object):
"""
Become rest_framework_bulk not support uuid as a primary key
......@@ -131,7 +155,11 @@ class BulkListSerializerMixin(object):
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
if 'id' in item.keys():
self.child.instance = self.instance.get(id=item['id']) if self.instance else None
if 'pk' in item.keys():
self.child.instance = self.instance.get(id=item['pk']) if self.instance else None
self.child.initial_data = item
# raw
validated = self.child.run_validation(item)
......
from .csv import *
\ No newline at end of file
# ~*~ coding: utf-8 ~*~
#
import json
import unicodecsv
from rest_framework.parsers import BaseParser
from rest_framework.exceptions import ParseError
from ..utils import get_logger
logger = get_logger(__file__)
class JMSCSVParser(BaseParser):
"""
Parses CSV file to serializer data
"""
media_type = 'text/csv'
@staticmethod
def _universal_newlines(stream):
"""
保证在`通用换行模式`下打开文件
"""
for line in stream.splitlines():
yield line
@staticmethod
def _gen_rows(csv_data, charset='utf-8', **kwargs):
csv_reader = unicodecsv.reader(csv_data, encoding=charset, **kwargs)
for row in csv_reader:
if not any(row): # 空行
continue
yield row
@staticmethod
def _get_fields_map(serializer):
fields_map = {}
fields = serializer.get_fields()
fields_map.update({v.label: k for k, v in fields.items()})
fields_map.update({k: k for k, _ in fields.items()})
return fields_map
@staticmethod
def _process_row(row):
"""
构建json数据前的行处理
"""
_row = []
for col in row:
# 列表转换
if isinstance(col, str) and col.find("[") != -1 and col.find("]") != -1:
# 替换中文格式引号
col = col.replace("“", '"').replace("”", '"').\
replace("‘", '"').replace('’', '"').replace("'", '"')
col = json.loads(col)
_row.append(col)
return _row
@staticmethod
def _process_row_data(row_data):
"""
构建json数据后的行数据处理
"""
_row_data = {}
for k, v in row_data.items():
if isinstance(v, list) \
or isinstance(v, str) and k.strip() and v.strip():
_row_data[k] = v
return _row_data
def parse(self, stream, media_type=None, parser_context=None):
parser_context = parser_context or {}
encoding = parser_context.get('encoding', 'utf-8')
try:
serializer = parser_context["view"].get_serializer()
except Exception as e:
logger.debug(e, exc_info=True)
raise ParseError('The resource does not support imports!')
try:
stream_data = stream.read()
binary = self._universal_newlines(stream_data)
rows = self._gen_rows(binary, charset=encoding)
header = next(rows)
fields_map = self._get_fields_map(serializer)
header = [fields_map.get(name, '') for name in header]
data = []
for row in rows:
row = self._process_row(row)
row_data = dict(zip(header, row))
row_data = self._process_row_data(row_data)
data.append(row_data)
return data
except Exception as e:
logger.debug(e, exc_info=True)
raise ParseError('CSV parse error!')
from .csv import *
\ No newline at end of file
# ~*~ coding: utf-8 ~*~
#
import unicodecsv
from six import BytesIO
from rest_framework.renderers import BaseRenderer
from rest_framework.utils import encoders, json
from ..utils import get_logger
logger = get_logger(__file__)
class JMSCSVRender(BaseRenderer):
media_type = 'text/csv'
format = 'csv'
@staticmethod
def _get_header(fields, template):
if template == 'import':
header = [
k for k, v in fields.items()
if not v.read_only and k != 'org_id'
]
elif template == 'update':
header = [k for k, v in fields.items() if not v.read_only]
else:
# template in ['export']
header = [k for k, v in fields.items() if not v.write_only]
return header
@staticmethod
def _gen_table(data, header, labels=None):
labels = labels or {}
yield [labels.get(k, k) for k in header]
for item in data:
row = [item.get(key) for key in header]
yield row
def render(self, data, media_type=None, renderer_context=None):
renderer_context = renderer_context or {}
encoding = renderer_context.get('encoding', 'utf-8')
request = renderer_context['request']
template = request.query_params.get('template', 'export')
view = renderer_context['view']
data = json.loads(json.dumps(data, cls=encoders.JSONEncoder))
if template == 'import':
data = [data[0]] if data else data
try:
serializer = view.get_serializer()
except Exception as e:
logger.debug(e, exc_info=True)
value = 'The resource not support export!'.encode('utf-8')
else:
fields = serializer.get_fields()
header = self._get_header(fields, template)
labels = {k: v.label for k, v in fields.items() if v.label}
table = self._gen_table(data, header, labels)
csv_buffer = BytesIO()
csv_writer = unicodecsv.writer(csv_buffer, encoding=encoding)
for row in table:
csv_writer.writerow(row)
value = csv_buffer.getvalue()
return value
# -*- coding: utf-8 -*-
#
from django.urls import path
from .. import api
app_name = 'common'
urlpatterns = [
path('resources/cache/',
api.ResourcesIDCacheApi.as_view(), name='resources-cache'),
]
......@@ -363,6 +363,16 @@ REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'common.permissions.IsOrgAdmin',
),
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
'common.renders.JMSCSVRender',
),
'DEFAULT_PARSER_CLASSES': (
'rest_framework.parsers.JSONParser',
'rest_framework.parsers.FormParser',
'common.parsers.JMSCSVParser'
),
'DEFAULT_AUTHENTICATION_CLASSES': (
# 'rest_framework.authentication.BasicAuthentication',
'authentication.backends.api.AccessKeyAuthentication',
......
......@@ -20,6 +20,7 @@ api_v1 = [
path('orgs/v1/', include('orgs.urls.api_urls', namespace='api-orgs')),
path('settings/v1/', include('settings.urls.api_urls', namespace='api-settings')),
path('authentication/v1/', include('authentication.urls.api_urls', namespace='api-auth')),
path('common/v1/', include('common.urls.api_urls', namespace='api-common')),
path('applications/v1/', include('applications.urls.api_urls', namespace='api-applications')),
]
......
......@@ -621,7 +621,7 @@ msgstr "管理用户"
#: assets/forms/asset.py:33 assets/forms/asset.py:69 assets/forms/asset.py:109
#: assets/templates/assets/asset_create.html:36
#: assets/templates/assets/asset_create.html:38
#: assets/templates/assets/asset_list.html:81
#: assets/templates/assets/asset_list.html:93
#: assets/templates/assets/asset_update.html:41
#: assets/templates/assets/asset_update.html:43
#: assets/templates/assets/user_asset_list.html:33
......@@ -680,6 +680,37 @@ msgstr "如果有多个的互相隔离的网络,设置资产属于的网域,
msgid "Select assets"
msgstr "选择资产"
#: assets/forms/domain.py:15 assets/forms/label.py:13
#: assets/models/asset.py:279 assets/models/authbook.py:27
#: assets/serializers/admin_user.py:31 assets/serializers/system_user.py:32
#: assets/templates/assets/admin_user_list.html:49
#: assets/templates/assets/domain_detail.html:60
#: assets/templates/assets/domain_list.html:26
#: assets/templates/assets/label_list.html:16
#: assets/templates/assets/system_user_list.html:55 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: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
#: terminal/backends/command/models.py:13 terminal/models.py:155
#: terminal/templates/terminal/command_list.html:40
#: terminal/templates/terminal/command_list.html:73
#: terminal/templates/terminal/session_list.html:41
#: terminal/templates/terminal/session_list.html:72
#: xpack/plugins/change_auth_plan/forms.py:114
#: xpack/plugins/change_auth_plan/models.py:409
#: xpack/plugins/change_auth_plan/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
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:14
#: xpack/plugins/cloud/models.py:187
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:63
#: xpack/plugins/orgs/templates/orgs/org_list.html:16
msgid "Asset"
msgstr "资产"
#: assets/forms/domain.py:51
msgid "Password should not contain special characters"
msgstr "不能包含特殊字符"
......@@ -688,16 +719,65 @@ msgstr "不能包含特殊字符"
msgid "SSH gateway support proxy SSH,RDP,VNC"
msgstr "SSH网关,支持代理SSH,RDP和VNC"
#: assets/forms/domain.py:73 assets/forms/user.py:84 assets/forms/user.py:146
#: assets/models/base.py:26 assets/models/cluster.py:18
#: assets/models/cmd_filter.py:20 assets/models/domain.py:20
#: assets/models/group.py:20 assets/models/label.py:18
#: assets/templates/assets/admin_user_detail.html:56
#: assets/templates/assets/admin_user_list.html:47
#: assets/templates/assets/cmd_filter_detail.html:61
#: assets/templates/assets/cmd_filter_list.html:24
#: assets/templates/assets/domain_detail.html:56
#: assets/templates/assets/domain_gateway_list.html:67
#: assets/templates/assets/domain_list.html:25
#: assets/templates/assets/label_list.html:14
#: assets/templates/assets/system_user_detail.html:58
#: assets/templates/assets/system_user_list.html:51 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: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
#: perms/templates/perms/asset_permission_user.html:54 settings/models.py:29
#: settings/templates/settings/_ldap_list_users_modal.html:38
#: settings/templates/settings/command_storage_create.html:41
#: settings/templates/settings/replay_storage_create.html:44
#: settings/templates/settings/terminal_setting.html:80
#: 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: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:35
#: users/templates/users/user_list.html:35
#: users/templates/users/user_profile.html:51
#: users/templates/users/user_pubkey_update.html:53
#: xpack/plugins/change_auth_plan/forms.py:97
#: xpack/plugins/change_auth_plan/models.py:58
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:61
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:12
#: xpack/plugins/cloud/models.py:49 xpack/plugins/cloud/models.py:119
#: xpack/plugins/cloud/templates/cloud/account_detail.html:50
#: xpack/plugins/cloud/templates/cloud/account_list.html:12
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:53
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:12
#: xpack/plugins/orgs/templates/orgs/org_detail.html:52
#: xpack/plugins/orgs/templates/orgs/org_list.html:12
msgid "Name"
msgstr "名称"
#: assets/forms/domain.py:74 assets/forms/user.py:85 assets/forms/user.py:147
#: assets/models/base.py:27
#: assets/templates/assets/_asset_user_auth_modal.html:15
#: assets/templates/assets/_asset_user_view_auth_modal.html:31
#: assets/templates/assets/admin_user_detail.html:60
#: assets/templates/assets/admin_user_list.html:27
#: assets/templates/assets/admin_user_list.html:48
#: assets/templates/assets/asset_asset_user_list.html:44
#: assets/templates/assets/domain_gateway_list.html:71
#: assets/templates/assets/system_user_detail.html:62
#: assets/templates/assets/system_user_list.html:30 audits/models.py:94
#: assets/templates/assets/system_user_list.html:52 audits/models.py:94
#: audits/templates/audits/login_log_list.html:51 authentication/forms.py:11
#: authentication/templates/authentication/login.html:64
#: authentication/templates/authentication/new_login.html:90
......@@ -707,7 +787,7 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC"
#: settings/templates/settings/_ldap_list_users_modal.html:37 users/forms.py:13
#: 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_list.html:36
#: users/templates/users/user_profile.html:47
#: xpack/plugins/change_auth_plan/forms.py:99
#: xpack/plugins/change_auth_plan/models.py:60
......@@ -724,7 +804,8 @@ msgid "Password or private key passphrase"
msgstr "密码或密钥密码"
#: assets/forms/user.py:26 assets/models/base.py:28
#: assets/serializers/asset_user.py:19
#: assets/serializers/admin_user.py:20 assets/serializers/asset_user.py:19
#: assets/serializers/system_user.py:16
#: assets/templates/assets/_asset_user_auth_modal.html:21
#: assets/templates/assets/_asset_user_view_auth_modal.html:37
#: authentication/forms.py:13
......@@ -792,7 +873,7 @@ msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig"
#: assets/templates/assets/_asset_list_modal.html:46
#: assets/templates/assets/admin_user_assets.html:49
#: assets/templates/assets/asset_detail.html:64
#: assets/templates/assets/asset_list.html:93
#: assets/templates/assets/asset_list.html:105
#: assets/templates/assets/domain_gateway_list.html:68
#: assets/templates/assets/system_user_asset.html:51
#: assets/templates/assets/user_asset_list.html:45
......@@ -810,7 +891,7 @@ msgstr "IP"
#: assets/templates/assets/_asset_user_view_auth_modal.html:25
#: assets/templates/assets/admin_user_assets.html:48
#: assets/templates/assets/asset_detail.html:60
#: assets/templates/assets/asset_list.html:92
#: assets/templates/assets/asset_list.html:104
#: assets/templates/assets/system_user_asset.html:50
#: assets/templates/assets/user_asset_list.html:44
#: assets/templates/assets/user_asset_list.html:162
......@@ -826,7 +907,7 @@ msgstr "主机名"
#: assets/models/user.py:136 assets/templates/assets/asset_detail.html:76
#: assets/templates/assets/domain_gateway_list.html:70
#: assets/templates/assets/system_user_detail.html:70
#: assets/templates/assets/system_user_list.html:31
#: assets/templates/assets/system_user_list.html:53
#: assets/templates/assets/user_asset_list.html:165
#: terminal/templates/terminal/session_list.html:75
msgid "Protocol"
......@@ -926,19 +1007,96 @@ msgstr "主机名原始"
msgid "Labels"
msgstr "标签管理"
#: assets/models/asset.py:109 assets/models/base.py:34
#: assets/models/cluster.py:28 assets/models/cmd_filter.py:25
#: assets/models/cmd_filter.py:58 assets/models/group.py:21
#: assets/templates/assets/admin_user_detail.html:68
#: assets/templates/assets/asset_detail.html:128
#: assets/templates/assets/cmd_filter_detail.html:77
#: assets/templates/assets/domain_detail.html:72
#: assets/templates/assets/system_user_detail.html:100
#: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:15 perms/models.py:57
#: perms/models.py:110 perms/templates/perms/asset_permission_detail.html:98
#: users/models/user.py:102 users/serializers/v1.py:69
#: 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
msgid "Created by"
msgstr "创建者"
#: assets/models/asset.py:110 assets/models/cluster.py:26
#: assets/models/domain.py:23 assets/models/group.py:22
#: assets/models/label.py:25 assets/serializers/admin_user.py:23
#: assets/templates/assets/admin_user_detail.html:64
#: assets/templates/assets/cmd_filter_detail.html:69
#: 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: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
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:105
#: xpack/plugins/cloud/models.py:56 xpack/plugins/cloud/models.py:128
#: xpack/plugins/cloud/templates/cloud/account_detail.html:66
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:77
#: xpack/plugins/orgs/templates/orgs/org_detail.html:60
msgid "Date created"
msgstr "创建日期"
#: assets/models/asset.py:111 assets/models/base.py:31
#: assets/models/cluster.py:29 assets/models/cmd_filter.py:22
#: assets/models/cmd_filter.py:55 assets/models/domain.py:21
#: assets/models/domain.py:53 assets/models/group.py:23
#: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:72
#: assets/templates/assets/admin_user_list.html:53
#: assets/templates/assets/asset_detail.html:136
#: assets/templates/assets/cmd_filter_detail.html:65
#: assets/templates/assets/cmd_filter_list.html:27
#: assets/templates/assets/cmd_filter_rule_list.html:62
#: assets/templates/assets/domain_detail.html:76
#: assets/templates/assets/domain_gateway_list.html:72
#: assets/templates/assets/domain_list.html:28
#: assets/templates/assets/system_user_detail.html:104
#: assets/templates/assets/system_user_list.html:59
#: assets/templates/assets/user_asset_list.html:171 ops/models/adhoc.py:43
#: 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:94
#: users/templates/users/user_detail.html:127
#: users/templates/users/user_group_detail.html:67
#: users/templates/users/user_group_list.html:37
#: users/templates/users/user_profile.html:134
#: xpack/plugins/change_auth_plan/models.py:99
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:117
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:19
#: xpack/plugins/cloud/models.py:54 xpack/plugins/cloud/models.py:125
#: xpack/plugins/cloud/templates/cloud/account_detail.html:70
#: xpack/plugins/cloud/templates/cloud/account_list.html:15
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:69
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:16
#: xpack/plugins/orgs/templates/orgs/org_detail.html:64
#: xpack/plugins/orgs/templates/orgs/org_list.html:22
msgid "Comment"
msgstr "备注"
#: assets/models/asset.py:117 assets/models/base.py:38
#: assets/templates/assets/admin_user_list.html:30
#: assets/templates/assets/system_user_list.html:35
#: assets/serializers/admin_user.py:29 assets/serializers/system_user.py:22
#: assets/templates/assets/admin_user_list.html:51
#: assets/templates/assets/system_user_list.html:57
msgid "Unreachable"
msgstr "不可达"
#: assets/models/asset.py:118 assets/models/base.py:39
#: assets/serializers/admin_user.py:32 assets/serializers/system_user.py:31
#: assets/templates/assets/admin_user_assets.html:51
#: assets/templates/assets/admin_user_list.html:29
#: assets/templates/assets/admin_user_list.html:50
#: assets/templates/assets/asset_list.html:107
#: assets/templates/assets/asset_asset_user_list.html:46
#: assets/templates/assets/asset_list.html:95
#: assets/templates/assets/system_user_asset.html:53
#: assets/templates/assets/system_user_list.html:34
#: assets/templates/assets/system_user_list.html:56
#: users/templates/users/user_group_granted_asset.html:47
msgid "Reachable"
msgstr "可连接"
......@@ -1085,6 +1243,42 @@ msgstr "内容"
msgid "One line one command"
msgstr "每行一个命令"
#: assets/models/cmd_filter.py:54
#: assets/templates/assets/admin_user_assets.html:52
#: assets/templates/assets/admin_user_list.html:54
#: assets/templates/assets/asset_list.html:108
#: assets/templates/assets/asset_asset_user_list.html:48
#: assets/templates/assets/cmd_filter_list.html:28
#: assets/templates/assets/cmd_filter_rule_list.html:63
#: assets/templates/assets/domain_gateway_list.html:73
#: assets/templates/assets/domain_list.html:29
#: assets/templates/assets/label_list.html:17
#: assets/templates/assets/system_user_asset.html:54
#: assets/templates/assets/system_user_list.html:60
#: assets/templates/assets/user_asset_list.html:48 audits/models.py:38
#: audits/templates/audits/operate_log_list.html:41
#: 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
#: terminal/templates/terminal/terminal_list.html:36
#: users/templates/users/user_group_list.html:38
#: users/templates/users/user_list.html:41
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:60
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:18
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:20
#: xpack/plugins/cloud/templates/cloud/account_list.html:16
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:18
#: xpack/plugins/orgs/templates/orgs/org_list.html:23
msgid "Action"
msgstr "动作"
#: assets/models/cmd_filter.py:64
msgid "Command filter rule"
msgstr "命令过滤规则"
......@@ -1125,9 +1319,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:36 users/models/user.py:467
#: users/models/user.py:36 users/models/user.py:467 users/serializers/v1.py:61
#: users/templates/users/user_group_detail.html:78
#: users/templates/users/user_group_list.html:13 users/views/user.py:395
#: users/templates/users/user_group_list.html:36 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
......@@ -1174,7 +1368,7 @@ msgid "Shell"
msgstr "Shell"
#: assets/models/user.py:140 assets/templates/assets/system_user_detail.html:66
#: assets/templates/assets/system_user_list.html:32
#: assets/templates/assets/system_user_list.html:54
msgid "Login mode"
msgstr "登录模式"
......@@ -1183,6 +1377,25 @@ msgstr "登录模式"
msgid "%(value)s is not an even number"
msgstr "%(value)s is not an even number"
#: assets/serializers/admin_user.py:26
#: assets/templates/assets/asset_asset_user_list.html:51
#: assets/templates/assets/cmd_filter_detail.html:73
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:109
msgid "Date updated"
msgstr "更新日期"
#: assets/serializers/asset.py:20
msgid "Org name"
msgstr "组织名"
#: assets/serializers/asset.py:22
msgid "Hardware info"
msgstr "硬件信息"
#: assets/serializers/asset.py:25
msgid "Connectivity"
msgstr "连接"
#: assets/serializers/asset_user.py:23 users/forms.py:230
#: users/models/user.py:91 users/templates/users/first_login.html:42
#: users/templates/users/user_password_update.html:46
......@@ -1192,6 +1405,18 @@ msgstr "%(value)s is not an even number"
msgid "Public key"
msgstr "ssh公钥"
#: assets/serializers/system_user.py:19
msgid "Login mode display"
msgstr "登录模式显示"
#: assets/serializers/system_user.py:25
msgid "Unreachable assets"
msgstr "不可达资产"
#: assets/serializers/system_user.py:29
msgid "Reachable assets"
msgstr "可连接资产"
#: assets/tasks.py:31
msgid "Asset has been disabled, skipped: {}"
msgstr "资产或许不支持ansible, 跳过: {}"
......@@ -1261,6 +1486,15 @@ msgstr "推送系统用户到入资产: {} => {}"
msgid "Test asset user connectivity: {}"
msgstr "测试资产用户可连接性: {}"
#: assets/templates/assets/_admin_user_import_modal.html:4
msgid "Import admin user"
msgstr "导入管理用户"
#: assets/templates/assets/_admin_user_update_modal.html:4
#: assets/views/admin_user.py:64
msgid "Update admin user"
msgstr "更新管理用户"
#: assets/templates/assets/_asset_group_bulk_update_modal.html:5
msgid "Update asset group"
msgstr "更新用户组"
......@@ -1290,32 +1524,18 @@ msgid "Enable-MFA"
msgstr "启用MFA"
#: assets/templates/assets/_asset_import_modal.html:4
msgid "Import asset"
msgid "Import assets"
msgstr "导入资产"
#: assets/templates/assets/_asset_import_modal.html:9
#: users/templates/users/_user_import_modal.html:10
msgid "Template"
msgstr "模板"
#: assets/templates/assets/_asset_import_modal.html:10
#: users/templates/users/_user_import_modal.html:11
msgid "Download"
msgstr "下载"
#: assets/templates/assets/_asset_import_modal.html:13
msgid "Asset csv file"
msgstr "资产csv文件"
#: assets/templates/assets/_asset_import_modal.html:16
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:52
#: templates/_nav.html:22 xpack/plugins/change_auth_plan/views.py:110
msgid "Asset list"
msgstr "资产列表"
#: assets/templates/assets/_asset_update_modal.html:4
msgid "Update assets"
msgstr "更新资产"
#: assets/templates/assets/_asset_user_auth_modal.html:4
msgid "Update asset user auth"
msgstr "更新资产用户认证信息"
......@@ -1425,6 +1645,84 @@ msgstr "自动生成密钥"
msgid "Other"
msgstr "其它"
#: assets/templates/assets/_system_user.html:75
#: assets/templates/assets/admin_user_create_update.html:45
#: assets/templates/assets/asset_bulk_update.html:23
#: assets/templates/assets/asset_create.html:67
#: assets/templates/assets/asset_update.html:71
#: assets/templates/assets/cmd_filter_create_update.html:15
#: assets/templates/assets/cmd_filter_rule_create_update.html:40
#: 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: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:152
#: settings/templates/settings/security_setting.html:70
#: settings/templates/settings/terminal_setting.html:68
#: terminal/templates/terminal/terminal_update.html:45
#: users/templates/users/_user.html:50
#: users/templates/users/user_bulk_update.html:23
#: users/templates/users/user_detail.html:176
#: users/templates/users/user_password_update.html:71
#: users/templates/users/user_profile.html:204
#: users/templates/users/user_profile_update.html:63
#: users/templates/users/user_pubkey_update.html:70
#: users/templates/users/user_pubkey_update.html:76
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:71
#: xpack/plugins/cloud/templates/cloud/account_create_update.html:33
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:35
#: xpack/plugins/interface/templates/interface/interface.html:72
msgid "Reset"
msgstr "重置"
#: assets/templates/assets/_system_user.html:76
#: assets/templates/assets/admin_user_create_update.html:46
#: assets/templates/assets/asset_bulk_update.html:24
#: assets/templates/assets/asset_create.html:68
#: assets/templates/assets/asset_list.html:125
#: assets/templates/assets/asset_update.html:72
#: assets/templates/assets/cmd_filter_create_update.html:16
#: assets/templates/assets/cmd_filter_rule_create_update.html:41
#: assets/templates/assets/domain_create_update.html:17
#: 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: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:153
#: settings/templates/settings/security_setting.html:71
#: settings/templates/settings/terminal_setting.html:70
#: terminal/templates/terminal/command_list.html:103
#: terminal/templates/terminal/session_list.html:126
#: terminal/templates/terminal/terminal_update.html:46
#: users/templates/users/_user.html:51
#: users/templates/users/forgot_password.html:42
#: users/templates/users/user_bulk_update.html:24
#: users/templates/users/user_list.html:57
#: users/templates/users/user_password_update.html:72
#: 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:74
msgid "Submit"
msgstr "提交"
#: assets/templates/assets/_system_user_import_modal.html:4
msgid "Import system user"
msgstr "导入系统用户"
#: assets/templates/assets/_system_user_update_modal.html:4
#: assets/views/system_user.py:61
msgid "Update system user"
msgstr "更新系统用户"
#: assets/templates/assets/_user_asset_detail_modal.html:11
#: assets/templates/assets/asset_asset_user_list.html:13
#: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:187
......@@ -1495,6 +1793,82 @@ msgstr "更新成功"
msgid "Update failed!"
msgstr "更新失败"
#: assets/templates/assets/admin_user_detail.html:24
#: assets/templates/assets/admin_user_list.html:29
#: assets/templates/assets/admin_user_list.html:111
#: assets/templates/assets/asset_detail.html:27
#: assets/templates/assets/asset_list.html:86
#: assets/templates/assets/asset_list.html:190
#: assets/templates/assets/cmd_filter_detail.html:29
#: 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:54
#: assets/templates/assets/label_list.html:39
#: assets/templates/assets/system_user_detail.html:26
#: assets/templates/assets/system_user_list.html:33
#: assets/templates/assets/system_user_list.html:117 audits/models.py:33
#: perms/templates/perms/asset_permission_detail.html:30
#: perms/templates/perms/asset_permission_list.html:181
#: terminal/templates/terminal/terminal_detail.html:16
#: 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:20
#: users/templates/users/user_group_list.html:69
#: users/templates/users/user_list.html:20
#: users/templates/users/user_list.html:96
#: users/templates/users/user_list.html:99
#: users/templates/users/user_profile.html:177
#: users/templates/users/user_profile.html:187
#: users/templates/users/user_profile.html:196
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:29
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:55
#: xpack/plugins/cloud/templates/cloud/account_detail.html:23
#: xpack/plugins/cloud/templates/cloud/account_list.html:39
#: xpack/plugins/orgs/templates/orgs/org_detail.html:25
#: xpack/plugins/orgs/templates/orgs/org_list.html:87
msgid "Update"
msgstr "更新"
#: assets/templates/assets/admin_user_detail.html:28
#: assets/templates/assets/admin_user_list.html:112
#: assets/templates/assets/asset_detail.html:31
#: assets/templates/assets/asset_list.html:191
#: assets/templates/assets/cmd_filter_detail.html:33
#: 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:55
#: assets/templates/assets/label_list.html:40
#: assets/templates/assets/system_user_detail.html:30
#: assets/templates/assets/system_user_list.html:118 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:182
#: settings/templates/settings/terminal_setting.html:90
#: settings/templates/settings/terminal_setting.html:112
#: 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:71
#: users/templates/users/user_list.html:104
#: users/templates/users/user_list.html:108
#: 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:57
#: xpack/plugins/cloud/templates/cloud/account_detail.html:27
#: xpack/plugins/cloud/templates/cloud/account_list.html:41
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:30
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:55
#: xpack/plugins/orgs/templates/orgs/org_detail.html:29
#: xpack/plugins/orgs/templates/orgs/org_list.html:89
msgid "Delete"
msgstr "删除"
#: assets/templates/assets/admin_user_detail.html:83
msgid "Replace node assets admin user with this"
msgstr "替换资产的管理员"
......@@ -1506,35 +1880,94 @@ msgstr "替换资产的管理员"
msgid "Select nodes"
msgstr "选择节点"
#: assets/templates/assets/admin_user_list.html:10
#: assets/templates/assets/admin_user_detail.html:100
#: assets/templates/assets/asset_detail.html:211
#: assets/templates/assets/asset_list.html:692
#: 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:168
#: 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
#: users/templates/users/user_detail.html:414
#: users/templates/users/user_detail.html:437
#: users/templates/users/user_detail.html:482
#: users/templates/users/user_group_create_update.html:32
#: users/templates/users/user_group_list.html:114
#: users/templates/users/user_list.html:260
#: users/templates/users/user_profile.html:238
#: xpack/plugins/cloud/templates/cloud/account_create_update.html:34
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:36
#: xpack/plugins/interface/templates/interface/interface.html:103
#: xpack/plugins/orgs/templates/orgs/org_create_update.html:33
msgid "Confirm"
msgstr "确认"
#: assets/templates/assets/admin_user_list.html:7
msgid ""
"Admin users are asset (charged server) on the root, or have NOPASSWD: ALL "
"sudo permissions users, "
msgstr ""
"管理用户是资产(被控服务器)上的root,或拥有 NOPASSWD: ALL sudo权限的用户,"
#: assets/templates/assets/admin_user_list.html:11
#: assets/templates/assets/admin_user_list.html:8
msgid ""
"Jumpserver users of the system using the user to `push system user`, `get "
"assets hardware information`, etc. "
msgstr "Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。"
#: assets/templates/assets/admin_user_list.html:12
#: assets/templates/assets/admin_user_list.html:9
msgid "You can set any one for Windows or other hardware."
msgstr "Windows或其它硬件可以随意设置一个"
#: assets/templates/assets/admin_user_list.html:18
#: assets/templates/assets/admin_user_list.html:19
#: assets/templates/assets/asset_list.html:76
#: assets/templates/assets/system_user_list.html:23
#: audits/templates/audits/login_log_list.html:85
#: users/templates/users/user_group_list.html:10
#: users/templates/users/user_list.html:10
msgid "Export"
msgstr "导出"
#: assets/templates/assets/admin_user_list.html:24
#: assets/templates/assets/asset_list.html:81
#: assets/templates/assets/system_user_list.html:28
#: settings/templates/settings/_ldap_list_users_modal.html:97
#: users/templates/users/user_group_list.html:15
#: users/templates/users/user_list.html:15
#: xpack/plugins/license/templates/license/license_detail.html:110
msgid "Import"
msgstr "导入"
#: assets/templates/assets/admin_user_list.html:39
#: assets/views/admin_user.py:48
msgid "Create admin user"
msgstr "创建管理用户"
#: assets/templates/assets/admin_user_list.html:31
#: assets/templates/assets/system_user_list.html:36
#: assets/templates/assets/admin_user_list.html:52
#: assets/templates/assets/system_user_list.html:58
#: ops/templates/ops/adhoc_history.html:54
#: ops/templates/ops/task_history.html:60
msgid "Ratio"
msgstr "比例"
#: assets/templates/assets/admin_user_list.html:159
#: assets/templates/assets/admin_user_list.html:197
#: assets/templates/assets/asset_list.html:499
#: assets/templates/assets/asset_list.html:543
#: assets/templates/assets/system_user_list.html:226
#: assets/templates/assets/system_user_list.html:262
#: users/templates/users/user_group_list.html:163
#: users/templates/users/user_group_list.html:199
#: users/templates/users/user_list.html:162
#: users/templates/users/user_list.html:198
#, fuzzy
#| msgid "Please Select User"
msgid "Please select file"
msgstr "选择用户"
#: assets/templates/assets/asset_asset_user_list.html:16
#: assets/templates/assets/asset_detail.html:23 assets/views/asset.py:68
msgid "Asset user list"
......@@ -1548,6 +1981,7 @@ msgstr "资产用户"
msgid "Password version"
msgstr "密码版本"
#: assets/templates/assets/asset_asset_user_list.html:47
#: assets/templates/assets/cmd_filter_detail.html:73
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:109
......@@ -1626,146 +2060,133 @@ msgstr ""
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:110
msgid "Import"
msgstr "导入"
#: assets/templates/assets/asset_list.html:76
#: audits/templates/audits/login_log_list.html:85
#: users/templates/users/user_list.html:10
msgid "Export"
msgstr "导出"
#: assets/templates/assets/asset_list.html:94
#: assets/templates/assets/asset_list.html:106
msgid "Hardware"
msgstr "硬件"
#: assets/templates/assets/asset_list.html:105
#: users/templates/users/user_list.html:38
#: assets/templates/assets/asset_list.html:117
#: users/templates/users/user_list.html:50
msgid "Delete selected"
msgstr "批量删除"
#: assets/templates/assets/asset_list.html:106
#: users/templates/users/user_list.html:39
#: assets/templates/assets/asset_list.html:118
#: users/templates/users/user_list.html:51
msgid "Update selected"
msgstr "批量更新"
#: assets/templates/assets/asset_list.html:107
#: assets/templates/assets/asset_list.html:119
msgid "Remove from this node"
msgstr "从节点移除"
#: assets/templates/assets/asset_list.html:108
#: users/templates/users/user_list.html:40
#: assets/templates/assets/asset_list.html:120
#: users/templates/users/user_list.html:52
msgid "Deactive selected"
msgstr "禁用所选"
#: assets/templates/assets/asset_list.html:109
#: users/templates/users/user_list.html:41
#: assets/templates/assets/asset_list.html:121
#: users/templates/users/user_list.html:53
msgid "Active selected"
msgstr "激活所选"
#: assets/templates/assets/asset_list.html:126
#: assets/templates/assets/asset_list.html:138
msgid "Add node"
msgstr "新建节点"
#: assets/templates/assets/asset_list.html:127
#: assets/templates/assets/asset_list.html:139
msgid "Rename node"
msgstr "重命名节点"
#: assets/templates/assets/asset_list.html:128
#: assets/templates/assets/asset_list.html:140
msgid "Delete node"
msgstr "删除节点"
#: assets/templates/assets/asset_list.html:130
#: assets/templates/assets/asset_list.html:142
msgid "Add assets to node"
msgstr "添加资产到节点"
#: assets/templates/assets/asset_list.html:131
#: assets/templates/assets/asset_list.html:143
msgid "Move assets to node"
msgstr "移动资产到节点"
#: assets/templates/assets/asset_list.html:133
#: assets/templates/assets/asset_list.html:145
msgid "Refresh node hardware info"
msgstr "更新节点资产硬件信息"
#: assets/templates/assets/asset_list.html:134
#: assets/templates/assets/asset_list.html:146
msgid "Test node connective"
msgstr "测试节点资产可连接性"
#: assets/templates/assets/asset_list.html:136
#: assets/templates/assets/asset_list.html:148
msgid "Refresh all node assets amount"
msgstr "刷新所有节点资产数量"
#: assets/templates/assets/asset_list.html:138
#: assets/templates/assets/asset_list.html:150
msgid "Display only current node assets"
msgstr "仅显示当前节点资产"
#: assets/templates/assets/asset_list.html:139
#: assets/templates/assets/asset_list.html:151
msgid "Displays all child node assets"
msgstr "显示所有子节点资产"
#: assets/templates/assets/asset_list.html:217
#: assets/templates/assets/asset_list.html:229
msgid "Create node failed"
msgstr "创建节点失败"
#: assets/templates/assets/asset_list.html:229
#: assets/templates/assets/asset_list.html:241
msgid "Have child node, cancel"
msgstr "存在子节点,不能删除"
#: assets/templates/assets/asset_list.html:231
#: assets/templates/assets/asset_list.html:243
msgid "Have assets, cancel"
msgstr "存在资产,不能删除"
#: assets/templates/assets/asset_list.html:302
#: assets/templates/assets/asset_list.html:314
msgid "Rename success"
msgstr "重命名成功"
#: assets/templates/assets/asset_list.html:303
#: assets/templates/assets/asset_list.html:315
msgid "Rename failed, do not change the root node name"
msgstr "重命名失败,不能更改root节点的名称"
#: assets/templates/assets/asset_list.html:631
#: assets/templates/assets/system_user_list.html:138
#: assets/templates/assets/asset_list.html:686
#: assets/templates/assets/system_user_list.html:162
#: 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:84
#: users/templates/users/user_list.html:209
#: users/templates/users/user_group_list.html:108
#: users/templates/users/user_list.html:254
#: xpack/plugins/interface/templates/interface/interface.html:97
msgid "Are you sure?"
msgstr "你确认吗?"
#: assets/templates/assets/asset_list.html:632
#: assets/templates/assets/asset_list.html:687
msgid "This will delete the selected assets !!!"
msgstr "删除选择资产"
#: assets/templates/assets/asset_list.html:635
#: assets/templates/assets/system_user_list.html:142
#: assets/templates/assets/asset_list.html:690
#: assets/templates/assets/system_user_list.html:166
#: 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:88
#: users/templates/users/user_list.html:213
#: users/templates/users/user_group_list.html:112
#: users/templates/users/user_list.html:258
#: 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:641
#: assets/templates/assets/asset_list.html:696
msgid "Asset Deleted."
msgstr "已被删除"
#: assets/templates/assets/asset_list.html:642
#: assets/templates/assets/asset_list.html:647
#: assets/templates/assets/asset_list.html:697
#: assets/templates/assets/asset_list.html:702
msgid "Asset Delete"
msgstr "删除"
#: assets/templates/assets/asset_list.html:646
#: assets/templates/assets/asset_list.html:701
msgid "Asset Deleting failed."
msgstr "删除失败"
......@@ -1948,25 +2369,25 @@ msgstr ""
"资产中,如果资产(交换机、windows)不支持ansible, 请手动填写账号密码。目前还不"
"支持Windows的自动推送"
#: assets/templates/assets/system_user_list.html:21
#: assets/templates/assets/system_user_list.html:43
#: assets/views/system_user.py:45
msgid "Create system user"
msgstr "创建系统用户"
#: assets/templates/assets/system_user_list.html:139
#: assets/templates/assets/system_user_list.html:163
msgid "This will delete the selected System Users !!!"
msgstr "删除选择系统用户"
#: assets/templates/assets/system_user_list.html:148
#: assets/templates/assets/system_user_list.html:172
msgid "System Users Deleted."
msgstr "已被删除"
#: assets/templates/assets/system_user_list.html:149
#: assets/templates/assets/system_user_list.html:154
#: assets/templates/assets/system_user_list.html:173
#: assets/templates/assets/system_user_list.html:178
msgid "System Users Delete"
msgstr "删除系统用户"
#: assets/templates/assets/system_user_list.html:153
#: assets/templates/assets/system_user_list.html:177
msgid "System Users Deleting failed."
msgstr "系统用户删除失败"
......@@ -1974,10 +2395,6 @@ msgstr "系统用户删除失败"
msgid "Admin user list"
msgstr "管理用户列表"
#: assets/views/admin_user.py:64
msgid "Update admin user"
msgstr "更新管理用户"
#: assets/views/admin_user.py:79 assets/views/admin_user.py:103
msgid "Admin user detail"
msgstr "管理用户详情"
......@@ -2058,10 +2475,6 @@ msgstr "更新标签"
msgid "System user list"
msgstr "系统用户列表"
#: assets/views/system_user.py:61
msgid "Update system user"
msgstr "更新系统用户"
#: assets/views/system_user.py:75
msgid "System user detail"
msgstr "系统用户详情"
......@@ -2551,11 +2964,11 @@ msgstr ""
msgid "Encrypt field using Secret Key"
msgstr ""
#: common/mixins.py:32
#: common/mixins.py:35
msgid "is discard"
msgstr ""
#: common/mixins.py:33
#: common/mixins.py:36
msgid "discard time"
msgstr ""
......@@ -2914,7 +3327,7 @@ msgstr "命令执行列表"
msgid "Command execution"
msgstr "命令执行"
#: orgs/mixins.py:77 orgs/models.py:24
#: orgs/mixins.py:81 orgs/models.py:24
msgid "Organization"
msgstr "组织管理"
......@@ -2940,7 +3353,7 @@ msgstr "下载文件"
#: templates/_nav.html:14 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
#: users/templates/users/user_list.html:38
#: xpack/plugins/orgs/templates/orgs/org_list.html:15
msgid "User group"
msgstr "用户组"
......@@ -3065,9 +3478,9 @@ msgstr "创建授权规则"
#: perms/templates/perms/asset_permission_list.html:59
#: perms/templates/perms/asset_permission_list.html:73
#: users/templates/users/user_list.html:40 xpack/plugins/cloud/models.py:53
#: xpack/plugins/cloud/templates/cloud/account_detail.html:60
#: perms/templates/perms/remote_app_permission_list.html:18
#: users/templates/users/user_list.html:28 xpack/plugins/cloud/models.py:53
#: xpack/plugins/cloud/templates/cloud/account_detail.html:58
#: xpack/plugins/cloud/templates/cloud/account_list.html:14
msgid "Validity"
msgstr "有效"
......@@ -3700,6 +4113,18 @@ msgstr "注销登录"
msgid "Dashboard"
msgstr "仪表盘"
#: templates/_import_modal.html:12
msgid "Download the imported template or use the exported CSV file format"
msgstr "下载导入的模板或使用导出的csv格式"
#: templates/_import_modal.html:13
msgid "Download the import template"
msgstr "下载导入模版"
#: templates/_import_modal.html:17 templates/_update_modal.html:17
msgid "Select the CSV file to import"
msgstr "请选择csv文件导入"
#: templates/_message.html:7
#, python-format
msgid ""
......@@ -3840,6 +4265,14 @@ msgid ""
"Displays the results of items _START_ to _END_; A total of _TOTAL_ entries"
msgstr "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项"
#: templates/_update_modal.html:12
msgid "Download the update template or use the exported CSV file format"
msgstr "下载更新的模板或使用导出的csv格式"
#: templates/_update_modal.html:13
msgid "Download the update template"
msgstr "下载更新模版"
#: templates/captcha/image.html:3
msgid "Play CAPTCHA as audio file"
msgstr "语言播放验证码"
......@@ -4206,18 +4639,18 @@ msgid ""
"You should use your ssh client tools connect terminal: {} <br /> <br />{}"
msgstr "你可以使用ssh客户端工具连接终端"
#: users/api/user.py:69 users/api/user.py:80 users/api/user.py:106
#: users/api/user.py:77 users/api/user.py:88 users/api/user.py:114
msgid "You do not have permission."
msgstr "你没有权限"
#: users/api/user.py:210
#: users/api/user.py:218
msgid "Could not reset self otp, use profile reset instead"
msgstr "不能再该页面重置MFA, 请去个人信息页面重置"
#: 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
#: users/templates/users/user_list.html:37
#: users/templates/users/user_profile.html:55
msgid "Role"
msgstr "角色"
......@@ -4242,7 +4675,7 @@ msgstr "添加到用户组"
msgid "Public key should not be the same as your old one."
msgstr "不能和原来的密钥相同"
#: users/forms.py:89 users/forms.py:219 users/serializers/v1.py:38
#: users/forms.py:89 users/forms.py:219 users/serializers/v1.py:53
msgid "Not a valid ssh public key"
msgstr "ssh密钥不合法"
......@@ -4339,7 +4772,7 @@ msgid "Wechat"
msgstr "微信"
#: users/models/user.py:106 users/templates/users/user_detail.html:103
#: users/templates/users/user_list.html:27
#: users/templates/users/user_list.html:39
#: users/templates/users/user_profile.html:100
msgid "Source"
msgstr "用户来源"
......@@ -4357,6 +4790,34 @@ msgstr "用户认证源来自 {}, 请去相应系统修改密码"
msgid "Administrator is the super user of system"
msgstr "Administrator是初始的超级管理员"
#: users/serializers/v1.py:17
msgid "Groups name"
msgstr "用户组名"
#: users/serializers/v1.py:20
msgid "Source name"
msgstr "用户来源名"
#: users/serializers/v1.py:23
msgid "Is first login"
msgstr "首次登录"
#: users/serializers/v1.py:25
msgid "Role name"
msgstr "角色名"
#: users/serializers/v1.py:26
msgid "Is valid"
msgstr "账户是否有效"
#: users/serializers/v1.py:27
msgid "Is expired"
msgstr " 是否过期"
#: users/serializers/v1.py:28
msgid "Avatar url"
msgstr "头像路径"
#: users/serializers_v2/user.py:36
msgid "name not unique"
msgstr "名称重复"
......@@ -4393,21 +4854,22 @@ msgstr "资产数量"
msgid "Security and Role"
msgstr "角色安全"
#: users/templates/users/_user_import_modal.html:4
msgid "Import user"
msgstr "导入"
#: users/templates/users/_user_groups_import_modal.html:4
msgid "Import user groups"
msgstr "导入用户组"
#: users/templates/users/_user_import_modal.html:6
msgid "Download template or use export csv format"
msgstr "下载模板或使用导出的csv格式"
#: users/templates/users/_user_groups_update_modal.html:4
msgid "Update user groups"
msgstr "更新用户组"
#: users/templates/users/_user_import_modal.html:14
msgid "Users csv file"
msgstr "用户csv文件"
#: users/templates/users/_user_import_modal.html:4
msgid "Import users"
msgstr "导入用户"
#: users/templates/users/_user_import_modal.html:16
msgid "If set id, will use this id update user existed"
msgstr "如果设置了id,则会使用该行信息更新该id的用户"
#: users/templates/users/_user_update_modal.html:4
#: users/templates/users/user_update.html:4 users/views/user.py:123
msgid "Update user"
msgstr "更新用户"
#: users/templates/users/_user_update_pk_modal.html:4
msgid "Update User SSH Public Key"
......@@ -4535,7 +4997,7 @@ msgid "Very strong"
msgstr "很强"
#: users/templates/users/user_create.html:4
#: users/templates/users/user_list.html:16 users/views/user.py:83
#: users/templates/users/user_list.html:28 users/views/user.py:83
msgid "Create user"
msgstr "创建用户"
......@@ -4656,49 +5118,49 @@ msgstr "用户组详情"
msgid "Add user"
msgstr "添加用户"
#: users/templates/users/user_group_list.html:5 users/views/group.py:44
#: users/templates/users/user_group_list.html:28 users/views/group.py:44
msgid "Create user group"
msgstr "创建用户组"
#: users/templates/users/user_group_list.html:85
#: users/templates/users/user_group_list.html:109
msgid "This will delete the selected groups !!!"
msgstr "删除选择组"
#: users/templates/users/user_group_list.html:94
#: users/templates/users/user_group_list.html:118
msgid "UserGroups Deleted."
msgstr "用户组删除"
#: users/templates/users/user_group_list.html:95
#: users/templates/users/user_group_list.html:100
#: users/templates/users/user_group_list.html:119
#: users/templates/users/user_group_list.html:124
msgid "UserGroups Delete"
msgstr "用户组删除"
#: users/templates/users/user_group_list.html:99
#: users/templates/users/user_group_list.html:123
msgid "UserGroup Deleting failed."
msgstr "用户组删除失败"
#: users/templates/users/user_list.html:210
#: users/templates/users/user_list.html:255
msgid "This will delete the selected users !!!"
msgstr "删除选中用户 !!!"
#: users/templates/users/user_list.html:219
#: users/templates/users/user_list.html:264
msgid "User Deleted."
msgstr "已被删除"
#: users/templates/users/user_list.html:220
#: users/templates/users/user_list.html:225
#: users/templates/users/user_list.html:265
#: users/templates/users/user_list.html:270
msgid "User Delete"
msgstr "删除"
#: users/templates/users/user_list.html:224
#: users/templates/users/user_list.html:269
msgid "User Deleting failed."
msgstr "用户删除失败"
#: users/templates/users/user_list.html:260
#: users/templates/users/user_list.html:305
msgid "User is expired"
msgstr "用户已失效"
#: users/templates/users/user_list.html:263
#: users/templates/users/user_list.html:308
msgid "User is inactive"
msgstr "用户已禁用"
......@@ -4798,10 +5260,6 @@ msgid ""
"corresponding private key."
msgstr "新的公钥已设置成功,请下载对应的私钥"
#: users/templates/users/user_update.html:4 users/views/user.py:123
msgid "Update user"
msgstr "更新用户"
#: users/utils.py:38
msgid "Create account successfully"
msgstr "创建账户成功"
......@@ -5728,6 +6186,25 @@ msgstr "创建组织"
msgid "Update org"
msgstr "更新组织"
#~ msgid "Template"
#~ msgstr "模板"
#~ msgid "Download"
#~ msgstr "下载"
#~ msgid "Asset csv file"
#~ msgstr "资产csv文件"
#~ msgid "If set id, will use this id update asset existed"
#~ msgstr "如果设置了id,则会使用该行信息更新该id的资产"
#~ msgid "Users csv file"
#~ msgstr "用户csv文件"
#~ msgid "If set id, will use this id update user existed"
#~ msgstr "如果设置了id,则会使用该行信息更新该id的用户"
#~ msgid "MFA Confirm"
#~ msgstr "确认"
......@@ -5747,6 +6224,7 @@ msgstr "更新组织"
#~ msgid "Restore default successfully!"
#~ msgstr "恢复默认成功!"
#~ msgid "Beijing Duizhan Tech, Inc."
#~ msgstr "北京堆栈科技有限公司"
......@@ -5780,6 +6258,24 @@ msgstr "更新组织"
#~ msgid "Invalid private key"
#~ msgstr "ssh密钥不合法"
#, fuzzy
#~| msgid "CPU count"
#~ msgid "Cpu count"
#~ msgstr "CPU数量"
#~ msgid "Login Jumpserver"
#~ msgstr "登录 Jumpserver"
#, fuzzy
#~| msgid "Delete succeed"
#~ msgid "Delete success!"
#~ msgstr "删除成功"
#, fuzzy
#~| msgid "Username does not exist"
#~ msgid "This license does not exist!"
#~ msgstr "用户名不存在"
#~ msgid "Valid"
#~ msgstr "账户状态"
......
......@@ -8,9 +8,12 @@ from django.shortcuts import redirect, get_object_or_404
from django.forms import ModelForm
from django.http.response import HttpResponseForbidden
from django.core.exceptions import ValidationError
from rest_framework import serializers
from common.utils import get_logger
from .utils import current_org, set_current_org, set_to_root_org
from .utils import (
current_org, set_current_org, set_to_root_org, get_current_org_id
)
from .models import Organization
logger = get_logger(__file__)
......@@ -18,7 +21,8 @@ tl = Local()
__all__ = [
'OrgManager', 'OrgViewGenericMixin', 'OrgModelMixin', 'OrgModelForm',
'RootOrgViewMixin', 'OrgMembershipSerializerMixin', 'OrgMembershipModelViewSetMixin'
'RootOrgViewMixin', 'OrgMembershipSerializerMixin',
'OrgMembershipModelViewSetMixin', 'OrgResourceSerializerMixin',
]
......@@ -202,3 +206,11 @@ class OrgMembershipModelViewSetMixin:
def get_queryset(self):
queryset = self.membership_class.objects.filter(organization=self.org)
return queryset
class OrgResourceSerializerMixin(serializers.Serializer):
"""
通过API批量操作资源时, 自动给每个资源添加所需属性org_id的值为current_org_id
(同时为serializer.is_valid()对Model的unique_together校验做准备)
"""
org_id = serializers.HiddenField(default=get_current_org_id)
......@@ -38,4 +38,10 @@ def get_current_org():
return _find('current_org')
def get_current_org_id():
org = get_current_org()
org_id = str(org.id) if org.is_real() else ''
return org_id
current_org = LocalProxy(partial(_find, 'current_org'))
......@@ -954,9 +954,92 @@ function rootNodeAddDom(ztree, callback) {
})
}
function APIExportData(props) {
props = props || {};
$.ajax({
url: '/api/common/v1/resources/cache/',
type: props.method || "POST",
data: props.body,
contentType: props.content_type || "application/json; charset=utf-8",
dataType: props.data_type || "json",
success: function (data) {
var export_url = props.success_url;
var params = props.params || {};
params['format'] = props.format;
params['spm'] = data.spm;
for (var k in params){
export_url = setUrlParam(export_url, k, params[k])
}
window.open(export_url);
},
error: function () {
toastr.error('Export failed');
}
})
}
function APIImportData(props){
props = props || {};
$.ajax({
url: props.url,
type: props.method || "POST",
processData: false,
data: props.body,
contentType: props.content_type || 'text/csv',
success: function (data) {
if(props.method === 'POST'){
$('#created_failed').html('');
$('#created_failed_detail').html('');
$('#success_created').html("Import Success");
$('#success_created_detail').html("Count" + ": " + data.length);
}else{
$('#updated_failed').html('');
$('#updated_failed_detail').html('');
$('#success_updated').html("Update Success");
$('#success_updated_detail').html("Count" + ": " + data.length);
}
props.data_table.ajax.reload()
},
error: function (error) {
var data = error.responseJSON;
if (data instanceof Array){
var html = '';
var li = '';
var err = '';
$.each(data, function (index, item){
err = '';
for (var prop in item) {
err += prop + ": " + item[prop][0] + " "
}
if (err) {
li = "<li>" + "Line " + (++index) + ". " + err + "</li>";
html += li
}
});
html = "<ul>" + html + "</ul>"
}
else {
html = error.responseText
}
if(props.method === 'POST'){
$('#success_created').html('');
$('#success_created_detail').html('');
$('#created_failed').html("Import failed");
$('#created_failed_detail').html(html);
}else{
$('#success_updated').html('');
$('#success_updated_detail').html('');
$('#updated_failed').html("Update failed");
$('#updated_failed_detail').html(html);
}
}
})
}
function htmlEscape ( d ) {
return typeof d === 'string' ?
d.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;') :
d;
}
\ No newline at end of file
}
{% extends '_modal.html' %}
{% load i18n %}
{% block modal_id %}import_modal{% endblock %}
{% block modal_confirm_id %}btn_import_confirm{% endblock %}
{% block modal_body %}
<form method="post" id="fm_import">
{% csrf_token %}
<div class="form-group">
<label class="control-label">{% trans "Download the imported template or use the exported CSV file format" %}</label>
<a href="{% block import_modal_download_template_url %}{% endblock %}?format=csv&template=import" style="display: block">{% trans 'Download the import template' %}</a>
</div>
<div class="form-group">
<label class="control-label" for="id_file">{% trans "Select the CSV file to import" %}</label>
<input id="id_file" type="file" name="file" />
</div>
</form>
<div>
<p class="text-success" id="success_created"></p>
<p id="success_created_detail"></p>
<p class="text-danger" id="created_failed"></p>
<p id="created_failed_detail"></p>
</div>
{% endblock %}
......@@ -8,7 +8,7 @@
<div class="modal-dialog {% block modal_class %}{% endblock %}">
<div class="modal-content animated fadeIn">
<div class="modal-header">
<button data-dismiss="modal" class="close close_btn1" type="button"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
<button data-dismiss="modal" id="close_button1" class="close close_btn1 " type="button"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
<h4 class="modal-title">{% block modal_title %}{% endblock %}</h4>
<small>{% block modal_comment %}{% endblock %}</small>
</div>
......@@ -19,10 +19,32 @@
</div>
<div class="modal-footer">
{% block modal_button %}
<button data-dismiss="modal" class="btn btn-white close_btn2" type="button">{% trans "Close" %}</button>
<button id="close_button2" data-dismiss="modal" class="btn btn-white close_btn2" type="button">{% trans "Close" %}</button>
<button class="btn btn-primary" type="button" id="{% block modal_confirm_id %}{% endblock %}">{% trans 'Confirm' %}</button>
{% endblock %}
</div>
</div>
</div>
</div>
<script>
$(document).ready(function(){
})
.on('click', '#close_button1', function () {
SetMessageLabelEmpty()
})
.on('click', '#close_button2', function () {
SetMessageLabelEmpty()
});
function SetMessageLabelEmpty() {
$('#success_created').html('');
$('#success_created_detail').html('');
$('#created_failed').html('');
$('#created_failed_detail').html('');
$('#success_updated').html('');
$('#success_updated_detail').html('');
$('#updated_failed').html('');
$('#updated_failed_detail').html('');
}
</script>
{% extends '_modal.html' %}
{% load i18n %}
{% block modal_id %}update_modal{% endblock %}
{% block modal_confirm_id %}btn_update_confirm{% endblock %}
{% block modal_body %}
<form method="post" id="fm_import">
{% csrf_token %}
<div class="form-group">
<label class="control-label">{% trans "Download the update template or use the exported CSV file format" %}</label>
<a id="download_update_template" style="display: block">{% trans 'Download the update template' %}</a>
</div>
<div class="form-group">
<label class="control-label" for="update_file">{% trans "Select the CSV file to import" %}</label>
<input id="update_file" type="file" name="file" />
</div>
</form>
<div>
<p class="text-warning" id="success_updated"></p>
<p id="success_updated_detail"></p>
<p class="text-danger" id="updated_failed"></p>
<p id="updated_failed_detail"></p>
</div>
{% endblock %}
......@@ -9,13 +9,13 @@ from ..serializers import UserGroupSerializer, \
UserGroupUpdateMemberSerializer
from ..models import UserGroup
from common.permissions import IsOrgAdmin
from common.mixins import IDInFilterMixin
from common.mixins import IDInCacheFilterMixin
__all__ = ['UserGroupViewSet', 'UserGroupUpdateUserApi']
class UserGroupViewSet(IDInFilterMixin, BulkModelViewSet):
class UserGroupViewSet(IDInCacheFilterMixin, BulkModelViewSet):
filter_fields = ("name",)
search_fields = filter_fields
queryset = UserGroup.objects.all()
......
......@@ -15,7 +15,7 @@ from rest_framework.pagination import LimitOffsetPagination
from common.permissions import (
IsOrgAdmin, IsCurrentUserOrReadOnly, IsOrgAdminOrAppUser
)
from common.mixins import IDInFilterMixin
from common.mixins import IDInCacheFilterMixin
from common.utils import get_logger
from orgs.utils import current_org
from ..serializers import UserSerializer, UserPKUpdateSerializer, \
......@@ -32,7 +32,7 @@ __all__ = [
]
class UserViewSet(IDInFilterMixin, BulkModelViewSet):
class UserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
filter_fields = ('username', 'email', 'name', 'id')
search_fields = filter_fields
queryset = User.objects.exclude(role=User.ROLE_APP)
......@@ -40,9 +40,15 @@ class UserViewSet(IDInFilterMixin, BulkModelViewSet):
permission_classes = (IsOrgAdmin,)
pagination_class = LimitOffsetPagination
def send_created_signal(self, users):
if not isinstance(users, list):
users = [users]
for user in users:
post_user_create.send(self.__class__, user=user)
def perform_create(self, serializer):
user = serializer.save()
post_user_create.send(self.__class__, user=user)
users = serializer.save()
self.send_created_signal(users)
def get_queryset(self):
queryset = current_org.get_org_users()
......@@ -213,4 +219,4 @@ class UserResetOTPApi(generics.RetrieveAPIView):
user.otp_secret_key = ''
user.save()
logout(request)
return Response({"msg": "success"})
return Response({"msg": "success"})
\ No newline at end of file
......@@ -19,12 +19,21 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
list_serializer_class = AdaptedBulkListSerializer
fields = [
'id', 'name', 'username', 'email', 'groups', 'groups_display',
'role', 'role_display', 'avatar_url', 'wechat', 'phone',
'otp_level', 'comment', 'source', 'source_display',
'is_valid', 'is_expired', 'is_active',
'created_by', 'is_first_login',
'date_password_last_updated', 'date_expired',
'role', 'role_display', 'wechat', 'phone', 'otp_level',
'comment', 'source', 'source_display', 'is_valid', 'is_expired',
'is_active', 'created_by', 'is_first_login',
'date_password_last_updated', 'date_expired', 'avatar_url',
]
extra_kwargs = {
'groups_display': {'label': _('Groups name')},
'source_display': {'label': _('Source name')},
'is_first_login': {'label': _('Is first login'), 'read_only': True},
'role_display': {'label': _('Role name')},
'is_valid': {'label': _('Is valid')},
'is_expired': {'label': _('Is expired')},
'avatar_url': {'label': _('Avatar url')},
'created_by': {'read_only': True}, 'source': {'read_only': True}
}
class UserPKUpdateSerializer(serializers.ModelSerializer):
......@@ -48,17 +57,20 @@ class UserUpdateGroupSerializer(serializers.ModelSerializer):
class UserGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer):
users = serializers.SerializerMethodField()
users = serializers.PrimaryKeyRelatedField(
required=False, many=True, queryset=User.objects.all(), label=_('User')
)
class Meta:
model = UserGroup
list_serializer_class = AdaptedBulkListSerializer
fields = '__all__'
read_only_fields = ['created_by']
@staticmethod
def get_users(obj):
return [user.name for user in obj.users.all()]
fields = [
'id', 'org_id', 'name', 'users', 'comment', 'date_created',
'created_by',
]
extra_kwargs = {
'created_by': {'label': _('Created by'), 'read_only': True}
}
class UserGroupUpdateMemberSerializer(serializers.ModelSerializer):
......
......@@ -28,4 +28,3 @@ def on_user_create(sender, user=None, **kwargs):
logger.info(" - Sending welcome mail ...".format(user.name))
if user.email:
send_user_created_mail(user)
{% extends '_import_modal.html' %}
{% load i18n %}
{% block modal_title%}{% trans "Import user groups" %}{% endblock %}
{% block import_modal_download_template_url %}{% url "api-users:user-group-list" %}{% endblock %}
\ No newline at end of file
{% extends '_update_modal.html' %}
{% load i18n %}
{% block modal_title%}{% trans "Update user group" %}{% endblock %}
\ No newline at end of file
{% extends '_modal.html' %}
{% extends '_import_modal.html' %}
{% load i18n %}
{% block modal_id %}user_import_modal{% endblock %}
{% block modal_title%}{% trans "Import user" %}{% endblock %}
{% block modal_body %}
<p class="text-success">{% trans "Download template or use export csv format" %}</p>
<form method="post" action="{% url 'users:user-import' %}" id="fm_user_import" enctype="multipart/form-data">
{% csrf_token %}
<div class="form-group">
<label class="control-label" for="id_users">{% trans "Template" %}</label>
<a href="{% url 'users:user-export' %}" style="display: block">{% trans 'Download' %}</a>
</div>
<div class="form-group">
<label class="control-label" for="id_users">{% trans "Users csv file" %}</label>
<input id="id_users" type="file" name="file" />
<span class="help-block red-fonts">{% trans 'If set id, will use this id update user existed' %}</span>
</div>
</form>
<p>
<p class="text-success" id="id_created"></p>
<p id="id_created_detail"></p>
<p class="text-warning" id="id_updated"></p>
<p id="id_updated_detail"></p>
<p class="text-danger" id="id_failed"></p>
<p id="id_failed_detail"></p>
</p>
{% endblock %}
{% block modal_confirm_id %}btn_user_import{% endblock %}
{% block modal_title%}{% trans "Import users" %}{% endblock %}
{% block import_modal_download_template_url %}{% url "api-users:user-list" %}{% endblock %}
{% extends '_update_modal.html' %}
{% load i18n %}
{% block modal_title%}{% trans "Update user" %}{% endblock %}
\ No newline at end of file
{% extends '_base_list.html' %}
{% load i18n static %}
{% block table_search %}{% endblock %}
{% block table_search %}
<div class="" style="float: right">
<div class=" btn-group">
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
<ul class="dropdown-menu">
<li>
<a class=" btn_export" tabindex="0">
<span>{% trans "Export" %}</span>
</a>
</li>
<li>
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
<span>{% trans "Import" %}</span>
</a>
</li>
<li>
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
<span>{% trans "Update" %}</span>
</a>
</li>
</ul>
</div>
</div>
{% endblock %}
{% block table_container %}
<div class="pull-left m-r-5"><a href="{% url 'users:user-group-create' %}" class="btn btn-sm btn-primary ">{% trans "Create user group" %}</a></div>
<table class="table table-striped table-bordered table-hover " id="group_list_table" >
......@@ -16,7 +39,8 @@
</tr>
</thead>
</table>
{% include "users/_user_groups_import_modal.html" %}
{% include "users/_user_groups_update_modal.html" %}
{% endblock %}
{% block content_bottom_left %}{% endblock %}
......@@ -111,6 +135,78 @@ $(document).ready(function() {
default:
break;
}
}).on('click', '.btn_export', function(){
var data_table = $('#group_list_table').DataTable();
var rows = data_table.rows('.selected').data();
var groups = [];
$.each(rows, function (index, obj) {
groups.push(obj.id)
});
var data = {
'resources': groups
};
var search = $("input[type='search']").val();
var props = {
method: "POST",
body: JSON.stringify(data),
success_url: "{% url 'api-users:user-group-list' %}",
format: "csv",
params: {
search: search
}
};
APIExportData(props);
}).on('click', '#btn_import_confirm',function () {
var url = "{% url 'api-users:user-group-list' %}";
var file = document.getElementById('id_file').files[0];
if(!file){
toastr.error("{% trans "Please select file" %}");
return
}
var data_table = $('#group_list_table').DataTable();
APIImportData({
url: url,
method: "POST",
body: file,
data_table: data_table
});
})
.on('click', '#download_update_template', function(){
var data_table = $('#group_list_table').DataTable();
var rows = data_table.rows('.selected').data();
var groups = [];
$.each(rows, function (index, obj) {
groups.push(obj.id)
});
var data = {
'resources': groups
};
var search = $("input[type='search']").val();
var props = {
method: "POST",
body: JSON.stringify(data),
success_url: "{% url 'api-users:user-group-list' %}?format=csv&template=update",
format: "csv",
params: {
search: search
}
};
APIExportData(props);
}).on('click', '#btn_update_confirm',function () {
var url = "{% url 'api-users:user-group-list' %}";
var file = document.getElementById('update_file').files[0];
if(!file){
toastr.error("{% trans "Please select file" %}");
return
}
var data_table = $('#group_list_table').DataTable();
APIImportData({
url: url,
method: "PUT",
body: file,
data_table: data_table
});
})
</script>
{% endblock %}
{% extends '_base_list.html' %}
{% load i18n static %}
{% block table_search %}
<div class="html5buttons">
<div class="dt-buttons btn-group">
<a class="btn btn-default btn_import" data-toggle="modal" data-target="#user_import_modal" tabindex="0">
<span>{% trans "Import" %}</span>
</a>
<a class="btn btn-default btn_export" tabindex="0">
<span>{% trans "Export" %}</span>
</a>
</div>
</div>
<div class="" style="float: right">
<div class=" btn-group">
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
<ul class="dropdown-menu">
<li>
<a class=" btn_export" tabindex="0">
<span>{% trans "Export" %}</span>
</a>
</li>
<li>
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
<span>{% trans "Import" %}</span>
</a>
</li>
<li>
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
<span>{% trans "Update" %}</span>
</a>
</li>
</ul>
</div>
</div>
{% endblock %}
{% block table_container %}
<div class="uc pull-left m-r-5"><a href="{% url "users:user-create" %}" class="btn btn-sm btn-primary"> {% trans "Create user" %} </a></div>
......@@ -48,6 +60,7 @@
</div>
</div>
{% include "users/_user_import_modal.html" %}
{% include "users/_user_update_modal.html" %}
{% endblock %}
{% block content_bottom_left %}{% endblock %}
{% block custom_foot_js %}
......@@ -113,6 +126,7 @@ function initTable() {
return table
}
$(document).ready(function(){
var table = initTable();
var fields = $('#fm_user_bulk_update .form-group');
......@@ -120,87 +134,127 @@ $(document).ready(function(){
console.log(value)
});
$('.btn_export').click(function () {
var users = [];
var rows = table.rows('.selected').data();
if(rows.length===0){
rows = table.rows().data();
}
var users = [];
$.each(rows, function (index, obj) {
users.push(obj.id)
});
$.ajax({
url: "{% url 'users:user-export' %}",
method: 'POST',
data: JSON.stringify({users_id: users}),
dataType: "json",
success: function (data, textStatus) {
window.open(data.redirect)
},
error: function () {
toastr.error('Export failed');
var data = {
'resources': users
};
var search = $("input[type='search']").val();
var props = {
method: "POST",
body: JSON.stringify(data),
success_url: "{% url 'api-users:user-list' %}",
format: 'csv',
params: {
search: search
}
})
};
APIExportData(props);
});
$('#btn_user_import').click(function() {
var $form = $('#fm_user_import');
$form.find('.help-block').remove();
function success (data) {
if (data.valid === false) {
$('<span />', {class: 'help-block text-danger'}).html(data.msg).insertAfter($('#id_users'));
} else {
$('#id_created').html(data.created_info);
$('#id_created_detail').html(data.created.join(', '));
$('#id_updated').html(data.updated_info);
$('#id_updated_detail').html(data.updated.join(', '));
$('#id_failed').html(data.failed_info);
$('#id_failed_detail').html(data.failed.join(', '));
var $data_table = $('#user_list_table').DataTable();
$data_table.ajax.reload();
$('#btn_import_confirm').click(function() {
var url = "{% url 'api-users:user-list' %}";
var file = document.getElementById('id_file').files[0];
if(!file){
toastr.error("{% trans "Please select file" %}");
return
}
var data_table = $('#user_list_table').DataTable();
APIImportData({
url: url,
method: "POST",
body: file,
data_table: data_table
});
});
$('#download_update_template').click(function () {
var rows = table.rows('.selected').data();
var users = [];
$.each(rows, function (index, obj) {
users.push(obj.id)
});
var data = {
'resources': users
};
var search = $("input[type='search']").val();
var props = {
method: "POST",
body: JSON.stringify(data),
success_url: "{% url 'api-users:user-list' %}?format=csv&template=update",
format: 'csv',
params: {
search: search
}
};
APIExportData(props);
});
$('#btn_update_confirm').click(function() {
var url = "{% url 'api-users:user-list' %}";
var file = document.getElementById('update_file').files[0];
if(!file){
toastr.error("{% trans "Please select file" %}");
return
}
$form.ajaxSubmit({success: success});
})
var data_table = $('#user_list_table').DataTable();
APIImportData({
url: url,
method: "PUT",
body: file,
data_table: data_table
});
});
}).on('click', '#btn_bulk_update', function(){
var action = $('#slct_bulk_update').val();
var $data_table = $('#user_list_table').DataTable();
var id_list = [];
var plain_id_list = [];
$data_table.rows({selected: true}).every(function(){
id_list.push({pk: this.data().id});
plain_id_list.push(this.data().id);
id_list.push(this.data().id);
});
if (id_list === []) {
if (id_list.length === 0) {
return false;
}
var the_url = "{% url 'api-users:user-list' %}";
var data = {
'resources': id_list
};
function refreshTag() {
$('#user_list_table').DataTable().ajax.reload()
}
function doDeactive() {
var body = $.each(id_list, function(index, user_object) {
user_object['is_active'] = false;
var data = [];
$.each(id_list, function(index, object_id) {
var obj = {"pk": object_id, "is_active": false};
data.push(obj);
});
function success() {
location.reload();
setTimeout( function () {
window.location.reload();}, 300);
}
APIUpdateAttr({
url: the_url,
method: 'PATCH',
body: JSON.stringify(body),
body: JSON.stringify(data),
success: success
});
location.reload();
}
function doActive() {
var body = $.each(id_list, function(index, user_object) {
user_object['is_active'] = true;
function doActive() {
var data = [];
$.each(id_list, function(index, object_id) {
var obj = {"pk": object_id, "is_active": true};
data.push(obj);
});
function success() {
location.reload();
setTimeout( function () {
window.location.reload();}, 300);
}
APIUpdateAttr({
url: the_url,
method: 'PATCH',
body: JSON.stringify(body),
body: JSON.stringify(data),
success: success
});
}
......@@ -214,26 +268,49 @@ $(document).ready(function(){
confirmButtonColor: "#DD6B55",
confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: false
}, function() {
var success = function() {
},function () {
function success(data) {
url = setUrlParam(the_url, 'spm', data.spm);
APIUpdateAttr({
url:url,
method:'DELETE',
success:refreshTag,
flash_message:false,
});
var msg = "{% trans 'User Deleted.' %}";
swal("{% trans 'User Delete' %}", msg, "success");
$('#user_list_table').DataTable().ajax.reload();
};
var fail = function() {
}
function fail() {
var msg = "{% trans 'User Deleting failed.' %}";
swal("{% trans 'User Delete' %}", msg, "error");
};
var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list);
APIUpdateAttr({url: url_delete, method: 'DELETE', success: success, error: fail});
jumpserver.checked = false;
});
}
APIUpdateAttr({
url: "{% url 'api-common:resources-cache' %}",
method:'POST',
body:JSON.stringify(data),
success:success,
error:fail
})
})
}
function doUpdate() {
var users_id = plain_id_list.join(',');
var url = "{% url 'users:user-bulk-update' %}?users_id=" + users_id;
location.href = url
}
function fail(data) {
toastr.error(JSON.parse(data))
}
function success(data) {
var url = "{% url 'users:user-bulk-update' %}";
location.href= setUrlParam(url, 'spm', data.spm);
}
APIUpdateAttr({
url: "{% url 'api-common:resources-cache' %}",
method:'POST',
body:JSON.stringify(data),
flash_message:false,
success:success,
error:fail
})
}
switch(action) {
case 'deactive':
doDeactive();
......
......@@ -31,7 +31,9 @@ from django.views.generic.detail import DetailView
from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth import logout as auth_logout
from common.const import create_success_msg, update_success_msg
from common.const import (
create_success_msg, update_success_msg, KEY_CACHE_RESOURCES_ID
)
from common.mixins import JSONResponseMixin
from common.utils import get_logger, get_object_or_none, is_uuid, ssh_key_gen
from common.permissions import AdminUserRequiredMixin
......@@ -156,15 +158,12 @@ class UserBulkUpdateView(AdminUserRequiredMixin, TemplateView):
id_list = None
def get(self, request, *args, **kwargs):
users_id = self.request.GET.get('users_id', '')
self.id_list = [i for i in users_id.split(',')]
spm = request.GET.get('spm', '')
users_id = cache.get(KEY_CACHE_RESOURCES_ID.format(spm))
if kwargs.get('form'):
self.form = kwargs['form']
elif users_id:
self.form = self.form_class(
initial={'users': self.id_list}
)
self.form = self.form_class(initial={'users': users_id})
else:
self.form = self.form_class()
return super().get(request, *args, **kwargs)
......
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