>From 9298272305693771e22cbb59e37c5da05f679983 Mon Sep 17 00:00:00 2001
From: "Endi S. Dewata" <edewata@redhat.com>
Date: Thu, 19 May 2016 22:26:29 +0200
Subject: [PATCH] Added TPS UI for managing user roles.

The TPS UI has been modified to provide an interface to manage
the user roles.

The ErrorDialog was modified to handle both text and JSON error
responses.

https://fedorahosted.org/pki/ticket/2267
---
 base/server/share/webapps/pki/js/pki-ui.js         |  48 +++++----
 base/tps/shared/webapps/tps/js/token.js            |  14 ++-
 base/tps/shared/webapps/tps/js/user.js             | 111 ++++++++++++++++++++-
 base/tps/shared/webapps/tps/ui/index.html          |  10 +-
 .../webapps/tps/ui/{user.html => user-roles.html}  |  64 ++++--------
 base/tps/shared/webapps/tps/ui/user.html           |   1 +
 6 files changed, 168 insertions(+), 80 deletions(-)
 copy base/tps/shared/webapps/tps/ui/{user.html => user-roles.html} (67%)

diff --git a/base/server/share/webapps/pki/js/pki-ui.js b/base/server/share/webapps/pki/js/pki-ui.js
index 0b5cdb0ddbbc2ecbc5941602ecd6684c8ee626e7..a59a283d8d4e4fd5782fa42197384ad12c4ada5b 100644
--- a/base/server/share/webapps/pki/js/pki-ui.js
+++ b/base/server/share/webapps/pki/js/pki-ui.js
@@ -89,8 +89,9 @@ var Model = Backbone.Model.extend({
 
 var Collection = Backbone.Collection.extend({
     urlRoot: null,
-    initialize: function(options) {
+    initialize: function(models, options) {
         var self = this;
+        Collection.__super__.initialize.call(self, models, options);
 
         self.options = options;
         self.links = {};
@@ -337,8 +338,19 @@ var ErrorDialog = Backbone.View.extend({
         var self = this;
         ErrorDialog.__super__.initialize.call(self, options);
 
-        self.title = options.title;
-        self.content = options.content;
+        var response = options.response;
+        if (response && response.responseJSON) {
+            self.title = "HTTP Error " + response.responseJSON.Code;
+            self.content = response.responseJSON.Message;
+
+        } else if (response && response.responseText) {
+            self.title = "HTTP Error " + response.status;
+            self.content = response.responseText;
+
+        } else {
+            self.title = options.title;
+            self.content = options.content;
+        }
     },
     render: function() {
         var self = this;
@@ -830,8 +842,7 @@ var ModelTable = Table.extend({
             error: function(collection, response, options) {
                 new ErrorDialog({
                     el: $("#error-dialog"),
-                    title: "HTTP Error " + response.responseJSON.Code,
-                    content: response.responseJSON.Message
+                    response: response
                 }).open();
             }
         });
@@ -851,6 +862,8 @@ var ModelTable = Table.extend({
     },
     totalEntries: function() {
         var self = this;
+        if (!self.collection)
+            return 0;
         return self.collection.total;
     },
     open: function(item) {
@@ -889,8 +902,7 @@ var ModelTable = Table.extend({
                     }
                     new ErrorDialog({
                         el: $("#error-dialog"),
-                        title: "HTTP Error " + response.responseJSON.Code,
-                        content: response.responseJSON.Message
+                        response: response
                     }).open();
                 }
             });
@@ -904,8 +916,7 @@ var ModelTable = Table.extend({
             error: function(model, response, options) {
                 new ErrorDialog({
                     el: $("#error-dialog"),
-                    title: "HTTP Error " + response.responseJSON.Code,
-                    content: response.responseJSON.Message
+                    response: response
                 }).open();
             }
         });
@@ -945,8 +956,7 @@ var ModelTable = Table.extend({
                     }
                     new ErrorDialog({
                         el: $("#error-dialog"),
-                        title: "HTTP Error " + response.responseJSON.Code,
-                        content: response.responseJSON.Message
+                        response: response
                     }).open();
                 }
             });
@@ -968,8 +978,7 @@ var ModelTable = Table.extend({
                 error: function(model, response, options) {
                     new ErrorDialog({
                         el: $("#error-dialog"),
-                        title: "HTTP Error " + response.responseJSON.Code,
-                        content: response.responseJSON.Message
+                        response: response
                     }).open();
                 }
             });
@@ -1140,8 +1149,7 @@ var EntryPage = Page.extend({
                     }
                     new ErrorDialog({
                         el: $("#error-dialog"),
-                        title: "HTTP Error " + response.responseJSON.Code,
-                        content: response.responseJSON.Message
+                        response: response
                     }).open();
                 }
             });
@@ -1160,8 +1168,7 @@ var EntryPage = Page.extend({
                     }
                     new ErrorDialog({
                         el: $("#error-dialog"),
-                        title: "HTTP Error " + response.responseJSON.Code,
-                        content: response.responseJSON.Message
+                        response: response
                     }).open();
                 }
             });
@@ -1194,15 +1201,14 @@ var EntryPage = Page.extend({
         dialog.handler("ok", function() {
 
             self.model.changeStatus(action, {
-                success: function(data, textStatus, jqXHR) {
+                success: function(data, textStatus, response) {
                     self.entry = _.clone(self.model.attributes);
                     self.render();
                 },
-                error: function(jqXHR, textStatus, errorThrown) {
+                error: function(response, textStatus, errorThrown) {
                     new ErrorDialog({
                         el: $("#error-dialog"),
-                        title: "HTTP Error " + jqXHR.responseJSON.Code,
-                        content: jqXHR.responseJSON.Message
+                        response: response
                     }).open();
                 }
             });
diff --git a/base/tps/shared/webapps/tps/js/token.js b/base/tps/shared/webapps/tps/js/token.js
index 5c7bc0ca7931a393ef200508e9ddb66d802b08db..fe53beccac7071f1d4781e200b1c8833bc188a4f 100644
--- a/base/tps/shared/webapps/tps/js/token.js
+++ b/base/tps/shared/webapps/tps/js/token.js
@@ -198,14 +198,13 @@ var TokenPage = EntryPage.extend({
 
                     self.model.changeStatus({
                         status: dialog.entry.status,
-                        success: function(data, textStatus, jqXHR) {
+                        success: function(data, textStatus, response) {
                             self.render();
                         },
-                        error: function(jqXHR, textStatus, errorThrow) {
+                        error: function(response, textStatus, errorThrow) {
                             new ErrorDialog({
                                 el: $("#error-dialog"),
-                                title: "HTTP Error " + jqXHR.responseJSON.Code,
-                                content: jqXHR.responseJSON.Message
+                                response: response
                             }).open();
                         }
                     });
@@ -334,14 +333,13 @@ var TokenTableItem = TableItem.extend({
 
                 model.changeStatus({
                     status: dialog.entry.status,
-                    success: function(data, textStatus, jqXHR) {
+                    success: function(data, textStatus, response) {
                         self.table.render();
                     },
-                    error: function(jqXHR, textStatus, errorThrow) {
+                    error: function(response, textStatus, errorThrow) {
                         new ErrorDialog({
                             el: $("#error-dialog"),
-                            title: "HTTP Error " + jqXHR.responseJSON.Code,
-                            content: jqXHR.responseJSON.Message
+                            response: response
                         }).open();
                     }
                 });
diff --git a/base/tps/shared/webapps/tps/js/user.js b/base/tps/shared/webapps/tps/js/user.js
index f9f43c34fed5136431a9c37ed0528c835b3820f1..49835f0040f47795490a61292a8a8aa27354d2de 100644
--- a/base/tps/shared/webapps/tps/js/user.js
+++ b/base/tps/shared/webapps/tps/js/user.js
@@ -82,6 +82,74 @@ var UserCollection = Collection.extend({
     }
 });
 
+var UserRoleModel = Model.extend({
+    url: function() {
+        var self = this;
+
+        var userID = self.get("userID");
+        var url = "/tps/rest/admin/users/" + userID + "/memberships";
+
+        if (self.id) url = url + "/" + self.id;
+
+        return url;
+    },
+    parseResponse: function(response) {
+        return {
+            id: response.id,
+            roleID: response.id,
+            userID: response.UserID
+        };
+    },
+    createRequest: function(entry) {
+        return {
+            id: entry.roleID,
+            UserID: entry.userID
+        };
+    },
+    save: function(attributes, options) {
+        var self = this;
+        $.ajax({
+            type: "POST",
+            url: self.url(),
+            dataType: "json",
+            data: attributes.roleID,
+        }).done(function(data, textStatus, response) {
+            self.set(self.parseResponse(data));
+            if (options.success) options.success.call(self, self, response, options);
+        }).fail(function(response, textStatus, errorThrown) {
+            if (options.error) options.error.call(self, self, response, options);
+        });
+    }
+});
+
+var UserRoleCollection = Collection.extend({
+    initialize: function(models, options) {
+        var self = this;
+        UserRoleCollection.__super__.initialize.call(self, models, options);
+        options = options || {};
+        self.userID = options.userID;
+        self.urlRoot = "/tps/rest/admin/users/" + self.userID + "/memberships";
+    },
+    getEntries: function(response) {
+        return response.Membership;
+    },
+    getLinks: function(response) {
+        return response.Link;
+    },
+    model: function(attrs, options) {
+        return new UserRoleModel({
+            userID: this.userID
+        });
+    },
+    parseEntry: function(entry) {
+        return new UserRoleModel({
+            id: entry.id,
+            roleID: entry.id,
+            userID: entry.UserID
+        });
+    }
+});
+
 var UserProfilesTableItem = TableItem.extend({
     initialize: function(options) {
         var self = this;
@@ -199,10 +267,8 @@ var UserPage = EntryPage.extend({
 
         UserPage.__super__.setup.call(self);
 
-        var dialog = self.$("#user-profile-dialog");
-
-        var addDialog = new Dialog({
-            el: dialog,
+        var addProfileDialog = new Dialog({
+            el: self.$("#user-profile-dialog"),
             title: "Add Profile",
             actions: ["cancel", "add"]
         });
@@ -212,11 +278,17 @@ var UserPage = EntryPage.extend({
 
         self.profilesTable = new UserProfilesTable({
             el: self.profilesList,
-            addDialog: addDialog,
+            addDialog: addProfileDialog,
             pageSize: 10,
             parent: self
         });
 
+        self.showRolesAction = $("[name='showRoles']", self.viewMenu);
+
+        $("a", self.showRolesAction).click(function(e) {
+            e.preventDefault();
+            window.location.hash = window.location.hash + "/roles";
+        });
     },
     saveFields: function() {
         var self = this;
@@ -302,3 +374,32 @@ var UsersPage = Page.extend({
         table.render();
     }
 });
+
+var UserRolesPage = Page.extend({
+    load: function() {
+        var self = this;
+
+        if (self.collection && self.collection.options && self.collection.options.userID) {
+            $(".breadcrumb li[name='user'] a")
+                .attr("href", "#users/" + self.collection.options.userID)
+                .text("User " + self.collection.options.userID);
+            $(".pki-title").text("Roles for User " + self.collection.options.userID);
+        }
+
+        var addRoleDialog = new Dialog({
+            el: self.$("#user-role-dialog"),
+            title: "Add Role",
+            readonly: ["userID"],
+            actions: ["cancel", "add"]
+        });
+
+        var table = new ModelTable({
+            el: self.$("table[name='roles']"),
+            pageSize: 10,
+            addDialog: addRoleDialog,
+            collection: self.collection
+        });
+
+        table.render();
+    }
+});
diff --git a/base/tps/shared/webapps/tps/ui/index.html b/base/tps/shared/webapps/tps/ui/index.html
index 16867c19345169d856372278bd79d159741c8b5b..9446cfbf4e0a4e5ff47b69ce8617dc42ddea2b46 100644
--- a/base/tps/shared/webapps/tps/ui/index.html
+++ b/base/tps/shared/webapps/tps/ui/index.html
@@ -314,7 +314,7 @@ $(function() {
         new CertificatesPage({
             el: content,
             url: "certs.html",
-            collection: new CertificateCollection({ tokenID: id })
+            collection: new CertificateCollection(null, { tokenID: id })
         }).open();
     });
 
@@ -346,6 +346,14 @@ $(function() {
         }).open();
     });
 
+    router.route("users/:id/roles", "user-roles", function(id) {
+        new UserRolesPage({
+            el: content,
+            url: "user-roles.html",
+            collection: new UserRoleCollection(null, { userID: id })
+        }).open();
+    });
+
     router.route("new-user", "new-user", function() {
         new UserPage({
             el: content,
diff --git a/base/tps/shared/webapps/tps/ui/user.html b/base/tps/shared/webapps/tps/ui/user-roles.html
similarity index 67%
copy from base/tps/shared/webapps/tps/ui/user.html
copy to base/tps/shared/webapps/tps/ui/user-roles.html
index 435349e5a1b7bf316981af86aca77f4079b76be5..7d8c654f2bf809cfdf3e346d450f96113284c99f 100644
--- a/base/tps/shared/webapps/tps/ui/user.html
+++ b/base/tps/shared/webapps/tps/ui/user-roles.html
@@ -12,52 +12,24 @@
      with this program; if not, write to the Free Software Foundation, Inc.,
      51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
-     Copyright (C) 2014 Red Hat, Inc.
+     Copyright (C) 2013 Red Hat, Inc.
      All rights reserved.
      --- END COPYRIGHT BLOCK --- -->
 <div class="pki-header">
 <ol class="breadcrumb">
     <li><a href="#">Home</a></li>
     <li><a href="#users">Users</a></li>
-    <li class="active"><strong><span name="title">User ${id}</span></strong></li>
+    <li name="user"><a href="#users/${userID}">User ${id}</a></li>
+    <li class="active"><strong>Roles</strong></li>
 </ol>
 
-<span name="title" class="pki-title">User ${id}</span>
-
-<span class="pki-actions">
-
-<ul name="view" class="pki-actions-menu">
-<li name="edit"><a href="#">Edit</a></li>
-</ul>
-
-<span name="edit" class="pki-actions-menu" style="display: none;">
-<button name="cancel">Cancel</button>
-<button name="save" class="primary">Save</button>
-</span>
-
-</span>
-
+<span class="pki-title">User Roles</span>
 </div>
 
-<div name="user" class="pki-fields">
-<fieldset>
-    <label>User ID</label>
-    <input name="userID" readonly="readonly"><br>
-    <label>Full Name</label>
-    <input name="fullName" readonly="readonly"><br>
-    <label>Email</label>
-    <input name="email" readonly="readonly"><br>
-</fieldset>
-</div>
-
-<div name="profiles">
-
-<h2>Profiles</h2>
-
-<table name="list">
+<table name="roles">
 <thead>
     <tr>
-         <th class="pki-table-actions" colspan="2">
+         <th class="pki-table-actions" colspan="10">
              <span name="search">
                  <input name="search" type="text" placeholder="Search...">
              </span>
@@ -68,19 +40,19 @@
          </th>
     </tr>
     <tr>
-        <th class="pki-select-column"><input id="user-profiles-selectall" type="checkbox"><label for="user-profiles-selectall">&nbsp;</label></th>
-        <th>Profile ID</th>
+        <th class="pki-select-column"><input id="roles-selectall" type="checkbox"><label for="roles-selectall">&nbsp;</label></tdh>
+        <th>User ID</th>
     </tr>
 </thead>
 <tbody>
     <tr>
-        <td class="pki-select-column"><input id="user-profiles-select" type="checkbox"><label for="user-profiles-select">&nbsp;</label></td>
+        <td class="pki-select-column"><input id="roles-select" type="checkbox"><label for="roles-select">&nbsp;</label></td>
         <td name="id">${id}</td>
     </tr>
 </tbody>
 <tfoot>
     <tr>
-         <th class="pki-table-actions" colspan="2">
+         <th class="pki-table-actions" colspan="10">
              <div class="pki-table-info">
                  Total: <span name="totalEntries">0</span> entries
              </div>
@@ -102,24 +74,26 @@
 </tfoot>
 </table>
 
-</div>
-
-<div id="user-profile-dialog" class="modal">
+<div id="user-role-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">Add Profile</h4>
+                <h4 class="modal-title">User Role</h4>
             </div>
             <div class="modal-body">
-                Profile:
-                <select name="id">
-                </select>
+                <fieldset>
+                    <label>User ID</label>
+                    <input name="userID" readonly="readonly"><br>
+                    <label>Role ID</label>
+                    <input name="roleID" readonly="readonly"><br>
+                </fieldset>
             </div>
             <div class="modal-footer">
                 <button name="add" class="btn btn-primary">Add</button>
+                <button name="close" class="btn btn-primary">Close</button>
                 <button name="cancel" class="btn btn-default" data-dismiss="modal">Cancel</button>
             </div>
         </div>
diff --git a/base/tps/shared/webapps/tps/ui/user.html b/base/tps/shared/webapps/tps/ui/user.html
index 435349e5a1b7bf316981af86aca77f4079b76be5..22ca2575a73f7c0fead3a013f48299c96874d928 100644
--- a/base/tps/shared/webapps/tps/ui/user.html
+++ b/base/tps/shared/webapps/tps/ui/user.html
@@ -28,6 +28,7 @@
 
 <ul name="view" class="pki-actions-menu">
 <li name="edit"><a href="#">Edit</a></li>
+<li name="showRoles"><a href="#">Show Roles</a></li>
 </ul>
 
 <span name="edit" class="pki-actions-menu" style="display: none;">
-- 
2.4.11

