From 768402ecc04557e745b388986ec5d0ac1b7086e5 Mon Sep 17 00:00:00 2001
From: "Endi S. Dewata" <edewata@redhat.com>
Date: Tue, 15 Apr 2014 10:12:51 -0400
Subject: [PATCH] Added group members table in TPS UI.

A new table has been added to the group page in TPS UI for managing
the group members.

The addGroupMember() method in group REST interface has been fixed
to accept JSON request properly.

Ticket #654
---
 .../com/netscape/certsrv/group/GroupClient.java    |   4 +-
 .../com/netscape/certsrv/group/GroupResource.java  |   2 +-
 .../org/dogtagpki/server/rest/GroupService.java    |  11 +-
 base/server/share/webapps/pki/js/pki-ui.js         |   5 +-
 base/tps-tomcat/shared/webapps/tps/js/group.js     | 135 +++++++++++++++++++++
 base/tps-tomcat/shared/webapps/tps/ui/group.html   |  70 +++++++++++
 6 files changed, 214 insertions(+), 13 deletions(-)

diff --git a/base/common/src/com/netscape/certsrv/group/GroupClient.java b/base/common/src/com/netscape/certsrv/group/GroupClient.java
index 4823d4ed08f0668588e39b12198d4c9e425ac821..f516e9842cb0c7d9fb1862ce6404213ed0106af3 100644
--- a/base/common/src/com/netscape/certsrv/group/GroupClient.java
+++ b/base/common/src/com/netscape/certsrv/group/GroupClient.java
@@ -76,7 +76,9 @@ public class GroupClient extends Client {
     }
 
     public GroupMemberData addGroupMember(String groupID, String memberID) {
-        Response response = groupClient.addGroupMember(groupID, memberID);
+        GroupMemberData data = new GroupMemberData();
+        data.setID(memberID);
+        Response response = groupClient.addGroupMember(groupID, data);
         return client.getEntity(response, GroupMemberData.class);
     }
 
diff --git a/base/common/src/com/netscape/certsrv/group/GroupResource.java b/base/common/src/com/netscape/certsrv/group/GroupResource.java
index 2f42f4aa7d7d948875ab7c5e5955709f2f646000..aff584fe66d47dc64532fbc1a91cf31ef3664671 100644
--- a/base/common/src/com/netscape/certsrv/group/GroupResource.java
+++ b/base/common/src/com/netscape/certsrv/group/GroupResource.java
@@ -77,7 +77,7 @@ public interface GroupResource {
     @POST
     @Path("{groupID}/members")
     @ClientResponseType(entityType=GroupMemberData.class)
-    public Response addGroupMember(@PathParam("groupID") String groupID, String memberID);
+    public Response addGroupMember(@PathParam("groupID") String groupID, GroupMemberData groupMemberData);
 
     @GET
     @Path("{groupID}/members/{memberID}")
diff --git a/base/server/cms/src/org/dogtagpki/server/rest/GroupService.java b/base/server/cms/src/org/dogtagpki/server/rest/GroupService.java
index 0f3d9b0db25dcdd313468f5dc90b8eac5fd5f78b..3653263153829168d9ad9fab2af11417269dc505 100644
--- a/base/server/cms/src/org/dogtagpki/server/rest/GroupService.java
+++ b/base/server/cms/src/org/dogtagpki/server/rest/GroupService.java
@@ -377,18 +377,11 @@ public class GroupService extends PKIService implements GroupResource {
     }
 
     @Override
-    public Response addGroupMember(String groupID, String memberID) {
-
+    public Response addGroupMember(String groupID, GroupMemberData groupMemberData) {
         if (groupID == null) throw new BadRequestException("Group ID is null.");
-        if (memberID == null) throw new BadRequestException("Member ID is null.");
-
-        GroupMemberData groupMemberData = new GroupMemberData();
-        groupMemberData.setID(memberID);
+        if (groupMemberData.getID() == null) throw new BadRequestException("Member ID is null.");
         groupMemberData.setGroupID(groupID);
-        return addGroupMember(groupMemberData);
-    }
 
-    public Response addGroupMember(GroupMemberData groupMemberData) {
         try {
             GroupMemberProcessor processor = new GroupMemberProcessor(getLocale(headers));
             processor.setUriInfo(uriInfo);
diff --git a/base/server/share/webapps/pki/js/pki-ui.js b/base/server/share/webapps/pki/js/pki-ui.js
index df6812bf6052dd909347493b0ff640d740614866..d983dfd401ab6a56205311c76c586f60ababedeb 100644
--- a/base/server/share/webapps/pki/js/pki-ui.js
+++ b/base/server/share/webapps/pki/js/pki-ui.js
@@ -827,7 +827,9 @@ var ModelTable = Table.extend({
         var self = this;
 
         var dialog = self.addDialog;
-        dialog.entry = {};
+
+        var model = self.collection.model.call(self.collection);
+        dialog.entry = _.clone(model.attributes);
 
         dialog.handler("add", function() {
 
@@ -840,7 +842,6 @@ var ModelTable = Table.extend({
             });
 
             // save new entry with POST
-            var model = new self.collection.model();
             model.save(entry, {
                 wait: true,
                 success: function(model, response, options) {
diff --git a/base/tps-tomcat/shared/webapps/tps/js/group.js b/base/tps-tomcat/shared/webapps/tps/js/group.js
index 71f1e25e8466add92cd7a688edaee58be371d337..874cefa71fa3453b31e065b84b9dd9b5e0b00243 100644
--- a/base/tps-tomcat/shared/webapps/tps/js/group.js
+++ b/base/tps-tomcat/shared/webapps/tps/js/group.js
@@ -55,10 +55,145 @@ var GroupCollection = Collection.extend({
     }
 });
 
+var GroupMemberModel = Model.extend({
+    url: function() {
+        var self = this;
+
+        // There's an attribute name mismatch for group ID: the
+        // server uses GroupID and the client uses groupID. In other
+        // models the mismatch can be translated just fine, but in
+        // this model it becomes a problem because the model needs
+        // to construct the URL using the attribute.
+        //
+        // During read operation it needs to use the attribute that's
+        // already translated for client (i.e. groupID), but during
+        // add it needs to use the attribute meant for server (i.e.
+        // GroupID). So the workaround is to read whichever available.
+        var groupID = self.get("groupID");        // for read
+        groupID = groupID || self.get("GroupID"); // for add
+
+        var url = "/tps/rest/admin/groups/" + groupID + "/members";
+
+        // append member ID for read
+        if (self.id) url = url + "/" + self.id;
+
+        return url;
+    },
+    parseResponse: function(response) {
+        return {
+            id: response.id,
+            memberID: response.id,
+            groupID: response.GroupID
+        };
+    },
+    createRequest: function(entry) {
+        return {
+            id: entry.memberID,
+            GroupID: entry.groupID
+        };
+    }
+});
+
+var GroupMemberCollection = Collection.extend({
+    initialize: function(models, options) {
+        var self = this;
+        GroupMemberCollection.__super__.initialize.call(self, models, options);
+        options = options || {};
+        self.groupID = options.groupID;
+        self.urlRoot = "/tps/rest/admin/groups/" + self.groupID + "/members";
+    },
+    getEntries: function(response) {
+        return response.Member;
+    },
+    getLinks: function(response) {
+        return response.Link;
+    },
+    model: function(attrs, options) {
+        return new GroupMemberModel({
+            groupID: this.groupID
+        });
+    },
+    parseEntry: function(entry) {
+        return new GroupMemberModel({
+            id: entry.id,
+            memberID: entry.id,
+            groupID: entry.GroupID
+        });
+    }
+});
+
+var GroupMembersTableItem = TableItem.extend({
+    initialize: function(options) {
+        var self = this;
+        GroupMembersTableItem.__super__.initialize.call(self, options);
+    },
+    renderColumn: function(td, templateTD) {
+        var self = this;
+
+        GroupMembersTableItem.__super__.renderColumn.call(self, td, templateTD);
+
+        $("a", td).click(function(e) {
+            e.preventDefault();
+            self.table.open(self);
+        });
+    }
+});
+
 var GroupPage = EntryPage.extend({
     initialize: function(options) {
         var self = this;
         GroupPage.__super__.initialize.call(self, options);
+    },
+    setup: function() {
+        var self = this;
+
+        GroupPage.__super__.setup.call(self);
+
+        var dialog = self.$("#member-dialog");
+
+        var addDialog = new Dialog({
+            el: dialog,
+            title: "Add Member",
+            readonly: ["groupID"],
+            actions: ["cancel", "add"]
+        });
+
+        var editDialog = new Dialog({
+            el: dialog,
+            title: "Member",
+            readonly: ["groupID", "memberID"],
+            actions: ["close"]
+        });
+
+        self.membersTable = new ModelTable({
+            el: self.$("table[name='members']"),
+            pageSize: 10,
+            addDialog: addDialog,
+            editDialog: editDialog,
+            tableItem: GroupMembersTableItem,
+            parent: self
+        });
+    },
+    renderContent: function() {
+        var self = this;
+
+        GroupPage.__super__.renderContent.call(self);
+
+        // Since the members table is backed by a REST resource any
+        // changes will be executed immediately even if the page is
+        // in view mode. To avoid confusion, the members table will
+        // be disabled in page edit mode.
+        if (self.mode == "edit") {
+            // In page edit mode, the members tables is read-only.
+            self.membersTable.mode = "view";
+
+        } else { // self.mode == "view"
+            // In page view mode, the members table is editable.
+            self.membersTable.mode = "edit";
+        }
+
+        self.membersTable.collection = new GroupMemberCollection(null, { groupID: self.entry.id });
+        self.membersTable.render();
     }
 });
 
diff --git a/base/tps-tomcat/shared/webapps/tps/ui/group.html b/base/tps-tomcat/shared/webapps/tps/ui/group.html
index 9e612c2c6ac8beabc8daa0aa89dec05009d7ec63..16c6d9a0d3007e7823375b77c5e9f588a1683470 100644
--- a/base/tps-tomcat/shared/webapps/tps/ui/group.html
+++ b/base/tps-tomcat/shared/webapps/tps/ui/group.html
@@ -47,3 +47,73 @@
     <input name="description" readonly="readonly"><br>
 </fieldset>
 </div>
+
+<h2>Members</h2>
+
+<table name="members">
+<thead>
+    <tr>
+         <th class="pki-table-actions" colspan="2">
+             <span name="search">
+                 <input name="search" type="text" placeholder="Search...">
+             </span>
+             <span class="pki-table-buttons">
+                 <button name="add">Add</button>
+                 <button name="remove">Remove</button>
+             </span>
+         </th>
+    </tr>
+    <tr>
+        <th class="pki-select-column"><input id="members_selectall" type="checkbox"><label for="members_selectall">&nbsp;</label></th>
+        <th>User ID</th>
+    </tr>
+</thead>
+<tbody>
+    <tr>
+        <td class="pki-select-column"><input id="members_select" type="checkbox"><label for="members_select">&nbsp;</label></td>
+        <td name="id"><a href="/tps/ui/admin/groups/${groupID}/members/${id}">${id}</a></td>
+    </tr>
+</tbody>
+<tfoot>
+    <tr>
+         <th class="pki-table-actions" colspan="2">
+             <div class="pki-table-info">
+                 Total: <span name="totalEntries">0</span> entries
+             </div>
+             <div class="pki-page-controls">
+                 <ul class="pagination">
+                     <li><a href="#" name="first"><span class="i fa fa-angle-double-left"></span></a></li>
+                     <li><a href="#" name="prev"><span class="i fa fa-angle-left"></span></a></li>
+                 </ul>
+                 <span class="pki-page-jump">
+                     <input name="page" type="text" value="1"> of <span name="totalPages">1</span>
+                 </span>
+                 <ul class="pagination">
+                     <li><a href="#" name="next"><span class="i fa fa-angle-right"></span></a></li>
+                     <li><a href="#" name="last"><span class="i fa fa-angle-double-right"></span></a></li>
+                 </ul>
+             </div>
+         </th>
+    </tr>
+</tfoot>
+</table>
+
+<div id="member-dialog" class="rcue-dialog-background">
+    <div class="rcue-dialog">
+        <header>
+            <h1>Group Member</h1>
+            <a class="rcue-button-close" href="#"></a>
+        </header>
+        <fieldset>
+            <label>Group ID</label>
+            <input name="groupID" readonly="readonly"><br>
+            <label>User ID</label>
+            <input name="memberID" readonly="readonly"><br>
+        </fieldset>
+        <footer>
+            <button name="add" class="primary">Add</button>
+            <button name="close" class="primary">Close</button>
+            <button name="cancel">Cancel</button>
+        </footer>
+    </div>
+</div>
-- 
1.8.4.2

