mirror of https://github.com/jumpserver/jumpserver
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] 翻译pull/2691/head^2
parent
4942900886
commit
22f362aab3
|
@ -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
|
||||
"""
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# -*- 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):
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
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)
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
{% 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 %}
|
|
@ -0,0 +1,4 @@
|
|||
{% extends '_update_modal.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal_title%}{% trans "Update admin user" %}{% endblock %}
|
|
@ -1,29 +1,6 @@
|
|||
{% 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 %}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
{% extends '_update_modal.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal_title%}{% trans "Update assets" %}{% endblock %}
|
|
@ -0,0 +1,6 @@
|
|||
{% 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 %}
|
|
@ -0,0 +1,4 @@
|
|||
{% extends '_update_modal.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal_title%}{% trans "Update system user" %}{% endblock %}
|
|
@ -1,8 +1,5 @@
|
|||
{% 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
|
||||
}
|
||||
$form.ajaxSubmit({success: success});
|
||||
var url = "{% url 'api-assets:asset-list' %}";
|
||||
if (current_node_id){
|
||||
url = setUrlParam(url, 'node_id', current_node_id);
|
||||
}
|
||||
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 nodes = zTree.getSelectedNodes();
|
||||
if (!current_node_id) {
|
||||
return
|
||||
}
|
||||
|
||||
var data = {
|
||||
'assets': id_list
|
||||
};
|
||||
var data = {
|
||||
'assets': id_list
|
||||
};
|
||||
|
||||
var success = function () {
|
||||
asset_table.ajax.reload()
|
||||
};
|
||||
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
|
||||
})
|
||||
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)
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
from .csv import *
|
|
@ -0,0 +1,101 @@
|
|||
# ~*~ 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!')
|
|
@ -0,0 +1 @@
|
|||
from .csv import *
|
|
@ -0,0 +1,71 @@
|
|||
# ~*~ 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
|
|
@ -0,0 +1,13 @@
|
|||
# -*- 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')),
|
||||
]
|
||||
|
||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -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, '<').replace(/>/g, '>').replace(/"/g, '"') :
|
||||
d;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
{% 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>
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
{% 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"})
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
{% 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 %}
|
|
@ -0,0 +1,4 @@
|
|||
{% extends '_update_modal.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal_title%}{% trans "Update user group" %}{% endblock %}
|
|
@ -1,28 +1,6 @@
|
|||
{% 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 %}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
{% extends '_update_modal.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal_title%}{% trans "Update user" %}{% endblock %}
|
|
@ -1,6 +1,29 @@
|
|||
{% 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 %}
|
||||
|
|
|
@ -1,16 +1,28 @@
|
|||
{% 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
|
||||
}
|
||||
$form.ajaxSubmit({success: success});
|
||||
})
|
||||
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
|
||||
}
|
||||
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)
|
||||
|
|
Loading…
Reference in New Issue