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