diff --git a/apps/assets/api/admin_user.py b/apps/assets/api/admin_user.py index f2229022f..e84d9731a 100644 --- a/apps/assets/api/admin_user.py +++ b/apps/assets/api/admin_user.py @@ -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 """ diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index a734806fb..9e21c81be 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -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 """ diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py index 9805872e7..f6398e974 100644 --- a/apps/assets/api/system_user.py +++ b/apps/assets/api/system_user.py @@ -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 """ diff --git a/apps/assets/serializers/admin_user.py b/apps/assets/serializers/admin_user.py index e44679995..66f25db87 100644 --- a/apps/assets/serializers/admin_user.py +++ b/apps/assets/serializers/admin_user.py @@ -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) diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset.py index c0f435adc..3e1cf39bb 100644 --- a/apps/assets/serializers/asset.py +++ b/apps/assets/serializers/asset.py @@ -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): diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index c737f8cbe..88ae6eb38 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -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) diff --git a/apps/assets/templates/assets/_admin_user_import_modal.html b/apps/assets/templates/assets/_admin_user_import_modal.html new file mode 100644 index 000000000..a4afc1a14 --- /dev/null +++ b/apps/assets/templates/assets/_admin_user_import_modal.html @@ -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 %} diff --git a/apps/assets/templates/assets/_admin_user_update_modal.html b/apps/assets/templates/assets/_admin_user_update_modal.html new file mode 100644 index 000000000..9af051dd2 --- /dev/null +++ b/apps/assets/templates/assets/_admin_user_update_modal.html @@ -0,0 +1,4 @@ +{% extends '_update_modal.html' %} +{% load i18n %} + +{% block modal_title%}{% trans "Update admin user" %}{% endblock %} \ No newline at end of file diff --git a/apps/assets/templates/assets/_asset_import_modal.html b/apps/assets/templates/assets/_asset_import_modal.html index ca7729e05..2460cb053 100644 --- a/apps/assets/templates/assets/_asset_import_modal.html +++ b/apps/assets/templates/assets/_asset_import_modal.html @@ -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 %} -
- {% csrf_token %} -
- - {% trans 'Download' %} -
-
- - - - {% trans 'If set id, will use this id update asset existed' %} - -
-
-

-

-

-

-

-

-

-

-{% 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 %} diff --git a/apps/assets/templates/assets/_asset_update_modal.html b/apps/assets/templates/assets/_asset_update_modal.html new file mode 100644 index 000000000..68b2ff8db --- /dev/null +++ b/apps/assets/templates/assets/_asset_update_modal.html @@ -0,0 +1,4 @@ +{% extends '_update_modal.html' %} +{% load i18n %} + +{% block modal_title%}{% trans "Update assets" %}{% endblock %} diff --git a/apps/assets/templates/assets/_system_user_import_modal.html b/apps/assets/templates/assets/_system_user_import_modal.html new file mode 100644 index 000000000..b8687d696 --- /dev/null +++ b/apps/assets/templates/assets/_system_user_import_modal.html @@ -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 %} diff --git a/apps/assets/templates/assets/_system_user_update_modal.html b/apps/assets/templates/assets/_system_user_update_modal.html new file mode 100644 index 000000000..9e2920e6a --- /dev/null +++ b/apps/assets/templates/assets/_system_user_update_modal.html @@ -0,0 +1,4 @@ +{% extends '_update_modal.html' %} +{% load i18n %} + +{% block modal_title%}{% trans "Update system user" %}{% endblock %} \ No newline at end of file diff --git a/apps/assets/templates/assets/admin_user_list.html b/apps/assets/templates/assets/admin_user_list.html index 605e89060..c182ba4c4 100644 --- a/apps/assets/templates/assets/admin_user_list.html +++ b/apps/assets/templates/assets/admin_user_list.html @@ -1,8 +1,5 @@ {% extends '_base_list.html' %} {% load i18n static %} -{% block table_search %} -{% endblock %} - {% block help_message %}
{# 管理用户是资产(被控服务器)上的root,或拥有 NOPASSWD: ALL sudo权限的用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。#} @@ -12,6 +9,30 @@ {% trans 'You can set any one for Windows or other hardware.' %}
{% endblock %} +{% block table_search %} +
+
+ + +
+
+{% endblock %} {% block table_container %}
@@ -36,6 +57,8 @@ + {% 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 + }); +}) {% endblock %} diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html index aa27de7a8..ea74e25d1 100644 --- a/apps/assets/templates/assets/asset_list.html +++ b/apps/assets/templates/assets/asset_list.html @@ -67,14 +67,26 @@
{% trans "Create asset" %}
-
-
- - {% trans "Import" %} - - - {% trans "Export" %} - +
+
@@ -140,7 +152,7 @@ {#
  • {% trans 'Refresh' %}
  • #}
    - +{% 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) { - $('', {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(); diff --git a/apps/assets/templates/assets/system_user_list.html b/apps/assets/templates/assets/system_user_list.html index b31039a46..16a0fd11c 100644 --- a/apps/assets/templates/assets/system_user_list.html +++ b/apps/assets/templates/assets/system_user_list.html @@ -14,6 +14,28 @@ {% endblock %} {% block table_search %} +
    + +
    {% endblock %} {% block table_container %} @@ -41,6 +63,8 @@ + {% include 'assets/_system_user_import_modal.html' %} + {% include 'assets/_system_user_update_modal.html' %} {% endblock %} {% block custom_foot_js %} {% endblock %} diff --git a/apps/assets/views/asset.py b/apps/assets/views/asset.py index 96bc451f0..653fa12c3 100644 --- a/apps/assets/views/asset.py +++ b/apps/assets/views/asset.py @@ -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: diff --git a/apps/common/api.py b/apps/common/api.py index 269d493d0..4f5f8da30 100644 --- a/apps/common/api.py +++ b/apps/common/api.py @@ -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}) diff --git a/apps/common/const.py b/apps/common/const.py index 018177d89..72d92da81 100644 --- a/apps/common/const.py +++ b/apps/common/const.py @@ -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_{}" diff --git a/apps/common/mixins.py b/apps/common/mixins.py index a5e9a58d3..8e4af26dd 100644 --- a/apps/common/mixins.py +++ b/apps/common/mixins.py @@ -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) diff --git a/apps/common/parsers/__init__.py b/apps/common/parsers/__init__.py new file mode 100644 index 000000000..671c86586 --- /dev/null +++ b/apps/common/parsers/__init__.py @@ -0,0 +1 @@ +from .csv import * \ No newline at end of file diff --git a/apps/common/parsers/csv.py b/apps/common/parsers/csv.py new file mode 100644 index 000000000..b536a0f73 --- /dev/null +++ b/apps/common/parsers/csv.py @@ -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!') diff --git a/apps/common/renders/__init__.py b/apps/common/renders/__init__.py new file mode 100644 index 000000000..671c86586 --- /dev/null +++ b/apps/common/renders/__init__.py @@ -0,0 +1 @@ +from .csv import * \ No newline at end of file diff --git a/apps/common/renders/csv.py b/apps/common/renders/csv.py new file mode 100644 index 000000000..cec857ddb --- /dev/null +++ b/apps/common/renders/csv.py @@ -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 diff --git a/apps/common/urls/__init__.py b/apps/common/urls/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/common/urls/api_urls.py b/apps/common/urls/api_urls.py new file mode 100644 index 000000000..01f164b00 --- /dev/null +++ b/apps/common/urls/api_urls.py @@ -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'), +] diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index a6ec9c92a..bdc71ae3c 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -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', diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index a2538b6c0..96c702acc 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -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')), ] diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index b8c64ffce..820dc398e 100644 Binary files a/apps/locale/zh/LC_MESSAGES/django.mo and b/apps/locale/zh/LC_MESSAGES/django.mo differ diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index b1f1774ef..97df573d9 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -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: {}

    {}" 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_groups_import_modal.html:4 +msgid "Import user groups" +msgstr "导入用户组" + +#: users/templates/users/_user_groups_update_modal.html:4 +msgid "Update user groups" +msgstr "更新用户组" + #: users/templates/users/_user_import_modal.html:4 -msgid "Import user" -msgstr "导入" +msgid "Import users" +msgstr "导入用户" -#: users/templates/users/_user_import_modal.html:6 -msgid "Download template or use export csv format" -msgstr "下载模板或使用导出的csv格式" - -#: users/templates/users/_user_import_modal.html:14 -msgid "Users csv file" -msgstr "用户csv文件" - -#: 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 "账户状态" diff --git a/apps/orgs/mixins.py b/apps/orgs/mixins.py index 5c4879204..431443ebe 100644 --- a/apps/orgs/mixins.py +++ b/apps/orgs/mixins.py @@ -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) diff --git a/apps/orgs/utils.py b/apps/orgs/utils.py index 7898e0343..808536984 100644 --- a/apps/orgs/utils.py +++ b/apps/orgs/utils.py @@ -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')) diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index b3a2bd35e..b452c353a 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -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 = "
  • " + "Line " + (++index) + ". " + err + "
  • "; + html += li + } + }); + html = "
      " + html + "
    " + } + 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, '"') : d; -} \ No newline at end of file +} diff --git a/apps/templates/_import_modal.html b/apps/templates/_import_modal.html new file mode 100644 index 000000000..01a1cdf72 --- /dev/null +++ b/apps/templates/_import_modal.html @@ -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 %} +
    + {% csrf_token %} +
    + + {% trans 'Download the import template' %} +
    + +
    + + +
    +
    + +
    +

    +

    +

    +

    +
    +{% endblock %} diff --git a/apps/templates/_modal.html b/apps/templates/_modal.html index 237e5618b..7b5f55de4 100644 --- a/apps/templates/_modal.html +++ b/apps/templates/_modal.html @@ -8,7 +8,7 @@
    + diff --git a/apps/templates/_update_modal.html b/apps/templates/_update_modal.html new file mode 100644 index 000000000..db2b14110 --- /dev/null +++ b/apps/templates/_update_modal.html @@ -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 %} +
    + {% csrf_token %} +
    + + {% trans 'Download the update template' %} +
    + +
    + + +
    +
    + +
    +

    +

    +

    +

    +
    +{% endblock %} diff --git a/apps/users/api/group.py b/apps/users/api/group.py index 8cf9fcb0e..fc9a84928 100644 --- a/apps/users/api/group.py +++ b/apps/users/api/group.py @@ -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() diff --git a/apps/users/api/user.py b/apps/users/api/user.py index 63c3dab12..c7668cb86 100644 --- a/apps/users/api/user.py +++ b/apps/users/api/user.py @@ -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 diff --git a/apps/users/serializers/v1.py b/apps/users/serializers/v1.py index b8c91417d..a0a13d4fa 100644 --- a/apps/users/serializers/v1.py +++ b/apps/users/serializers/v1.py @@ -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): diff --git a/apps/users/signals_handler.py b/apps/users/signals_handler.py index 1bc3ef430..4c6afc663 100644 --- a/apps/users/signals_handler.py +++ b/apps/users/signals_handler.py @@ -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) - diff --git a/apps/users/templates/users/_user_groups_import_modal.html b/apps/users/templates/users/_user_groups_import_modal.html new file mode 100644 index 000000000..63d057215 --- /dev/null +++ b/apps/users/templates/users/_user_groups_import_modal.html @@ -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 %} \ No newline at end of file diff --git a/apps/users/templates/users/_user_groups_update_modal.html b/apps/users/templates/users/_user_groups_update_modal.html new file mode 100644 index 000000000..a07c0f82c --- /dev/null +++ b/apps/users/templates/users/_user_groups_update_modal.html @@ -0,0 +1,4 @@ +{% extends '_update_modal.html' %} +{% load i18n %} + +{% block modal_title%}{% trans "Update user group" %}{% endblock %} \ No newline at end of file diff --git a/apps/users/templates/users/_user_import_modal.html b/apps/users/templates/users/_user_import_modal.html index 678023cfc..e53d67fa7 100644 --- a/apps/users/templates/users/_user_import_modal.html +++ b/apps/users/templates/users/_user_import_modal.html @@ -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 %} -

    {% trans "Download template or use export csv format" %}

    -
    - {% csrf_token %} -
    - - {% trans 'Download' %} -
    -
    - - - {% trans 'If set id, will use this id update user existed' %} -
    -
    -

    -

    -

    -

    -

    -

    -

    -

    -{% 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 %} diff --git a/apps/users/templates/users/_user_update_modal.html b/apps/users/templates/users/_user_update_modal.html new file mode 100644 index 000000000..9dfe60c96 --- /dev/null +++ b/apps/users/templates/users/_user_update_modal.html @@ -0,0 +1,4 @@ +{% extends '_update_modal.html' %} +{% load i18n %} + +{% block modal_title%}{% trans "Update user" %}{% endblock %} \ No newline at end of file diff --git a/apps/users/templates/users/user_group_list.html b/apps/users/templates/users/user_group_list.html index 6f6c6fc72..d8fc92e87 100644 --- a/apps/users/templates/users/user_group_list.html +++ b/apps/users/templates/users/user_group_list.html @@ -1,6 +1,29 @@ {% extends '_base_list.html' %} {% load i18n static %} -{% block table_search %}{% endblock %} +{% block table_search %} +
    + +
    +{% endblock %} {% block table_container %}
    {% trans "Create user group" %}
    @@ -16,7 +39,8 @@
    - +{% 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 + }); +}) + {% endblock %} diff --git a/apps/users/templates/users/user_list.html b/apps/users/templates/users/user_list.html index 15b5fb2f9..5d33a96aa 100644 --- a/apps/users/templates/users/user_list.html +++ b/apps/users/templates/users/user_list.html @@ -1,16 +1,28 @@ {% extends '_base_list.html' %} {% load i18n static %} {% block table_search %} -
    - -
    +
    + +
    {% endblock %} {% block table_container %}
    {% trans "Create user" %}
    @@ -48,6 +60,7 @@
    {% 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) { - $('', {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(); diff --git a/apps/users/views/user.py b/apps/users/views/user.py index 270a9a5c9..a7515d030 100644 --- a/apps/users/views/user.py +++ b/apps/users/views/user.py @@ -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)