>From a804ca06084eea9c098b1a228e829485678ae41a Mon Sep 17 00:00:00 2001
From: "Endi S. Dewata" <edewata@redhat.com>
Date: Tue, 15 Mar 2016 21:10:30 +0100
Subject: [PATCH] Added TPS token filter dialog.

The TPS UI Tokens page and the pki tps-token-find CLI have been
modified to provide an interface to filter tokens based on their
attributes.

The TokenService.findTokens() has been modified to accept
additional search criteria based on token attributes.

https://fedorahosted.org/pki/ticket/1482
---
 .../netscape/certsrv/tps/token/TokenClient.java    | 20 ++++++-
 .../netscape/certsrv/tps/token/TokenResource.java  |  4 ++
 .../netscape/cmstools/tps/token/TokenFindCLI.java  | 31 +++++++++-
 base/server/share/webapps/pki/js/pki-ui.js         | 28 +++++++--
 base/tps/shared/webapps/tps/js/token.js            | 70 +++++++++++++++++++++-
 base/tps/shared/webapps/tps/ui/tokens.html         | 31 ++++++++++
 .../dogtagpki/server/tps/rest/TokenService.java    | 29 ++++++++-
 7 files changed, 200 insertions(+), 13 deletions(-)

diff --git a/base/common/src/com/netscape/certsrv/tps/token/TokenClient.java b/base/common/src/com/netscape/certsrv/tps/token/TokenClient.java
index a62e71cf8b7b418d4d595e4a3f474eb95cd02b3d..63e0edcde173524f2833cdd2ca317bb0d05f8af1 100644
--- a/base/common/src/com/netscape/certsrv/tps/token/TokenClient.java
+++ b/base/common/src/com/netscape/certsrv/tps/token/TokenClient.java
@@ -40,8 +40,24 @@ public class TokenClient extends Client {
         resource = createProxy(TokenResource.class);
     }
 
-    public TokenCollection findTokens(String filter, Integer start, Integer size) {
-        Response response = resource.findTokens(filter, start, size);
+    public TokenCollection findTokens(
+            String filter,
+            String tokenID,
+            String userID,
+            String type,
+            String status,
+            Integer start,
+            Integer size) {
+
+        Response response = resource.findTokens(
+                filter,
+                tokenID,
+                userID,
+                type,
+                status,
+                start,
+                size);
+
         return client.getEntity(response, TokenCollection.class);
     }
 
diff --git a/base/common/src/com/netscape/certsrv/tps/token/TokenResource.java b/base/common/src/com/netscape/certsrv/tps/token/TokenResource.java
index e530a852758f6589f4abb0f1dfae707c349d3931..4c1bc39aa0264f5f4f01077e3642bc6650b81aa7 100644
--- a/base/common/src/com/netscape/certsrv/tps/token/TokenResource.java
+++ b/base/common/src/com/netscape/certsrv/tps/token/TokenResource.java
@@ -45,6 +45,10 @@ public interface TokenResource {
     @ClientResponseType(entityType=TokenCollection.class)
     public Response findTokens(
             @QueryParam("filter") String filter,
+            @QueryParam("tokenID") String tokenID,
+            @QueryParam("userID") String userID,
+            @QueryParam("type") String type,
+            @QueryParam("status") String status,
             @QueryParam("start") Integer start,
             @QueryParam("size") Integer size);
 
diff --git a/base/java-tools/src/com/netscape/cmstools/tps/token/TokenFindCLI.java b/base/java-tools/src/com/netscape/cmstools/tps/token/TokenFindCLI.java
index 9afc05ebef9a021db9c645c0b9e7320ba8cf31b4..cfe7c2fe3b2a906367f5863296588de8ede81860 100644
--- a/base/java-tools/src/com/netscape/cmstools/tps/token/TokenFindCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/tps/token/TokenFindCLI.java
@@ -48,7 +48,23 @@ public class TokenFindCLI extends CLI {
     }
 
     public void createOptions() {
-        Option option = new Option(null, "start", true, "Page start");
+        Option option = new Option(null, "token", true, "Token ID");
+        option.setArgName("token ID");
+        options.addOption(option);
+
+        option = new Option(null, "user", true, "User ID");
+        option.setArgName("user ID");
+        options.addOption(option);
+
+        option = new Option(null, "type", true, "Token type");
+        option.setArgName("type");
+        options.addOption(option);
+
+        option = new Option(null, "status", true, "Token status");
+        option.setArgName("status");
+        options.addOption(option);
+
+        option = new Option(null, "start", true, "Page start");
         option.setArgName("start");
         options.addOption(option);
 
@@ -78,6 +94,10 @@ public class TokenFindCLI extends CLI {
 
         String[] cmdArgs = cmd.getArgs();
         String filter = cmdArgs.length > 0 ? cmdArgs[0] : null;
+        String tokenID = cmd.getOptionValue("token");
+        String userID = cmd.getOptionValue("user");
+        String type = cmd.getOptionValue("type");
+        String status = cmd.getOptionValue("status");
 
         String s = cmd.getOptionValue("start");
         Integer start = s == null ? null : Integer.valueOf(s);
@@ -85,7 +105,14 @@ public class TokenFindCLI extends CLI {
         s = cmd.getOptionValue("size");
         Integer size = s == null ? null : Integer.valueOf(s);
 
-        TokenCollection result = tokenCLI.tokenClient.findTokens(filter, start, size);
+        TokenCollection result = tokenCLI.tokenClient.findTokens(
+                filter,
+                tokenID,
+                userID,
+                type,
+                status,
+                start,
+                size);
 
         MainCLI.printMessage(result.getTotal() + " entries matched");
         if (result.getTotal() == 0) return;
diff --git a/base/server/share/webapps/pki/js/pki-ui.js b/base/server/share/webapps/pki/js/pki-ui.js
index 73043f51f9ae0ff00318aad80bd550af02bc5c72..701816b9357b4525f60ccd3eb3e096efea88d479 100644
--- a/base/server/share/webapps/pki/js/pki-ui.js
+++ b/base/server/share/webapps/pki/js/pki-ui.js
@@ -198,6 +198,7 @@ var Dialog = Backbone.View.extend({
 
         self.title = options.title;
         self.content = options.content;
+        self.entry = options.entry || {};
 
         // list of readonly fields
         // by default all fields are editable
@@ -499,11 +500,15 @@ var Table = Backbone.View.extend({
         var self = this;
 
         Table.__super__.initialize.call(self, options);
+
         self.entries = options.entries || [];
         self.columnMappings = options.columnMappings || {};
         self.mode = options.mode || "view";
         self.parent = options.parent;
 
+        self.searchFilter = "";
+        self.searchAttributes = {};
+
         self.addDialog = options.addDialog;
         self.editDialog = options.editDialog;
         self.viewDialog = options.viewDialog;
@@ -525,6 +530,9 @@ var Table = Backbone.View.extend({
         self.searchField = $("input[name='search']", self.thead);
         self.searchField.keypress(function(e) {
             if (e.which == 13) {
+
+                self.searchFilter = self.searchField.val();
+
                 // show the first page of search results
                 self.page = 1;
                 self.render();
@@ -634,11 +642,10 @@ var Table = Backbone.View.extend({
         var self = this;
 
         // perform manual filter
-        var filter = self.searchField.val();
         self.filteredEntries = [];
 
         _(self.entries).each(function(item, index) {
-            if (!self.matchesFilter(item, filter)) return;
+            if (!self.matchesFilter(item, self.searchFilter)) return;
             self.filteredEntries.push(item);
         });
 
@@ -792,11 +799,20 @@ var ModelTable = Table.extend({
         }
 
         // set query based on current page, page size, and filter
-        self.collection.query({
+        var params = {
             start: (self.page - 1) * self.pageSize,
-            size: self.pageSize,
-            filter: self.searchField.val()
-        });
+            size: self.pageSize
+        };
+
+        if (self.searchFilter != null) {
+            params["filter"] = self.searchFilter;
+        }
+
+        if (!_.isEmpty(self.searchAttributes)) {
+            _.extend(params, self.searchAttributes);
+        }
+
+        self.collection.query(params);
 
         // fetch data based on query
         self.collection.fetch({
diff --git a/base/tps/shared/webapps/tps/js/token.js b/base/tps/shared/webapps/tps/js/token.js
index fb1c3ea4608312882eaa6d758fa18f6bd160f2e8..e09fa5fc6816f812c64e0fed5605707ceec71859 100644
--- a/base/tps/shared/webapps/tps/js/token.js
+++ b/base/tps/shared/webapps/tps/js/token.js
@@ -19,6 +19,17 @@
  * @author Endi S. Dewata
  */
 
+// TODO: load labels from server
+var TokenStatus = {
+    UNINITIALIZED       : "Uninitialized",
+    ACTIVE              : "Active",
+    TEMP_LOST           : "Temporarily lost",
+    PERM_LOST           : "Permanently lost",
+    DAMAGED             : "Physically damaged",
+    TEMP_LOST_PERM_LOST : "Temporarily lost then permanently lost",
+    TERMINATED          : "Terminated"
+};
+
 var TokenModel = Model.extend({
     urlRoot: "/tps/rest/tokens",
     parseResponse: function(response) {
@@ -119,6 +130,34 @@ var TokenDialog = Dialog.extend({
     }
 });
 
+var TokenFilterDialog = Dialog.extend({
+    loadField: function(input) {
+        var self = this;
+
+        var name = input.attr("name");
+        if (name != "status") {
+            TokenFilterDialog.__super__.loadField.call(self, input);
+            return;
+        }
+
+        var select = input.empty();
+        var status = self.entry.status;
+
+        $('<option/>', {
+            text: "",
+            value: ""
+        }).appendTo(select);
+
+        _.each(TokenStatus, function(value, key) {
+            $('<option/>', {
+                value: key,
+                text: value,
+                selected: key == status
+            }).appendTo(select);
+        });
+    }
+});
+
 var TokenPage = EntryPage.extend({
     initialize: function(options) {
         var self = this;
@@ -314,11 +353,40 @@ var TokensPage = Page.extend({
     load: function() {
         var self = this;
 
+        self.tokensTable = self.$("table[name='tokens']");
+
         var table = new TokensTable({
-            el: $("table[name='tokens']"),
+            el: self.tokensTable,
             collection: new TokenCollection()
         });
 
         table.render();
+
+        $("a[name='filter']", self.tokensTable).click(function(e) {
+
+            e.preventDefault();
+
+            var dialog = new TokenFilterDialog({
+                el: $("#token-filter-dialog"),
+                actions: ["cancel", "apply"]
+            });
+
+            dialog.entry = _.clone(table.searchAttributes);
+
+            dialog.handler("apply", function() {
+
+                dialog.save();
+
+                table.searchAttributes = _.clone(dialog.entry);
+
+                // show the first page of search results
+                table.page = 1;
+                table.render();
+
+                dialog.close();
+            });
+
+            dialog.open();
+        });
     }
 });
diff --git a/base/tps/shared/webapps/tps/ui/tokens.html b/base/tps/shared/webapps/tps/ui/tokens.html
index b0782d3731e4664ef2bcff5474d640d130807fc8..4f10578e83c8391961a10060cd0445ddf11e5614 100644
--- a/base/tps/shared/webapps/tps/ui/tokens.html
+++ b/base/tps/shared/webapps/tps/ui/tokens.html
@@ -30,6 +30,7 @@
          <th class="pki-table-actions" colspan="10">
              <span name="search">
                  <input name="search" type="text" placeholder="Search...">
+                 <a name="filter" href="#tokens-filter">Filter</a>
              </span>
              <span class="pki-table-buttons">
                  <button name="add">Add</button>
@@ -127,3 +128,33 @@
         </div>
     </div>
 </div>
+
+<div id="token-filter-dialog" class="modal">
+    <div class="modal-dialog">
+        <div class="modal-content">
+            <div class="modal-header">
+                <button type="button" class="close" data-dismiss="modal" aria-hidden="true">
+                    <span class="pficon pficon-close"></span>
+                </button>
+                <h4 class="modal-title">Token Filter</h4>
+            </div>
+            <div class="modal-body">
+                <fieldset>
+                    <label>Token ID</label>
+                    <input name="tokenID"><br>
+                    <label>User ID</label>
+                    <input name="userID"><br>
+                    <label>Type</label>
+                    <input name="type"><br>
+                    <label>Status</label>
+                    <select name="status">
+                    </select><br>
+                </fieldset>
+            </div>
+            <div class="modal-footer">
+                <button name="apply" class="btn btn-primary">Apply</button>
+                <button name="cancel" class="btn btn-default" data-dismiss="modal">Cancel</button>
+            </div>
+        </div>
+    </div>
+</div>
diff --git a/base/tps/src/org/dogtagpki/server/tps/rest/TokenService.java b/base/tps/src/org/dogtagpki/server/tps/rest/TokenService.java
index 226f039f489e15ce7a9e1a790ce0cfe3c08f7ad1..a0ca7add3e2dc2d2a6a215c6bb436459f69990f3 100644
--- a/base/tps/src/org/dogtagpki/server/tps/rest/TokenService.java
+++ b/base/tps/src/org/dogtagpki/server/tps/rest/TokenService.java
@@ -268,7 +268,14 @@ public class TokenService extends PKIService implements TokenResource {
     }
 
     @Override
-    public Response findTokens(String filter, Integer start, Integer size) {
+    public Response findTokens(
+            String filter,
+            String tokenID,
+            String userID,
+            String type,
+            String status,
+            Integer start,
+            Integer size) {
 
         CMS.debug("TokenService.findTokens()");
 
@@ -276,6 +283,24 @@ public class TokenService extends PKIService implements TokenResource {
             throw new BadRequestException("Filter is too short.");
         }
 
+        Map<String, String> attributes = new HashMap<String, String>();
+
+        if (StringUtils.isNotEmpty(tokenID)) {
+            attributes.put("id", tokenID);
+        }
+
+        if (StringUtils.isNotEmpty(userID)) {
+            attributes.put("userID", userID);
+        }
+
+        if (StringUtils.isNotEmpty(type)) {
+            attributes.put("type", type);
+        }
+
+        if (StringUtils.isNotEmpty(status)) {
+            attributes.put("status", status);
+        }
+
         start = start == null ? 0 : start;
         size = size == null ? DEFAULT_SIZE : size;
 
@@ -283,7 +308,7 @@ public class TokenService extends PKIService implements TokenResource {
             TPSSubsystem subsystem = (TPSSubsystem)CMS.getSubsystem(TPSSubsystem.ID);
             TokenDatabase database = subsystem.getTokenDatabase();
 
-            Iterator<TokenRecord> tokens = database.findRecords(filter).iterator();
+            Iterator<TokenRecord> tokens = database.findRecords(filter, attributes).iterator();
 
             TokenCollection response = new TokenCollection();
             int i = 0;
-- 
2.4.3

