diff --git a/apps/assets/api.py b/apps/assets/api.py index e5c5a44ff..f3bfedb5a 100644 --- a/apps/assets/api.py +++ b/apps/assets/api.py @@ -19,8 +19,9 @@ from rest_framework_bulk import BulkModelViewSet from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView from django.shortcuts import get_object_or_404 from django.db.models import Q +from rest_framework.pagination import LimitOffsetPagination -from common.mixins import IDInFilterMixin +from common.mixins import CustomFilterMixin from common.utils import get_logger from .hands import IsSuperUser, IsValidUser, IsSuperUserOrAppUser, \ get_user_granted_assets @@ -34,12 +35,16 @@ from .tasks import update_asset_hardware_info_manual, test_admin_user_connectabi logger = get_logger(__file__) -class AssetViewSet(IDInFilterMixin, BulkModelViewSet): +class AssetViewSet(CustomFilterMixin, BulkModelViewSet): """ API endpoint that allows Asset to be viewed or edited. """ + filter_fields = ("hostname", "ip") + search_fields = filter_fields + ordering_fields = ("hostname", "ip", "port", "cluster", "type", "env", "cpu_cores") queryset = Asset.objects.all() serializer_class = serializers.AssetSerializer + pagination_class = LimitOffsetPagination permission_classes = (IsSuperUserOrAppUser,) def get_queryset(self): @@ -78,7 +83,7 @@ class UserAssetListView(generics.ListAPIView): return queryset -class AssetGroupViewSet(IDInFilterMixin, BulkModelViewSet): +class AssetGroupViewSet(CustomFilterMixin, BulkModelViewSet): """ Asset group api set, for add,delete,update,list,retrieve resource """ @@ -112,7 +117,7 @@ class GroupAddAssetsApi(generics.UpdateAPIView): return Response({'error': serializer.errors}, status=400) -class ClusterViewSet(IDInFilterMixin, BulkModelViewSet): +class ClusterViewSet(CustomFilterMixin, BulkModelViewSet): """ Cluster api set, for add,delete,update,list,retrieve resource """ @@ -153,7 +158,7 @@ class ClusterAddAssetsApi(generics.UpdateAPIView): return Response({'error': serializer.errors}, status=400) -class AdminUserViewSet(IDInFilterMixin, BulkModelViewSet): +class AdminUserViewSet(CustomFilterMixin, BulkModelViewSet): """ Admin user api set, for add,delete,update,list,retrieve resource """ @@ -189,7 +194,7 @@ class SystemUserViewSet(BulkModelViewSet): permission_classes = (IsSuperUserOrAppUser,) -class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView): +class AssetListUpdateApi(CustomFilterMixin, ListBulkCreateUpdateDestroyAPIView): """ Asset bulk update api """ diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html index 2c562ffa5..8fc20057f 100644 --- a/apps/assets/templates/assets/asset_list.html +++ b/apps/assets/templates/assets/asset_list.html @@ -71,10 +71,21 @@ function initTable() { columnDefs: [ {targets: 1, createdCell: function (td, cellData, rowData) { {% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %} - console.log('{{ the_url }}'); var detail_btn = '' + cellData + ''; $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); }}, + {targets: 4, createdCell: function (td, cellData, rowData) { + $(td).html(rowData.cluster_name) + }}, + {targets: 5, createdCell: function (td, cellData, rowData) { + $(td).html(rowData.get_type_display) + }}, + {targets: 6, createdCell: function (td, cellData, rowData) { + $(td).html(rowData.get_env_display) + }}, + {targets: 7, createdCell: function (td, cellData, rowData) { + $(td).html(rowData.hardware_info) + }}, {targets: 8, createdCell: function (td, cellData) { if (!cellData) { $(td).html('') @@ -98,12 +109,15 @@ function initTable() { }} ], ajax_url: '{% url "api-assets:asset-list" %}', - columns: [{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" }, {data: "cluster_name"}, - {data: "get_type_display" }, {data: "get_env_display"}, {data: "hardware_info"}, - {data: "is_active" }, {data: "is_connective"}, {data: "id" }], + columns: [ + {data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" }, + {data: "cluster"}, {data: "type" }, {data: "env"}, + {data: "cpu_cores"}, {data: "is_active", orderable: false }, + {data: "is_connective", orderable: false}, {data: "id", orderable: false } + ], op_html: $('#actions').html() }; - return jumpserver.initDataTable(options); + return jumpserver.initServerSideDataTable(options); } $(document).ready(function(){ diff --git a/apps/common/mixins.py b/apps/common/mixins.py index 2832424c6..d1afb081b 100644 --- a/apps/common/mixins.py +++ b/apps/common/mixins.py @@ -47,8 +47,9 @@ class JSONResponseMixin(object): return JsonResponse(context) -class IDInFilterMixin(object): +class CustomFilterMixin(object): def filter_queryset(self, queryset): + queryset = super(CustomFilterMixin, self).filter_queryset(queryset) id_list = self.request.query_params.get('id__in') if id_list: import json diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 5bdc932e3..c687c038c 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -288,9 +288,17 @@ REST_FRAMEWORK = { 'users.authentication.PrivateTokenAuthentication', 'users.authentication.SessionAuthentication', ), - 'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',), + 'DEFAULT_FILTER_BACKENDS': ( + 'django_filters.rest_framework.DjangoFilterBackend', + 'rest_framework.filters.SearchFilter', + 'rest_framework.filters.OrderingFilter', + ), + 'ORDERING_PARAM': "order", + 'SEARCH_PARAM': "search", 'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S %z', 'DATETIME_INPUT_FORMATS': ['%Y-%m-%d %H:%M:%S %z'], + # 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', + 'PAGE_SIZE': 15 } AUTHENTICATION_BACKENDS = [ diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index 119151897..8be19b4a0 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -330,6 +330,120 @@ jumpserver.initDataTable = function (options) { return table; }; +jumpserver.initServerSideDataTable = function (options) { + // options = { + // ele *: $('#dataTable_id'), + // ajax_url *: '{% url 'users:user-list-api' %}', + // columns *: [{data: ''}, ....], + // dom: 'fltip', + // i18n_url: '{% static "js/...../en-us.json" %}', + // order: [[1, 'asc'], [2, 'asc'], ...], + // buttons: ['excel', 'pdf', 'print'], + // columnDefs: [{target: 0, createdCell: ()=>{}}, ...], + // uc_html: 'header button', + // op_html: 'div.btn-group?', + // paging: true + // } + var ele = options.ele || $('.dataTable'); + var columnDefs = [ + { + targets: 0, + orderable: false, + createdCell: function (td, cellData) { + $(td).html(''.replace('99991937', cellData)); + } + }, + {className: 'text-center', targets: '_all'} + ]; + columnDefs = options.columnDefs ? options.columnDefs.concat(columnDefs) : columnDefs; + var select = { + style: 'multi', + selector: 'td:first-child' + }; + var table = ele.DataTable({ + pageLength: options.pageLength || 15, + dom: options.dom || '<"#uc.pull-left">flt<"row m-t"<"col-md-8"<"#op.col-md-6"><"col-md-6 text-center"i>><"col-md-4"p>>', + order: options.order || [], + // select: options.select || 'multi', + buttons: [], + columnDefs: columnDefs, + serverSide: true, + processing: true, + ajax: { + url: options.ajax_url , + data: function (data) { + delete data.columns; + var length = data.length; + if (data.length !== null ){ + data.limit = data.length; + delete data.length; + } + if (data.start !== null) { + data.offset = data.start; + delete data.start; + } + if (data.search !== null) { + var search_val = data.search.value; + data.search = search_val; + } + if (data.order !== null && data.order.length === 1) { + var col = data.order[0].column; + var order = options.columns[col].data; + if (data.order[0].dir = "desc") { + order = "-" + order; + } + data.order = order; + } + }, + dataSrc: "results" + }, + columns: options.columns || [], + select: options.select || select, + language: { + search: "搜索", + lengthMenu: "每页 _MENU_", + info: "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项", + infoFiltered: "", + infoEmpty: "", + zeroRecords: "没有匹配项", + emptyTable: "没有记录", + paginate: { + first: "«", + previous: "‹", + next: "›", + last: "»" + } + }, + lengthMenu: [[15, 25, 50, -1], [15, 25, 50, "All"]] + }); + table.on('select', function(e, dt, type, indexes) { + var $node = table[ type ]( indexes ).nodes().to$(); + $node.find('input.ipt_check').prop('checked', true); + jumpserver.selected[$node.find('input.ipt_check').prop('id')] = true + }).on('deselect', function(e, dt, type, indexes) { + var $node = table[ type ]( indexes ).nodes().to$(); + $node.find('input.ipt_check').prop('checked', false); + jumpserver.selected[$node.find('input.ipt_check').prop('id')] = false + }). + on('draw', function(){ + $('#op').html(options.op_html || ''); + $('#uc').html(options.uc_html || ''); + }); + $('.ipt_check_all').on('click', function() { + if (!jumpserver.checked) { + $(this).closest('table').find('.ipt_check').prop('checked', true); + jumpserver.checked = true; + table.rows({search:'applied'}).select(); + } else { + $(this).closest('table').find('.ipt_check').prop('checked', false); + jumpserver.checked = false; + table.rows({search:'applied'}).deselect(); + } + }); + + return table; +}; + /** * 替换所有匹配exp的字符串为指定字符串 * @param exp 被替换部分的正则 diff --git a/apps/users/api.py b/apps/users/api.py index 326f2c3ec..8dbaf8b9a 100644 --- a/apps/users/api.py +++ b/apps/users/api.py @@ -13,14 +13,14 @@ from .tasks import write_login_log_async from .models import User, UserGroup from .permissions import IsSuperUser, IsValidUser, IsCurrentUserOrReadOnly from .utils import check_user_valid, generate_token -from common.mixins import IDInFilterMixin +from common.mixins import CustomFilterMixin from common.utils import get_logger logger = get_logger(__name__) -class UserViewSet(IDInFilterMixin, BulkModelViewSet): +class UserViewSet(CustomFilterMixin, BulkModelViewSet): queryset = User.objects.exclude(role="App") # queryset = User.objects.all().exclude(role="App").order_by("date_joined") serializer_class = UserSerializer @@ -72,7 +72,7 @@ class UserUpdatePKApi(generics.UpdateAPIView): user.save() -class UserGroupViewSet(IDInFilterMixin, BulkModelViewSet): +class UserGroupViewSet(CustomFilterMixin, BulkModelViewSet): queryset = UserGroup.objects.all() serializer_class = UserGroupSerializer