From c8e5809cc71999d2e9218451c9b10df598452889 Mon Sep 17 00:00:00 2001
From: Charlton Austin <charlton.austin@gmail.com>
Date: Thu, 13 Oct 2016 09:04:59 -0400
Subject: [PATCH] Refactoring manage users to it's own directive.

---
 static/directives/manage-users-tab.html    | 240 +++++++++++++++++++
 static/js/directives/ui/manage-user-tab.js | 230 ++++++++++++++++++
 static/js/pages/superuser.js               | 211 +----------------
 static/partials/super-user.html            | 257 ++-------------------
 4 files changed, 491 insertions(+), 447 deletions(-)
 create mode 100644 static/directives/manage-users-tab.html
 create mode 100644 static/js/directives/ui/manage-user-tab.js

diff --git a/static/directives/manage-users-tab.html b/static/directives/manage-users-tab.html
new file mode 100644
index 000000000..18d10ec84
--- /dev/null
+++ b/static/directives/manage-users-tab.html
@@ -0,0 +1,240 @@
+<div class="manage-users-tab-element">
+  <div class="cor-loader" ng-show="!users"></div>
+  <div class="alert alert-error" ng-show="usersError">
+    {{ usersError }}
+  </div>
+  <div ng-show="users">
+    <div class="manager-header" header-title="Users">
+      <button class="create-button btn btn-primary" ng-click="showCreateUser()"
+              quay-show="Config.AUTHENTICATION_TYPE == 'Database'">
+        <i class="fa fa-plus" style="margin-right: 6px;"></i>Create User
+      </button>
+      <span class="co-alert co-alert-info" quay-show="Config.AUTHENTICATION_TYPE != 'Database'">
+                Note: <span class="registry-name"></span> is configured to use external authentication, so users can only be created in that system
+              </span>
+    </div>
+
+    <div class="filter-box" collection="users" filter-model="search" filter-name="Users"></div>
+
+    <table class="cor-table">
+      <thead>
+      <td style="width: 24px;"></td>
+      <td>Username</td>
+      <td>E-mail address</td>
+      <td style="width: 24px;"></td>
+      </thead>
+
+      <tr ng-repeat="current_user in (users | filter:search | orderBy:'username')"
+          class="user-row"
+          ng-class="current_user.enabled ? 'enabled': 'disabled'">
+        <td>
+          <span class="avatar" data="current_user.avatar" size="24"></span>
+        </td>
+        <td>
+                  <span class="labels">
+                    <span class="label label-success" ng-if="user.username == current_user.username">You</span>
+                    <span class="label label-primary"
+                          ng-if="current_user.super_user">Superuser</span>
+                    <span class="label label-default"
+                          ng-if="!current_user.enabled">Disabled</span>
+                  </span>
+          {{ current_user.username }}
+        </td>
+        <td>
+          <a href="mailto:{{ current_user.email }}">{{ current_user.email }}</a>
+        </td>
+        <td style="text-align: center;">
+                  <span class="cor-options-menu"
+                        ng-if="user.username != current_user.username && !current_user.super_user">
+                    <span class="cor-option" option-click="setSuperuser(current_user, true)"
+                          quay-show="!current_user.super_user">
+                      <i class="fa">&Omega;</i>
+                      Make Superuser
+                    </span>
+                    <span class="cor-option" option-click="setSuperuser(current_user, false)"
+                          quay-show="current_user.super_user">
+                      <i class="fa">&omega;</i>
+                      Remove Superuser
+                    </span>
+
+                    <span class="cor-option" option-click="showChangeEmail(current_user)"
+                          quay-show="Config.AUTHENTICATION_TYPE == 'Database'">
+                      <i class="fa fa-envelope-o"></i> Change E-mail Address
+                    </span>
+                    <span class="cor-option" option-click="showChangePassword(current_user)"
+                          quay-show="Config.AUTHENTICATION_TYPE == 'Database'">
+                      <i class="fa fa-key"></i> Change Password
+                    </span>
+                    <span class="cor-option" option-click="sendRecoveryEmail(current_user)"
+                          quay-show="Features.MAILING && Config.AUTHENTICATION_TYPE == 'Database'">
+                      <i class="fa fa-envelope"></i> Send Recovery E-mail
+                    </span>
+                    <span class="cor-option" option-click="showDeleteUser(current_user)">
+                      <i class="fa fa-times"></i> Delete User
+                    </span>
+                    <span class="cor-option" option-click="askDisableUser(current_user)">
+                      <i class="fa" ng-class="current_user.enabled ? 'fa-circle-o' : 'fa-check-circle-o'"></i> <span
+                        ng-if="current_user.enabled">Disable</span> <span ng-if="!current_user.enabled">Enable</span> User
+                    </span>
+                    <span class="cor-option" option-click="askTakeOwnership(current_user, false)"
+                          ng-if="user.username != current_user.username && !current_user.super_user">
+                      <i class="fa fa-bolt"></i> Take Ownership
+                    </span>
+                  </span>
+        </td>
+      </tr>
+    </table>
+  </div> <!-- /show if users -->
+
+  <!-- Take ownership dialog -->
+  <div class="cor-confirm-dialog take-ownership-dialog"
+       dialog-context="takeOwnershipInfo"
+       dialog-action="takeOwnership(info, callback)"
+       dialog-title="Take Ownership"
+       dialog-action-title="Take Ownership">
+    Are you sure you want to take ownership of
+    <span ng-if="takeOwnershipInfo.is_org">organization <span class="avatar" data="takeOwnershipInfo.entity.avatar"
+                                                              size="16"></span> {{ takeOwnershipInfo.entity.name }}?</span>
+    <span ng-if="!takeOwnershipInfo.is_org">user namespace <span class="avatar" data="takeOwnershipInfo.entity.avatar"
+                                                                 size="16"></span> {{ takeOwnershipInfo .entity.username }}?</span>
+
+    <div class="co-alert co-alert-warning" ng-if="!takeOwnershipInfo.is_org">
+      Note: This will convert the user namespace into an organization. <strong>The user will no longer be able to login
+      to
+      this account.</strong>
+    </div>
+  </div>
+
+  <!-- Modal message dialog -->
+  <div class="co-dialog modal fade" id="confirmDeleteUserModal">
+    <div class="modal-dialog">
+      <div class="modal-content">
+        <div class="modal-header">
+          <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+          <h4 class="modal-title">Delete User?</h4>
+        </div>
+        <div class="modal-body">
+          <div class="alert alert-danger">
+            This operation <strong>cannot be undone</strong> and will <strong>delete any repositories owned by the
+            user</strong>.
+          </div>
+          Are you <strong>sure</strong> you want to delete user <strong>{{ userToDelete.username }}</strong>?
+        </div>
+        <div class="modal-footer">
+          <button type="button" class="btn btn-danger" ng-click="deleteUser(userToDelete)">Delete User</button>
+          <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
+        </div>
+      </div><!-- /.modal-content -->
+    </div><!-- /.modal-dialog -->
+  </div><!-- /.modal -->
+
+  <!-- Modal create user dialog -->
+  <div class="co-dialog modal fade" id="createUserModal">
+    <div class="modal-dialog">
+      <div class="modal-content">
+        <div class="modal-header">
+          <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+          <h4 class="modal-title">Create New User</h4>
+        </div>
+        <form name="createUserForm" ng-submit="createUser()">
+          <div class="modal-body" ng-show="createdUser">
+            <table class="table">
+              <thead>
+              <th>Username</th>
+              <th>E-mail address</th>
+              <th>Temporary Password</th>
+              </thead>
+
+              <tr class="user-row">
+                <td>{{ createdUser.username }}</td>
+                <td>{{ createdUser.email }}</td>
+                <td>{{ createdUser.password }}</td>
+              </tr>
+            </table>
+          </div>
+          <div class="modal-body" ng-show="creatingUser">
+            <div class="cor-loader"></div>
+          </div>
+          <div class="modal-body" ng-show="!creatingUser && !createdUser">
+            <div class="form-group">
+              <label>Username</label>
+              <input class="form-control" type="text" ng-model="newUser.username" ng-pattern="/^[a-z0-9_]{4,30}$/"
+                     required>
+            </div>
+
+            <div class="form-group">
+              <label>Email address</label>
+              <input class="form-control" type="email" ng-model="newUser.email" required>
+            </div>
+          </div>
+          <div class="modal-footer" ng-show="createdUser">
+            <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
+          </div>
+          <div class="modal-footer" ng-show="!creatingUser && !createdUser">
+            <button class="btn btn-primary" type="submit" ng-disabled="!createUserForm.$valid">
+              Create User
+            </button>
+            <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
+          </div>
+        </form>
+      </div><!-- /.modal-content -->
+    </div><!-- /.modal-dialog -->
+  </div><!-- /.modal -->
+
+
+  <!-- Modal change password dialog -->
+  <div class="co-dialog modal fade" id="changePasswordModal">
+    <div class="modal-dialog">
+      <div class="modal-content">
+        <div class="modal-header">
+          <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+          <h4 class="modal-title">Change User Password</h4>
+        </div>
+        <div class="modal-body">
+          <div class="alert alert-warning">
+            The user will no longer be able to access the registry with their current password
+          </div>
+
+          <form class="form-change" id="changePasswordForm" name="changePasswordForm" data-trigger="manual">
+            <input type="password" class="form-control" placeholder="User's new password"
+                   ng-model="userToChange.password"
+                   required ng-pattern="/^.{8,}$/">
+            <input type="password" class="form-control" placeholder="Verify the new password"
+                   ng-model="userToChange.repeatPassword"
+                   match="userToChange.password" required ng-pattern="/^.{8,}$/">
+          </form>
+        </div>
+        <div class="modal-footer">
+          <button type="button" class="btn btn-primary" ng-click="changeUserPassword(userToChange)"
+                  ng-disabled="changePasswordForm.$invalid">Change User Password
+          </button>
+          <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
+        </div>
+      </div><!-- /.modal-content -->
+    </div><!-- /.modal-dialog -->
+  </div><!-- /.modal -->
+
+  <!-- Modal change email dialog -->
+  <div class="co-dialog modal fade" id="changeEmailModal">
+    <div class="modal-dialog">
+      <div class="modal-content">
+        <div class="modal-header">
+          <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+          <h4 class="modal-title">Change User E-mail Address</h4>
+        </div>
+        <div class="modal-body">
+          <form class="form-change" id="changeEmailForm" name="changeEmailForm" data-trigger="manual">
+            <input type="email" class="form-control" placeholder="User's new email" ng-model="userToChange.newemail"
+                   required>
+          </form>
+        </div>
+        <div class="modal-footer">
+          <button type="button" class="btn btn-primary" ng-click="changeUserEmail(userToChange)"
+                  ng-disabled="changeEmailForm.$invalid">Change User E-mail
+          </button>
+          <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
+        </div>
+      </div><!-- /.modal-content -->
+    </div><!-- /.modal-dialog -->
+  </div><!-- /.modal -->
+</div>
diff --git a/static/js/directives/ui/manage-user-tab.js b/static/js/directives/ui/manage-user-tab.js
new file mode 100644
index 000000000..6193925f9
--- /dev/null
+++ b/static/js/directives/ui/manage-user-tab.js
@@ -0,0 +1,230 @@
+/**
+ * An element which displays a panel for managing users.
+ */
+angular.module('quay').directive('manageUserTab', function () {
+  var directiveDefinitionObject = {
+    priority: 0,
+    templateUrl: '/static/directives/manage-users-tab.html',
+    replace: false,
+    transclude: true,
+    restrict: 'C',
+    scope: {
+      'isEnabled': '=isEnabled'
+    },
+    controller: function ($scope, $timeout, $location, $element, ApiService, UserService) {
+
+      $scope.newUser = {};
+      $scope.createdUser = null;
+      $scope.takeOwnershipInfo = null;
+
+
+      $scope.showCreateUser = function () {
+        $scope.createdUser = null;
+        $('#createUserModal').modal('show');
+      };
+
+      var loadUsersInternal = function () {
+        ApiService.listAllUsers().then(function (resp) {
+          $scope.users = resp['users'];
+          $scope.showInterface = true;
+        }, function (resp) {
+          $scope.users = [];
+          $scope.usersError = ApiService.getErrorMessage(resp);
+        });
+      };
+
+      $scope.createUser = function () {
+        $scope.creatingUser = true;
+        $scope.createdUser = null;
+
+        var errorHandler = ApiService.errorDisplay('Cannot create user', function () {
+          $scope.creatingUser = false;
+          $('#createUserModal').modal('hide');
+        });
+
+        ApiService.createInstallUser($scope.newUser, null).then(function (resp) {
+          $scope.creatingUser = false;
+          $scope.newUser = {};
+          $scope.createdUser = resp;
+          loadUsersInternal();
+        }, errorHandler)
+      };
+
+
+      $scope.setSuperuser = function (user, status) {
+        var setSuperuser = function () {
+          var params = {
+            'username': user.username
+          };
+
+          var data = {
+            'superuser': status
+          };
+
+          ApiService.changeInstallUser(data, params).then(function (resp) {
+            $scope.requiresRestart = true;
+          }, ApiService.errorDisplay('Could not change user'));
+        };
+
+        var msg = 'Note: This change, once applied, will require your installation ' +
+          'to be restarted to take effect';
+
+        bootbox.confirm(msg, function (status) {
+          if (status) {
+            setSuperuser();
+          }
+        });
+      };
+
+      $scope.showChangeEmail = function (user) {
+        $scope.userToChange = user;
+        $('#changeEmailModal').modal({});
+      };
+
+      $scope.changeUserEmail = function (user) {
+        $('#changeEmailModal').modal('hide');
+
+        var params = {
+          'username': user.username
+        };
+
+        var data = {
+          'email': user.newemail
+        };
+
+        ApiService.changeInstallUser(data, params).then(function (resp) {
+          loadUsersInternal();
+          user.email = user.newemail;
+          delete user.newemail;
+        }, ApiService.errorDisplay('Could not change user'));
+      };
+
+      $scope.showChangePassword = function (user) {
+        $scope.userToChange = user;
+        $('#changePasswordModal').modal({});
+      };
+
+      $scope.changeUserPassword = function (user) {
+        $('#changePasswordModal').modal('hide');
+
+        var params = {
+          'username': user.username
+        };
+
+        var data = {
+          'password': user.password
+        };
+
+        ApiService.changeInstallUser(data, params).then(function (resp) {
+          loadUsersInternal();
+        }, ApiService.errorDisplay('Could not change user'));
+      };
+
+      $scope.sendRecoveryEmail = function (user) {
+        var params = {
+          'username': user.username
+        };
+
+        ApiService.sendInstallUserRecoveryEmail(null, params).then(function (resp) {
+          bootbox.dialog({
+            "message": "A recovery email has been sent to " + resp['email'],
+            "title": "Recovery email sent",
+            "buttons": {
+              "close": {
+                "label": "Close",
+                "className": "btn-primary"
+              }
+            }
+          });
+
+        }, ApiService.errorDisplay('Cannot send recovery email'))
+      };
+
+      $scope.showDeleteUser = function (user) {
+        if (user.username == UserService.currentUser().username) {
+          bootbox.dialog({
+            "message": 'Cannot delete yourself!',
+            "title": "Cannot delete user",
+            "buttons": {
+              "close": {
+                "label": "Close",
+                "className": "btn-primary"
+              }
+            }
+          });
+          return;
+        }
+
+        $scope.userToDelete = user;
+        $('#confirmDeleteUserModal').modal({});
+      };
+
+      $scope.deleteUser = function (user) {
+        $('#confirmDeleteUserModal').modal('hide');
+
+        var params = {
+          'username': user.username
+        };
+
+        ApiService.deleteInstallUser(null, params).then(function (resp) {
+          loadUsersInternal();
+        }, ApiService.errorDisplay('Cannot delete user'));
+      };
+
+      $scope.askDisableUser = function (user) {
+        var message = 'Are you sure you want to disable this user? ' +
+          'They will be unable to login, pull or push.';
+
+        if (!user.enabled) {
+          message = 'Are you sure you want to reenable this user? ' +
+            'They will be able to login, pull or push.'
+        }
+
+        bootbox.confirm(message, function (resp) {
+          if (resp) {
+            var params = {
+              'username': user.username
+            };
+
+            var data = {
+              'enabled': !user.enabled
+            };
+
+            ApiService.changeInstallUser(data, params).then(function (resp) {
+              loadUsersInternal();
+            });
+          }
+        });
+      };
+
+      $scope.askTakeOwnership = function (entity, is_org) {
+        $scope.takeOwnershipInfo = {
+          'entity': entity,
+          'is_org': is_org
+        };
+      };
+
+      $scope.takeOwnership = function (info, callback) {
+        var errorDisplay = ApiService.errorDisplay('Could not take ownership of namespace', callback);
+        var params = {
+          'namespace': info.entity.username || info.entity.name
+        };
+
+        ApiService.takeOwnership(null, params).then(function () {
+          callback(true);
+          $location.path('/organization/' + params.namespace);
+        }, errorDisplay)
+      };
+
+      $scope.$watch('isEnabled', function (value) {
+        if (value) {
+          if ($scope.users) {
+            return;
+          }
+          loadUsersInternal();
+        }
+      });
+    }
+  };
+  return directiveDefinitionObject;
+});
diff --git a/static/js/pages/superuser.js b/static/js/pages/superuser.js
index 50636337b..91bee789a 100644
--- a/static/js/pages/superuser.js
+++ b/static/js/pages/superuser.js
@@ -10,7 +10,7 @@
       })
   }]);
 
-  function SuperuserCtrl($scope, $timeout, $location, ApiService, Features, UserService, ContainerService, AngularPollChannel, CoreDialog) {
+  function SuperuserCtrl($scope, ApiService, Features, UserService, ContainerService, AngularPollChannel, CoreDialog) {
     if (!Features.SUPER_USERS) {
       return;
     }
@@ -21,8 +21,6 @@
     $scope.configStatus = null;
     $scope.requiresRestart = null;
     $scope.logsCounter = 0;
-    $scope.newUser = {};
-    $scope.createdUser = null;
     $scope.changeLog = null;
     $scope.debugServices = null;
     $scope.debugLogs = null;
@@ -32,7 +30,7 @@
     $scope.currentConfig = null;
     $scope.serviceKeysActive = false;
     $scope.globalMessagesActive = false;
-    $scope.takeOwnershipInfo = null;
+    $scope.manageUsersActive = false;
 
     $scope.loadMessageOfTheDay = function () {
       $scope.globalMessagesActive = true;
@@ -42,11 +40,6 @@
       $scope.requiresRestart = true;
     };
 
-    $scope.showCreateUser = function() {
-      $scope.createdUser = null;
-      $('#createUserModal').modal('show');
-    };
-
     $scope.loadServiceKeys = function() {
       $scope.serviceKeysActive = true;
     };
@@ -121,110 +114,7 @@
     };
 
     $scope.loadUsers = function() {
-      if ($scope.users) {
-        return;
-      }
-
-      $scope.loadUsersInternal();
-    };
-
-    $scope.loadUsersInternal = function() {
-      ApiService.listAllUsers().then(function(resp) {
-        $scope.users = resp['users'];
-        $scope.showInterface = true;
-      }, function(resp) {
-        $scope.users = [];
-        $scope.usersError = ApiService.getErrorMessage(resp);
-      });
-    };
-
-    $scope.showChangePassword = function(user) {
-      $scope.userToChange = user;
-      $('#changePasswordModal').modal({});
-    };
-
-    $scope.showChangeEmail = function(user) {
-      $scope.userToChange = user;
-      $('#changeEmailModal').modal({});
-    };
-
-    $scope.createUser = function() {
-      $scope.creatingUser = true;
-      $scope.createdUser = null;
-
-      var errorHandler = ApiService.errorDisplay('Cannot create user', function() {
-        $scope.creatingUser = false;
-        $('#createUserModal').modal('hide');
-      });
-
-      ApiService.createInstallUser($scope.newUser, null).then(function(resp) {
-        $scope.creatingUser = false;
-        $scope.newUser = {};
-        $scope.createdUser = resp;
-        $scope.loadUsersInternal();
-      }, errorHandler)
-    };
-
-    $scope.setSuperuser = function(user, status) {
-      var setSuperuser = function() {
-        var params = {
-          'username': user.username
-        };
-
-        var data = {
-          'superuser': status
-        };
-
-        ApiService.changeInstallUser(data, params).then(function(resp) {
-          $scope.requiresRestart = true;
-        }, ApiService.errorDisplay('Could not change user'));
-      };
-
-      var msg = 'Note: This change, once applied, will require your installation ' +
-                'to be restarted to take effect';
-
-      bootbox.confirm(msg, function(status) {
-        if (status) {
-          setSuperuser();
-        }
-      });
-    };
-
-    $scope.showDeleteUser = function(user) {
-      if (user.username == UserService.currentUser().username) {
-        bootbox.dialog({
-          "message": 'Cannot delete yourself!',
-          "title": "Cannot delete user",
-          "buttons": {
-            "close": {
-              "label": "Close",
-              "className": "btn-primary"
-            }
-          }
-        });
-        return;
-      }
-
-      $scope.userToDelete = user;
-      $('#confirmDeleteUserModal').modal({});
-    };
-
-    $scope.changeUserEmail = function(user) {
-      $('#changeEmailModal').modal('hide');
-
-      var params = {
-        'username': user.username
-      };
-
-      var data = {
-        'email': user.newemail
-      };
-
-      ApiService.changeInstallUser(data, params).then(function(resp) {
-        $scope.loadUsersInternal();
-        user.email = user.newemail;
-        delete user.newemail;
-      }, ApiService.errorDisplay('Could not change user'));
+      $scope.manageUsersActive = true;
     };
 
     $scope.askDeleteOrganization = function(org) {
@@ -261,99 +151,6 @@
       });
     };
 
-    $scope.askTakeOwnership = function(entity, is_org) {
-      $scope.takeOwnershipInfo = {
-        'entity': entity,
-        'is_org': is_org
-      };
-    };
-
-    $scope.takeOwnership = function(info, callback) {
-      var errorDisplay = ApiService.errorDisplay('Could not take ownership of namespace', callback);
-      var params = {
-        'namespace': info.entity.username || info.entity.name
-      };
-
-      ApiService.takeOwnership(null, params).then(function() {
-        callback(true);
-        $location.path('/organization/' + params.namespace);
-      }, errorDisplay)
-    };
-
-    $scope.askDisableUser = function(user) {
-      var message = 'Are you sure you want to disable this user? ' +
-                    'They will be unable to login, pull or push.'
-
-      if (!user.enabled) {
-        message = 'Are you sure you want to reenable this user? ' +
-                  'They will be able to login, pull or push.'
-      }
-
-      bootbox.confirm(message, function(resp) {
-        if (resp) {
-          var params = {
-            'username': user.username
-          };
-
-          var data = {
-            'enabled': !user.enabled
-          };
-
-          ApiService.changeInstallUser(data, params).then(function(resp) {
-            $scope.loadUsersInternal();
-          });
-        }
-      });
-    };
-
-    $scope.changeUserPassword = function(user) {
-      $('#changePasswordModal').modal('hide');
-
-      var params = {
-        'username': user.username
-      };
-
-      var data = {
-        'password': user.password
-      };
-
-      ApiService.changeInstallUser(data, params).then(function(resp) {
-        $scope.loadUsersInternal();
-      }, ApiService.errorDisplay('Could not change user'));
-    };
-
-    $scope.deleteUser = function(user) {
-      $('#confirmDeleteUserModal').modal('hide');
-
-      var params = {
-        'username': user.username
-      };
-
-      ApiService.deleteInstallUser(null, params).then(function(resp) {
-        $scope.loadUsersInternal();
-      }, ApiService.errorDisplay('Cannot delete user'));
-    };
-
-    $scope.sendRecoveryEmail = function(user) {
-      var params = {
-        'username': user.username
-      };
-
-      ApiService.sendInstallUserRecoveryEmail(null, params).then(function(resp) {
-        bootbox.dialog({
-          "message": "A recovery email has been sent to " + resp['email'],
-          "title": "Recovery email sent",
-          "buttons": {
-            "close": {
-              "label": "Close",
-              "className": "btn-primary"
-            }
-          }
-        });
-
-      }, ApiService.errorDisplay('Cannot send recovery email'))
-    };
-
     $scope.restartContainer = function() {
       $('#restartingContainerModal').modal({
         keyboard: false,
@@ -378,7 +175,7 @@
           var message = "Installation of this product has not yet been completed." +
                         "<br><br>Please read the " +
                         "<a href='https://coreos.com/docs/enterprise-registry/initial-setup/'>" +
-                        "Setup Guide</a>"
+                        "Setup Guide</a>";
 
           var title = "Installation Incomplete";
           CoreDialog.fatal(title, message);
diff --git a/static/partials/super-user.html b/static/partials/super-user.html
index 3b8892468..4e9eaa943 100644
--- a/static/partials/super-user.html
+++ b/static/partials/super-user.html
@@ -34,11 +34,12 @@
         <span class="cor-tab" tab-title="Usage Logs" tab-target="#logs" tab-init="loadUsageLogs()">
           <i class="fa fa-bar-chart"></i>
         </span>
-        <span class="cor-tab" tab-title="Internal Logs and Debugging" tab-target="#debug" tab-init="loadDebugServices()">
+        <span class="cor-tab" tab-title="Internal Logs and Debugging" tab-target="#debug"
+              tab-init="loadDebugServices()">
           <i class="fa fa-bug"></i>
         </span>
         <span class="cor-tab hidden-xs" tab-title="Registry Settings" tab-target="#setup"
-                              tab-init="loadConfig()">
+              tab-init="loadConfig()">
           <i class="fa fa-cog"></i>
         </span>
         <span class="cor-tab hidden-xs" tab-title="Globally visible user messages" tab-target="#message-of-the-day"
@@ -76,18 +77,18 @@
                   ng-class="debugService == service ? 'active' : ''">
                 <a ng-click="viewSystemLogs(service)">{{ service }}</a>
               </li>
-           </ul>
+            </ul>
 
-           <div class="system-log-download-panel" ng-if="!debugService">
-            Select a service above to view its local logs
+            <div class="system-log-download-panel" ng-if="!debugService">
+              Select a service above to view its local logs
 
-            <div>
-              <a class="btn btn-primary" href="/systemlogsarchive?_csrf_token={{ csrf_token }}" ng-safenewtab>
-                <i class="fa fa-download fa-lg" style="margin-right: 4px;"></i> Download All Local Logs (.tar.gz)
-              </a>
+              <div>
+                <a class="btn btn-primary" href="/systemlogsarchive?_csrf_token={{ csrf_token }}" ng-safenewtab>
+                  <i class="fa fa-download fa-lg" style="margin-right: 4px;"></i> Download All Local Logs (.tar.gz)
+                </a>
+              </div>
             </div>
-           </div>
-           <div class="cor-log-box" logs="debugLogs" ng-show="debugService"></div>
+            <div class="cor-log-box" logs="debugLogs" ng-show="debugService"></div>
           </div>
         </div>
 
@@ -114,10 +115,10 @@
 
             <table class="cor-table">
               <thead>
-                <td style="width: 24px;"></td>
-                <td>Name</td>
-                <td>Admin E-mail</td>
-                <td style="width: 24px;"></td>
+              <td style="width: 24px;"></td>
+              <td>Name</td>
+              <td>Admin E-mail</td>
+              <td style="width: 24px;"></td>
               </thead>
 
               <tr ng-repeat="current_org in (organizations | filter:search | orderBy:'name')"
@@ -151,187 +152,12 @@
 
         <!-- Users tab -->
         <div id="users" class="tab-pane active">
-          <div class="cor-loader" ng-show="!users"></div>
-          <div class="alert alert-error" ng-show="usersError">
-            {{ usersError }}
-          </div>
-          <div ng-show="users">
-            <div class="manager-header" header-title="Users">
-              <button class="create-button btn btn-primary" ng-click="showCreateUser()"
-                      quay-show="Config.AUTHENTICATION_TYPE == 'Database'">
-                <i class="fa fa-plus" style="margin-right: 6px;"></i>Create User
-              </button>
-              <span class="co-alert co-alert-info" quay-show="Config.AUTHENTICATION_TYPE != 'Database'">
-                Note: <span class="registry-name"></span> is configured to use external authentication, so users can only be created in that system
-              </span>
-            </div>
-
-            <div class="filter-box" collection="users" filter-model="search" filter-name="Users"></div>
-
-            <table class="cor-table">
-              <thead>
-                <td style="width: 24px;"></td>
-                <td>Username</td>
-                <td>E-mail address</td>
-                <td style="width: 24px;"></td>
-              </thead>
-
-              <tr ng-repeat="current_user in (users | filter:search | orderBy:'username')"
-                  class="user-row"
-                  ng-class="current_user.enabled ? 'enabled': 'disabled'">
-                <td>
-                  <span class="avatar" data="current_user.avatar" size="24"></span>
-                </td>
-                <td>
-                  <span class="labels">
-                    <span class="label label-success" ng-if="user.username == current_user.username">You</span>
-                    <span class="label label-primary"
-                          ng-if="current_user.super_user">Superuser</span>
-                    <span class="label label-default"
-                          ng-if="!current_user.enabled">Disabled</span>
-                  </span>
-                  {{ current_user.username }}
-                </td>
-                <td>
-                  <a href="mailto:{{ current_user.email }}">{{ current_user.email }}</a>
-                </td>
-                <td style="text-align: center;">
-                  <span class="cor-options-menu"
-                        ng-if="user.username != current_user.username && !current_user.super_user">
-                    <span class="cor-option" option-click="setSuperuser(current_user, true)"
-                          quay-show="!current_user.super_user">
-                      <i class="fa">&Omega;</i>
-                      Make Superuser
-                    </span>
-                    <span class="cor-option" option-click="setSuperuser(current_user, false)"
-                          quay-show="current_user.super_user">
-                      <i class="fa">&omega;</i>
-                      Remove Superuser
-                    </span>
-
-                    <span class="cor-option" option-click="showChangeEmail(current_user)"
-                          quay-show="Config.AUTHENTICATION_TYPE == 'Database'">
-                      <i class="fa fa-envelope-o"></i> Change E-mail Address
-                    </span>
-                    <span class="cor-option" option-click="showChangePassword(current_user)"
-                          quay-show="Config.AUTHENTICATION_TYPE == 'Database'">
-                      <i class="fa fa-key"></i> Change Password
-                    </span>
-                    <span class="cor-option" option-click="sendRecoveryEmail(current_user)"
-                          quay-show="Features.MAILING && Config.AUTHENTICATION_TYPE == 'Database'">
-                      <i class="fa fa-envelope"></i> Send Recovery E-mail
-                    </span>
-                    <span class="cor-option" option-click="showDeleteUser(current_user)">
-                      <i class="fa fa-times"></i> Delete User
-                    </span>
-                    <span class="cor-option" option-click="askDisableUser(current_user)">
-                      <i class="fa" ng-class="current_user.enabled ? 'fa-circle-o' : 'fa-check-circle-o'"></i> <span ng-if="current_user.enabled">Disable</span> <span ng-if="!current_user.enabled">Enable</span> User
-                    </span>
-                    <span class="cor-option" option-click="askTakeOwnership(current_user, false)"
-                          ng-if="user.username != current_user.username && !current_user.super_user">
-                      <i class="fa fa-bolt"></i> Take Ownership
-                    </span>
-                  </span>
-                </td>
-              </tr>
-            </table>
-          </div> <!-- /show if users -->
+          <div class="manage-user-tab" is-enabled="manageUsersActive"></div>
         </div> <!-- users-tab -->
 
       </div> <!-- /cor-tab-content -->
     </div> <!-- /cor-tab-panel -->
 
-    <!-- Take ownership dialog -->
-    <div class="cor-confirm-dialog take-ownership-dialog"
-       dialog-context="takeOwnershipInfo"
-       dialog-action="takeOwnership(info, callback)"
-       dialog-title="Take Ownership"
-       dialog-action-title="Take Ownership">
-       Are you sure you want to take ownership of
-       <span ng-if="takeOwnershipInfo.is_org">organization <span class="avatar" data="takeOwnershipInfo.entity.avatar" size="16"></span> {{ takeOwnershipInfo.entity.name }}?</span>
-       <span ng-if="!takeOwnershipInfo.is_org">user namespace <span class="avatar" data="takeOwnershipInfo.entity.avatar" size="16"></span> {{ takeOwnershipInfo .entity.username }}?</span>
-
-       <div class="co-alert co-alert-warning" ng-if="!takeOwnershipInfo.is_org">
-         Note: This will convert the user namespace into an organization. <strong>The user will no longer be able to login to this account.</strong>
-       </div>
-    </div>
-
-    <!-- Modal message dialog -->
-    <div class="co-dialog modal fade" id="confirmDeleteUserModal">
-      <div class="modal-dialog">
-        <div class="modal-content">
-          <div class="modal-header">
-            <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
-            <h4 class="modal-title">Delete User?</h4>
-          </div>
-          <div class="modal-body">
-            <div class="alert alert-danger">
-              This operation <strong>cannot be undone</strong> and will <strong>delete any repositories owned by the user</strong>.
-            </div>
-            Are you <strong>sure</strong> you want to delete user <strong>{{ userToDelete.username }}</strong>?
-          </div>
-          <div class="modal-footer">
-            <button type="button" class="btn btn-danger" ng-click="deleteUser(userToDelete)">Delete User</button>
-            <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
-          </div>
-        </div><!-- /.modal-content -->
-      </div><!-- /.modal-dialog -->
-    </div><!-- /.modal -->
-
-
-    <!-- Modal create user dialog -->
-    <div class="co-dialog modal fade" id="createUserModal">
-      <div class="modal-dialog">
-        <div class="modal-content">
-          <div class="modal-header">
-            <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
-            <h4 class="modal-title">Create New User</h4>
-          </div>
-          <form name="createUserForm" ng-submit="createUser()">
-            <div class="modal-body" ng-show="createdUser">
-              <table class="table">
-                <thead>
-                  <th>Username</th>
-                  <th>E-mail address</th>
-                  <th>Temporary Password</th>
-                </thead>
-
-                <tr class="user-row">
-                    <td>{{ createdUser.username }}</td>
-                    <td>{{ createdUser.email }}</td>
-                    <td>{{ createdUser.password }}</td>
-                </tr>
-              </table>
-            </div>
-            <div class="modal-body" ng-show="creatingUser">
-              <div class="cor-loader"></div>
-            </div>
-            <div class="modal-body" ng-show="!creatingUser && !createdUser">
-                <div class="form-group">
-                  <label>Username</label>
-                  <input class="form-control" type="text" ng-model="newUser.username" ng-pattern="/^[a-z0-9_]{4,30}$/" required>
-                </div>
-
-                <div class="form-group">
-                  <label>Email address</label>
-                  <input class="form-control" type="email" ng-model="newUser.email" required>
-                </div>
-            </div>
-            <div class="modal-footer" ng-show="createdUser">
-              <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
-            </div>
-            <div class="modal-footer" ng-show="!creatingUser && !createdUser">
-              <button class="btn btn-primary" type="submit" ng-disabled="!createUserForm.$valid">
-                Create User
-              </button>
-              <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
-            </div>
-          </form>
-        </div><!-- /.modal-content -->
-      </div><!-- /.modal-dialog -->
-    </div><!-- /.modal -->
-
-
     <!-- Modal message dialog -->
     <div class="co-dialog modal fade" id="restartingContainerModal">
       <div class="modal-dialog">
@@ -353,55 +179,6 @@
       </div><!-- /.modal-dialog -->
     </div><!-- /.modal -->
 
-    <!-- Modal message dialog -->
-    <div class="co-dialog modal fade" id="changePasswordModal">
-      <div class="modal-dialog">
-        <div class="modal-content">
-          <div class="modal-header">
-            <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
-            <h4 class="modal-title">Change User Password</h4>
-          </div>
-          <div class="modal-body">
-            <div class="alert alert-warning">
-              The user will no longer be able to access the registry with their current password
-            </div>
-
-            <form class="form-change" id="changePasswordForm" name="changePasswordForm" data-trigger="manual">
-              <input type="password" class="form-control" placeholder="User's new password" ng-model="userToChange.password" required ng-pattern="/^.{8,}$/">
-              <input type="password" class="form-control" placeholder="Verify the new password" ng-model="userToChange.repeatPassword"
-                     match="userToChange.password" required ng-pattern="/^.{8,}$/">
-            </form>
-          </div>
-          <div class="modal-footer">
-            <button type="button" class="btn btn-primary" ng-click="changeUserPassword(userToChange)"
-                    ng-disabled="changePasswordForm.$invalid">Change User Password</button>
-            <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
-          </div>
-        </div><!-- /.modal-content -->
-      </div><!-- /.modal-dialog -->
-    </div><!-- /.modal -->
-
-    <!-- Modal message dialog -->
-    <div class="co-dialog modal fade" id="changeEmailModal">
-      <div class="modal-dialog">
-        <div class="modal-content">
-          <div class="modal-header">
-            <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
-            <h4 class="modal-title">Change User E-mail Address</h4>
-          </div>
-          <div class="modal-body">
-            <form class="form-change" id="changeEmailForm" name="changeEmailForm" data-trigger="manual">
-              <input type="email" class="form-control" placeholder="User's new email" ng-model="userToChange.newemail" required>
-            </form>
-          </div>
-          <div class="modal-footer">
-            <button type="button" class="btn btn-primary" ng-click="changeUserEmail(userToChange)"
-                    ng-disabled="changeEmailForm.$invalid">Change User E-mail</button>
-            <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
-          </div>
-        </div><!-- /.modal-content -->
-      </div><!-- /.modal-dialog -->
-    </div><!-- /.modal -->
 
   </div> <!-- /page-content -->
 </div>