Details
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/app.js b/themes/src/main/resources/theme/base/admin/resources/js/app.js
index f75e66d..e1f9891 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/app.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/app.js
@@ -2802,3 +2802,81 @@ module.directive('kcOnReadFile', function ($parse) {
}
};
});
+
+module.controller('PagingCtrl', function ($scope) {
+ $scope.currentPageInput = 1;
+
+ $scope.firstPage = function() {
+ if (!$scope.hasPrevious()) return;
+ $scope.currentPage = 1;
+ $scope.currentPageInput = 1;
+ };
+
+ $scope.lastPage = function() {
+ if (!$scope.hasNext()) return;
+ $scope.currentPage = $scope.numberOfPages;
+ $scope.currentPageInput = $scope.numberOfPages;
+ };
+
+ $scope.previousPage = function() {
+ if (!$scope.hasPrevious()) return;
+ $scope.currentPage--;
+ $scope.currentPageInput = $scope.currentPage;
+ };
+
+ $scope.nextPage = function() {
+ if (!$scope.hasNext()) return;
+ $scope.currentPage++;
+ $scope.currentPageInput = $scope.currentPage;
+ };
+
+ $scope.hasNext = function() {
+ return $scope.currentPage < $scope.numberOfPages;
+ };
+
+ $scope.hasPrevious = function() {
+ return $scope.currentPage > 1;
+ };
+});
+
+module.directive('kcPaging', function () {
+ return {
+ scope: {
+ currentPage: '=',
+ currentPageInput: '=',
+ numberOfPages: '='
+ },
+ restrict: 'E',
+ replace: true,
+ controller: 'PagingCtrl',
+ templateUrl: resourceUrl + '/templates/kc-paging.html'
+ }
+});
+
+// Tests the page number input from currentPageInput to see
+// if it represents a valid page. If so, the current page is changed.
+module.directive('kcValidPage', function() {
+ return {
+ require: 'ngModel',
+ link: function(scope, element, attrs, ctrl) {
+ ctrl.$validators.inRange = function(modelValue, viewValue) {
+ if (viewValue >= 1 && viewValue <= scope.numberOfPages) {
+ scope.currentPage = viewValue;
+ }
+
+ return true;
+ }
+ }
+ }
+});
+
+// filter used for paged tables
+module.filter('startFrom', function () {
+ return function (input, start) {
+ if (input) {
+ start = +start;
+ return input.slice(start);
+ }
+ return [];
+ };
+});
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
index 4b33b14..7d8de51 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
@@ -731,9 +731,21 @@ module.controller('ClientImportCtrl', function($scope, $location, $upload, realm
});
-module.controller('ClientListCtrl', function($scope, realm, clients, Client, serverInfo, $route, Dialog, Notifications) {
+module.controller('ClientListCtrl', function($scope, realm, clients, Client, serverInfo, $route, Dialog, Notifications, filterFilter) {
$scope.realm = realm;
$scope.clients = clients;
+ $scope.currentPage = 1;
+ $scope.currentPageInput = 1;
+ $scope.pageSize = 20;
+ $scope.numberOfPages = Math.ceil($scope.clients.length/$scope.pageSize);
+
+ $scope.$watch('search', function (newVal, oldVal) {
+ $scope.filtered = filterFilter($scope.clients, newVal);
+ $scope.totalItems = $scope.filtered.length;
+ $scope.numberOfPages = Math.ceil($scope.totalItems/$scope.pageSize);
+ $scope.currentPage = 1;
+ $scope.currentPageInput = 1;
+ }, true);
$scope.removeClient = function(client) {
Dialog.confirmDelete(client.clientId, 'client', function() {
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
index 568ebdf..44ed518 100644
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
@@ -1291,9 +1291,21 @@ module.controller('RealmRevocationCtrl', function($scope, Realm, RealmPushRevoca
});
-module.controller('RoleListCtrl', function($scope, $route, Dialog, Notifications, realm, roles, RoleById) {
+module.controller('RoleListCtrl', function($scope, $route, Dialog, Notifications, realm, roles, RoleById, filterFilter) {
$scope.realm = realm;
$scope.roles = roles;
+ $scope.currentPage = 1;
+ $scope.currentPageInput = 1;
+ $scope.pageSize = 20;
+ $scope.numberOfPages = Math.ceil($scope.roles.length/$scope.pageSize);
+
+ $scope.$watch('searchQuery', function (newVal, oldVal) {
+ $scope.filtered = filterFilter($scope.roles, {name: newVal});
+ $scope.totalItems = $scope.filtered.length;
+ $scope.numberOfPages = Math.ceil($scope.totalItems/$scope.pageSize);
+ $scope.currentPage = 1;
+ $scope.currentPageInput = 1;
+ }, true);
$scope.removeRole = function (role) {
Dialog.confirmDelete(role.name, 'role', function () {
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-list.html
index 7f796e9..51aaa20 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-list.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-list.html
@@ -4,52 +4,53 @@
<kc-tooltip>{{:: 'clients.tooltip' | translate}}</kc-tooltip>
</h1>
- <table class="table table-striped table-bordered">
+ <table class="datatable table table-striped table-bordered dataTable no-footer">
<thead>
- <tr>
- <th class="kc-table-actions" colspan="6">
- <div class="form-inline">
- <div class="form-group">
- <div class="input-group">
- <input type="text" placeholder="{{:: 'search.placeholder' | translate}}" data-ng-model="search.clientId" class="form-control search" onkeyup="if(event.keyCode == 13){$(this).next('I').click();}">
- <div class="input-group-addon">
- <i class="fa fa-search" type="submit"></i>
+ <tr>
+ <th class="kc-table-actions" colspan="6">
+ <div class="form-inline">
+ <div class="form-group">
+ <div class="input-group">
+ <input type="text" placeholder="{{:: 'search.placeholder' | translate}}" data-ng-model="search.clientId" class="form-control search" onkeyup="if(event.keyCode === 13){$(this).next('I').click();}">
+ <div class="input-group-addon">
+ <i class="fa fa-search" type="submit"></i>
+ </div>
</div>
</div>
- </div>
- <div class="pull-right" data-ng-show="access.manageClients">
- <a id="createClient" class="btn btn-default" href="#/create/client/{{realm.realm}}">{{:: 'create' | translate}}</a>
- <a id="importClient" class="btn btn-default" href="#/import/client/{{realm.realm}}" data-ng-show="importButton">{{:: 'import' | translate}}</a>
+ <div class="pull-right" data-ng-show="access.manageClients">
+ <a id="createClient" class="btn btn-default" href="#/create/client/{{realm.realm}}">{{:: 'create' | translate}}</a>
+ <a id="importClient" class="btn btn-default" href="#/import/client/{{realm.realm}}" data-ng-show="importButton">{{:: 'import' | translate}}</a>
+ </div>
</div>
- </div>
- </th>
- </tr>
- <tr data-ng-hide="clients.length == 0">
- <th>{{:: 'client-id' | translate}}</th>
- <th>{{:: 'enabled' | translate}}</th>
- <th>{{:: 'base-url' | translate}}</th>
- <th colspan="3">{{:: 'actions' | translate}}</th>
- </tr>
+ </th>
+ </tr>
+ <tr data-ng-hide="clients.length == 0">
+ <th>{{:: 'client-id' | translate}}</th>
+ <th>{{:: 'enabled' | translate}}</th>
+ <th>{{:: 'base-url' | translate}}</th>
+ <th colspan="3">{{:: 'actions' | translate}}</th>
+ </tr>
</thead>
<tbody>
- <tr ng-repeat="client in clients | filter:search | orderBy:'clientId'">
- <td><a href="#/realms/{{realm.realm}}/clients/{{client.id}}">{{client.clientId}}</a></td>
- <td translate="{{client.enabled}}"></td>
- <td ng-class="{'text-muted': !client.baseUrl}">
- <a href="{{client.rootUrl}}{{client.baseUrl}}" target="_blank" data-ng-show="client.baseUrl">{{client.rootUrl}}{{client.baseUrl}}</a>
- <span data-ng-hide="client.baseUrl">{{:: 'not-defined' | translate}}</span>
- </td>
- <td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/clients/{{client.id}}">{{:: 'edit' | translate}}</td>
- <td class="kc-action-cell" data-ng-click="exportClient(client)">{{:: 'export' | translate}}</td>
- <td class="kc-action-cell" data-ng-show="access.manageClients" data-ng-click="removeClient(client)">{{:: 'delete' | translate}}</td>
- </tr>
- <tr data-ng-show="(clients | filter:search).length == 0">
- <td class="text-muted" colspan="3" data-ng-show="search.clientId">{{:: 'no-results' | translate}}</td>
- <td class="text-muted" colspan="3" data-ng-hide="search.clientId">{{:: 'no-clients-available' | translate}}</td>
- </tr>
+ <tr ng-repeat="client in clients| filter:search | orderBy:'clientId' | startFrom:(currentPage - 1) * pageSize | limitTo:pageSize">
+ <td><a href="#/realms/{{realm.realm}}/clients/{{client.id}}">{{client.clientId}}</a></td>
+ <td translate="{{client.enabled}}"></td>
+ <td ng-class="{'text-muted': !client.baseUrl}">
+ <a href="{{client.rootUrl}}{{client.baseUrl}}" target="_blank" data-ng-show="client.baseUrl">{{client.rootUrl}}{{client.baseUrl}}</a>
+ <span data-ng-hide="client.baseUrl">{{:: 'not-defined' | translate}}</span>
+ </td>
+ <td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/clients/{{client.id}}">{{:: 'edit' | translate}}</td>
+ <td class="kc-action-cell" data-ng-click="exportClient(client)">{{:: 'export' | translate}}</td>
+ <td class="kc-action-cell" data-ng-show="access.manageClients" data-ng-click="removeClient(client)">{{:: 'delete' | translate}}</td>
+ </tr>
+ <tr data-ng-show="(clients | filter:search).length == 0">
+ <td class="text-muted" colspan="4" data-ng-show="search.clientId">{{:: 'no-results' | translate}}</td>
+ <td class="text-muted" colspan="4" data-ng-hide="search.clientId">{{:: 'no-clients-available' | translate}}</td>
+ </tr>
</tbody>
</table>
+ <kc-paging current-page='currentPage' number-of-pages='numberOfPages' current-page-input='currentPageInput'></kc-paging>
</div>
<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/role-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/role-list.html
index 8b2bd26..5c0d473 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/role-list.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/role-list.html
@@ -6,47 +6,48 @@
<li><a href="#/realms/{{realm.realm}}/default-roles">{{:: 'default-roles' | translate}}</a></li>
</ul>
- <table class="table table-striped table-bordered">
+ <table class="datatable table table-striped table-bordered dataTable no-footer">
<thead>
- <tr>
- <th class="kc-table-actions" colspan="5">
- <div class="form-inline">
- <div class="form-group">
- <div class="input-group">
- <input type="text" placeholder="{{:: 'search.placeholder' | translate}}" data-ng-model="searchQuery" class="form-control search" onkeyup="if(event.keyCode == 13){$(this).next('I').click();}">
- <div class="input-group-addon">
- <i class="fa fa-search" type="submit"></i>
+ <tr>
+ <th class="kc-table-actions" colspan="5">
+ <div class="form-inline">
+ <div class="form-group">
+ <div class="input-group">
+ <input type="text" placeholder="{{:: 'search.placeholder' | translate}}" data-ng-model="searchQuery" class="form-control search" onkeyup="if (event.keyCode === 13){$(this).next('I').click(); }">
+ <div class="input-group-addon">
+ <i class="fa fa-search" type="submit"></i>
+ </div>
</div>
</div>
- </div>
- <div class="pull-right" data-ng-show="access.manageRealm">
- <a id="createRole" class="btn btn-default" href="#/create/role/{{realm.realm}}">{{:: 'add-role' | translate}}</a>
+ <div class="pull-right" data-ng-show="access.manageRealm">
+ <a id="createRole" class="btn btn-default" href="#/create/role/{{realm.realm}}">{{:: 'add-role' | translate}}</a>
+ </div>
</div>
- </div>
- </th>
- </tr>
- <tr data-ng-show="roles && roles.length > 0">
- <th>{{:: 'role-name' | translate}}</th>
- <th>{{:: 'composite' | translate}}</th>
- <th>{{:: 'description' | translate}}</th>
- <th colspan="2">{{:: 'actions' | translate}}</th>
- </tr>
+ </th>
+ </tr>
+ <tr data-ng-show="roles && roles.length > 0">
+ <th>{{:: 'role-name' | translate}}</th>
+ <th>{{:: 'composite' | translate}}</th>
+ <th>{{:: 'description' | translate}}</th>
+ <th colspan="2">{{:: 'actions' | translate}}</th>
+ </tr>
</thead>
<tbody>
- <tr ng-repeat="role in roles | orderBy:'name' | filter:{name: searchQuery}">
- <td><a href="#/realms/{{realm.realm}}/roles/{{role.id}}">{{role.name}}</a></td>
- <td translate="{{role.composite}}"></td>
- <td>{{role.description}}</td>
- <td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/roles/{{role.id}}">{{:: 'edit' | translate}}</td>
- <td class="kc-action-cell" data-ng-click="removeRole(role)">{{:: 'delete' | translate}}</td>
- </tr>
- <tr data-ng-show="(roles | filter:{name: searchQuery}).length == 0">
- <td class="text-muted" colspan="3" data-ng-show="searchQuery">{{:: 'no-results' | translate}}</td>
- <td class="text-muted" colspan="3" data-ng-hide="searchQuery">{{:: 'no-realm-roles-available' | translate}}</td>
- </tr>
+ <tr ng-repeat="role in roles| filter:{name: searchQuery} | orderBy:'name'| startFrom:(currentPage - 1) * pageSize | limitTo:pageSize">
+ <td><a href="#/realms/{{realm.realm}}/roles/{{role.id}}">{{role.name}}</a></td>
+ <td translate="{{role.composite}}"></td>
+ <td>{{role.description}}</td>
+ <td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/roles/{{role.id}}">{{:: 'edit' | translate}}</td>
+ <td class="kc-action-cell" data-ng-click="removeRole(role)">{{:: 'delete' | translate}}</td>
+ </tr>
+ <tr data-ng-show="(roles | filter:{name: searchQuery}).length == 0">
+ <td class="text-muted" colspan="4" data-ng-show="searchQuery">{{:: 'no-results' | translate}}</td>
+ <td class="text-muted" colspan="4" data-ng-hide="searchQuery">{{:: 'no-realm-roles-available' | translate}}</td>
+ </tr>
</tbody>
</table>
+ <kc-paging current-page='currentPage' number-of-pages='numberOfPages' current-page-input='currentPageInput'></kc-paging>
</div>
<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-paging.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-paging.html
new file mode 100644
index 0000000..653e4a5
--- /dev/null
+++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-paging.html
@@ -0,0 +1,25 @@
+<div ng-hide="numberOfPages < 2" class="dataTables_footer">
+ <div class="dataTables_paginate paging_bootstrap_input">
+ <ul class="pagination">
+ <li class="first" ng-class="{disabled: !hasPrevious()}" ng-click="firstPage()">
+ <span class="i fa fa-angle-double-left"></span>
+ </li>
+ <li class="prev" ng-class="{disabled: !hasPrevious()}" ng-click="previousPage()">
+ <span class="i fa fa-angle-left"></span>
+ </li>
+ </ul>
+ <div class="pagination-input">
+ <input ng-model="currentPageInput" kc-valid-page class="paginate_input" type="text">
+ <span class="paginate_of">of <b>{{numberOfPages}}</b></span>
+ </div>
+ <ul class="pagination">
+ <li class="next" ng-class="{disabled: !hasNext()}" ng-click="nextPage()">
+ <span class="i fa fa-angle-right"></span>
+ </li>
+ <li class="last" ng-class="{disabled: !hasNext()}" ng-click="lastPage()">
+ <span class="i fa fa-angle-double-right">
+ </span>
+ </li>
+ </ul>
+ </div>
+</div>
\ No newline at end of file