You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@usergrid.apache.org by sn...@apache.org on 2014/01/28 00:21:58 UTC

[40/61] [abbrv] [partial] updated to latest Angular-based admin portal

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/global/ug-service.js
----------------------------------------------------------------------
diff --git a/portal/js/global/ug-service.js b/portal/js/global/ug-service.js
new file mode 100644
index 0000000..ba9d762
--- /dev/null
+++ b/portal/js/global/ug-service.js
@@ -0,0 +1,987 @@
+'use strict';
+
+AppServices.Services.factory('ug', function (configuration, $rootScope,utility) {
+
+  return {
+    get:function(prop,isObject){
+      return isObject ? this.client().getObject(prop) : this.client().get(prop);
+    },
+    set:function(prop,value){
+      this.client().set(prop,value);
+
+    },
+    orgLogin:function(username,password){
+      var self = this;
+      this.client().set('email', username);
+      this.client().set('token', null);
+      this.client().orgLogin(username,password,function(err, data, user, organizations, applications){
+        if(err){
+          $rootScope.$broadcast('loginFailed', err,data);
+        }else{
+          self.initializeCurrentUser(function () {
+            $rootScope.$broadcast('loginSuccesful', user, organizations, applications);
+          });
+        }
+      });
+    },
+
+    checkAuthentication:function(force){
+      var ug = this;
+      var client = ug.client();
+
+      var initialize = function () {
+            ug.initializeCurrentUser(function () {
+              $rootScope.userEmail = client.get('email');
+              $rootScope.organizations = client.getObject('organizations');
+              $rootScope.applications = client.getObject('applications');
+              $rootScope.currentOrg = client.get('orgName');
+              $rootScope.currentApp = client.get('appName');
+              var size = 0, key;
+              for (key in  $rootScope.applications) {
+                if ($rootScope.applications.hasOwnProperty(key)) size++;
+              }
+              $rootScope.addApplications = size < 10;
+              $rootScope.$broadcast('checkAuthentication-success', client.getObject('organizations'), client.getObject('applications'), client.get('orgName'), client.get('appName'), client.get('email'));
+            });
+          },
+          isAuthenticated = function () {
+            var authenticated = client.get('token') !== null && client.get('organizations') !== null;
+            if (authenticated) {
+              initialize();
+            }
+            return authenticated;
+          };
+      if(!isAuthenticated() || force){
+        if(!client.get('token')){
+          return $rootScope.$broadcast('checkAuthentication-error','no token',{},client.get('email'));
+        }
+        this.client().reAuthenticateLite(function(err){
+          var missingData = err || ( !client.get('orgName') || !client.get('appName') || !client.getObject('organizations') || !client.getObject('applications'));
+          var email  = client.get('email');
+          if(err || missingData){
+            $rootScope.$broadcast('checkAuthentication-error',err,missingData,email);
+          }else{
+            initialize();
+          }
+        });
+      }
+    },
+    reAuthenticate:function(email,eventOveride){
+      var ug = this;
+      this.client().reAuthenticate(email,function(err, data, user, organizations, applications){
+        if(!err){
+          $rootScope.currentUser = user;
+        }
+        if(!err){
+          $rootScope.userEmail = user.get('email');
+          $rootScope.organizations = organizations;
+          $rootScope.applications = applications;
+          $rootScope.currentOrg = ug.get('orgName');
+          $rootScope.currentApp = ug.get('appName');
+          $rootScope.currentUser = user._data;
+          $rootScope.currentUser.profileImg = utility.get_gravatar($rootScope.currentUser.email);
+        }
+        $rootScope.$broadcast((eventOveride || 'reAuthenticate')+'-' + (err ? 'error' : 'success'),err, data, user, organizations, applications);
+
+      });
+    },
+    logoutCallback: function() {
+      $rootScope.$broadcast('userNotAuthenticated');
+    },
+    logout:function(){
+      $rootScope.activeUI = false;
+      $rootScope.userEmail = 'user@apigee.com';
+      $rootScope.organizations = {"noOrg":{name:"No Orgs Found"}};
+      $rootScope.applications = {"noApp":{name:"No Apps Found"}};
+      $rootScope.currentOrg = 'No Org Found';
+      $rootScope.currentApp = 'No App Found';
+      sessionStorage.setItem('accessToken', null);
+      sessionStorage.setItem('userUUID', null);
+      sessionStorage.setItem('userEmail', null);
+
+      this.client().logout();
+      this._client = null;
+    },
+    client: function(){
+      var options = {
+        buildCurl:true,
+        logging:true
+      };
+      if(Usergrid.options && Usergrid.options.client){
+        options.keys = Usergrid.options.client;
+      }
+
+      this._client = this._client || new Usergrid.Client(options,
+          $rootScope.urls().DATA_URL
+      );
+      return this._client;
+    },
+    getTopCollections: function () {
+      var options = {
+        method:'GET',
+        endpoint: ''
+      }
+      this.client().request(options, function (err, data) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error getting collections');
+        } else {
+          var collections = data.entities[0].metadata.collections;
+          $rootScope.$broadcast('top-collections-received', collections);
+        }
+      });
+    },
+    createCollection: function (collectionName) {
+      var collections = {};
+      collections[collectionName] = {};
+      var metadata = {
+        metadata: {
+          collections: collections
+        }
+      }
+      var options = {
+        method:'PUT',
+        body: metadata,
+        endpoint: ''
+      }
+      this.client().request(options, function (err, data) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error creating collection');
+        } else {
+          $rootScope.$broadcast('collection-created', collections);
+        }
+      });
+    },
+    getApplications: function () {
+      this.client().getApplications(function (err, applications) {
+        if (err) {
+           applications && console.error(applications);
+        }else{
+          $rootScope.$broadcast('applications-received', applications);
+        }
+      });
+    },
+    getAdministrators: function () {
+      this.client().getAdministrators(function (err, administrators) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error getting administrators');
+        }
+        $rootScope.$broadcast('administrators-received', administrators);
+      });
+    },
+    createApplication: function (appName) {
+      this.client().createApplication(appName, function (err, applications) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error creating application');
+        }else{
+          $rootScope.$broadcast('applications-created', applications,appName);
+          $rootScope.$broadcast('applications-received', applications);
+        }
+      });
+    },
+    createAdministrator: function (adminName) {
+      this.client().createAdministrator(adminName, function (err, administrators) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error creating administrator');
+        }
+        $rootScope.$broadcast('administrators-received', administrators);
+      });
+    },
+    getFeed: function () {
+      var options = {
+        method:'GET',
+        endpoint:'management/organizations/'+this.client().get('orgName')+'/feed',
+        mQuery:true
+      };
+      this.client().request(options, function (err, data) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error getting feed');
+        } else {
+          var feedData = data.entities;
+          var feed = [];
+          var i=0;
+          for (i=0; i < feedData.length; i++) {
+            var date = (new Date(feedData[i].created)).toUTCString();
+
+            var title = feedData[i].title;
+
+            var n=title.indexOf(">");
+            title = title.substring(n+1,title.length);
+
+            n=title.indexOf(">");
+            title = title.substring(n+1,title.length);
+
+            if (feedData[i].actor) {
+              title = feedData[i].actor.displayName + ' ' + title;
+            }
+            feed.push({date:date, title:title});
+          }
+          if (i === 0) {
+            feed.push({date:"", title:"No Activities found."});
+          }
+
+          $rootScope.$broadcast('feed-received', feed);
+        }
+      });
+
+    },
+    createGroup: function (path, title) {
+      var options = {
+        path:path,
+        title:title
+      }
+      var self = this;
+      this.groupsCollection.addEntity(options, function(err){
+        if (err) {
+          $rootScope.$broadcast('groups-create-error', err);
+        } else {
+          $rootScope.$broadcast('groups-create-success', self.groupsCollection);
+          $rootScope.$broadcast('groups-received', self.groupsCollection);
+        }
+      });
+    },
+    createRole: function (name, title) {
+      var options = {
+        name:name,
+        title:title
+          },
+          self = this;
+      this.rolesCollection.addEntity(options, function(err){
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error creating role');
+        } else {
+          $rootScope.$broadcast('roles-received', self.rolesCollection);
+        }
+      });
+    },
+    createUser: function (username, name, email, password){
+      var options = {
+        username:username,
+        name:name,
+        email:email,
+        password:password
+      }
+      var self = this;
+      this.usersCollection.addEntity(options, function(err, data){
+        if (err) {
+          if (data) {
+            $rootScope.$broadcast("alert", "error", "error: " + data);
+          } else {
+            $rootScope.$broadcast("alert", "error", "error creating user");
+          }
+        } else {
+          $rootScope.$broadcast('users-create-success', self.usersCollection);
+
+          $rootScope.$broadcast('users-received', self.usersCollection);
+        }
+      });
+    },
+    getCollection: function (type, path, orderBy, query, limit) {
+      var options = {
+        type:path,
+        qs:{}
+      }
+      if (query) {
+        options.qs['ql'] = query;
+      }
+
+      //force order by 'created desc' if none exists
+      if (options.qs.ql) {
+        options.qs['ql'] = options.qs.ql + ' order by ' + (orderBy || 'created desc');
+      } else {
+        options.qs['ql'] = ' order by ' + (orderBy || 'created desc');
+      }
+
+      if (limit) {
+        options.qs['limit'] = limit;
+      }
+      this.client().createCollection(options, function (err, collection, data) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error getting ' + collection._type + ': ' + data.error_description);
+        } else {
+          $rootScope.$broadcast(type + '-received', collection);
+        }
+        //temporarily adding scope.apply to get working in prod, otherwise the events won't get broadcast
+        //todo - we need an apply strategy for 3rd party ug calls!
+        if(!$rootScope.$$phase) {
+          $rootScope.$apply();
+        }
+      });
+    },
+    runDataQuery: function (queryPath, searchString, queryLimit) {
+      this.getCollection('query', queryPath, null, searchString, queryLimit);
+    },
+    runDataPOSTQuery: function(queryPath, body) {
+      var self = this;
+      var options = {
+        method:'POST',
+        endpoint:queryPath,
+        body:body
+      };
+      this.client().request(options, function (err, data) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error: ' + data.error_description);
+          $rootScope.$broadcast('error-running-query',  data);
+        } else {
+
+          var queryPath = data.path;
+          self.getCollection('query', queryPath, null, 'order by modified DESC', null);
+
+        }
+      });
+    },
+    runDataPutQuery: function(queryPath, searchString, queryLimit, body) {
+      var self = this;
+      var options = {
+        method:'PUT',
+        endpoint:queryPath,
+        body:body
+      };
+
+      if (searchString) {
+        options.qs['ql'] = searchString;
+      }
+      if (queryLimit) {
+        options.qs['queryLimit'] = queryLimit;
+      }
+
+      this.client().request(options, function (err, data) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error: ' + data.error_description);
+        } else {
+
+          var queryPath = data.path;
+          self.getCollection('query', queryPath, null, 'order by modified DESC', null);
+
+        }
+      });
+    },
+    runDataDeleteQuery: function(queryPath, searchString, queryLimit) {
+      var self = this;
+      var options = {
+        method:'DELETE',
+        endpoint:queryPath
+      };
+
+      if (searchString) {
+        options.qs['ql'] = searchString;
+      }
+      if (queryLimit) {
+        options.qs['queryLimit'] = queryLimit;
+      }
+
+      this.client().request(options, function (err, data) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error: ' + data.error_description);
+        } else {
+
+          var queryPath = data.path;
+          self.getCollection('query', queryPath, null, 'order by modified DESC', null);
+
+        }
+      });
+    },
+    getUsers: function () {
+      this.getCollection('users','users','username');
+      var self = this;
+      $rootScope.$on("users-received",function(evt, users){
+        self.usersCollection = users;
+      })
+    },
+    getGroups: function () {
+      this.getCollection('groups','groups','title');
+      var self = this;
+      $rootScope.$on('groups-received', function(event, roles) {
+        self.groupsCollection = roles;
+      });
+
+     },
+    getRoles: function () {
+      this.getCollection('roles','roles','name');
+      var self = this;
+      $rootScope.$on('roles-received', function(event, roles) {
+        self.rolesCollection = roles
+      });
+    },
+    getNotifiers: function () {
+      var query = '',
+          limit = '100',
+          self = this;
+      this.getCollection('notifiers','notifiers','created', query, limit);
+      $rootScope.$on('notifiers-received', function(event, notifiers) {
+        self.notifiersCollection = notifiers;
+      });
+    },
+    getNotificationHistory: function (type) {
+      var query = null;
+      if (type) {
+        query = "select * where state = '" + type + "'";
+      }
+      this.getCollection('notifications','notifications', 'created desc', query);
+      var self = this;
+      $rootScope.$on('notifications-received', function(event, notifications) {
+        self.notificationCollection = notifications;
+      });
+    },
+    getNotificationReceipts: function (uuid) {
+      this.getCollection('receipts', 'notifications/'+uuid+'/receipts');
+      var self = this;
+      $rootScope.$on('receipts-received', function(event, receipts) {
+        self.receiptsCollection = receipts;
+      });
+    },
+    getIndexes: function (path) {
+      var options = {
+        method:'GET',
+        endpoint: path + '/indexes'
+      }
+      this.client().request(options, function (err, data) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'Problem getting indexes: ' + data.error);
+        } else {
+          $rootScope.$broadcast('indexes-received', data.data);
+        }
+      });
+    },
+    sendNotification: function(path, body) {
+      var options = {
+        method:'POST',
+        endpoint: path,
+        body:body
+      }
+      this.client().request(options, function (err, data) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'Problem creating notification: ' + data.error);
+        } else {
+          $rootScope.$broadcast('send-notification-complete');
+        }
+      });
+    },
+    getRolesUsers: function (username) {
+      var self = this;
+      var options = {
+        type:'roles/users/'+username,
+        qs:{ql:'order by username'}
+      }
+      this.client().createCollection(options, function (err, users) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error getting users');
+        } else {
+          $rootScope.$broadcast('users-received', users);
+
+        }
+      });
+    },
+    getTypeAheadData: function (type, searchString, searchBy, orderBy) {
+
+      var self = this;
+      var search = '';
+      var qs = {limit: 100};
+      if (searchString) {
+        search = "select * where "+searchBy+" = '"+searchString+"'";
+      }
+      if (orderBy) {
+        search = search + " order by "+orderBy;
+      }
+      if (search) {
+        qs.ql = search;
+      }
+      var options = {
+        method:'GET',
+        endpoint: type,
+        qs:qs
+      }
+      this.client().request(options, function (err, data) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error getting '+type);
+        } else {
+          var entities = data.entities;
+          $rootScope.$broadcast(type +'-typeahead-received', entities);
+        }
+      });
+    },
+    getUsersTypeAhead: function (searchString) {
+      this.getTypeAheadData('users', searchString, 'username', 'username');
+    },
+    getGroupsTypeAhead: function (searchString) {
+      this.getTypeAheadData('groups', searchString, 'path', 'path');
+    },
+    getRolesTypeAhead: function (searchString) {
+      this.getTypeAheadData('roles', searchString, 'name', 'name');
+    },
+    getGroupsForUser: function (user) {
+      var self = this;
+      var options = {
+        type:'users/'+user+'/groups'
+      }
+      this.client().createCollection(options, function (err, groups) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error getting groups');
+        } else {
+
+          $rootScope.$broadcast('user-groups-received', groups);
+
+        }
+      });
+    },
+    addUserToGroup: function (user, group) {
+      var self = this;
+      var options = {
+        type:'users/'+user+'/groups/'+group
+      }
+      this.client().createEntity(options, function (err, entity) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error adding user to group');
+        } else {
+          $rootScope.$broadcast('user-added-to-group-received');
+        }
+      });
+    },
+    addUserToRole: function (user, role) {
+      var options = {
+        method:'POST',
+        endpoint:'roles/'+role+'/users/'+user
+      };
+      this.client().request(options, function (err, data) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error adding user to role');
+        } else {
+          $rootScope.$broadcast('role-update-received');
+        }
+      });
+    },
+    addGroupToRole: function (group, role) {
+      var options = {
+        method:'POST',
+        endpoint:'roles/'+role+'/groups/'+group
+      };
+      this.client().request(options, function (err, data) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error adding group to role');
+        } else {
+          $rootScope.$broadcast('role-update-received');
+        }
+      });
+    },
+    followUser: function (user) {
+      var self = this;
+      var username =  $rootScope.selectedUser.get('uuid');
+      var options = {
+        method:'POST',
+        endpoint:'users/'+username+'/following/users/'+user
+      };
+      this.client().request(options, function (err, data) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error following user');
+        } else {
+          $rootScope.$broadcast('follow-user-received');
+        }
+      });
+    },
+    newPermission: function (permission, type, entity) { //"get,post,put:/mypermission"
+      var options = {
+        method:'POST',
+        endpoint:type+'/'+entity+'/permissions',
+        body:{"permission":permission}
+      };
+      this.client().request(options, function (err, data) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error adding permission');
+        } else {
+          $rootScope.$broadcast('permission-update-received');
+        }
+      });
+    },
+    newUserPermission: function (permission, username) {
+      this.newPermission(permission,'users',username)
+    },
+    newGroupPermission: function (permission, path) {
+      this.newPermission(permission,'groups',path)
+    },
+    newRolePermission: function (permission, name) {
+      this.newPermission(permission,'roles',name)
+    },
+
+    deletePermission: function (permission, type, entity) { //"get,post,put:/mypermission"
+      var options = {
+        method:'DELETE',
+        endpoint:type+'/'+entity+'/permissions',
+        qs:{permission:permission}
+      };
+      this.client().request(options, function (err, data) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error deleting permission');
+        } else {
+          $rootScope.$broadcast('permission-update-received');
+        }
+      });
+    },
+    deleteUserPermission: function (permission, user) {
+      this.deletePermission(permission,'users',user);
+    },
+    deleteGroupPermission: function (permission, group) {
+      this.deletePermission(permission,'groups',group);
+    },
+    deleteRolePermission: function (permission, rolename) {
+      this.deletePermission(permission,'roles',rolename);
+    },
+    removeUserFromRole: function (user, role) { //"get,post,put:/mypermission"
+      var options = {
+        method:'DELETE',
+        endpoint:'roles/'+role+'/users/'+user
+      };
+      this.client().request(options, function (err, data) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error removing user from role');
+        } else {
+          $rootScope.$broadcast('role-update-received');
+        }
+      });
+    },
+    removeUserFromGroup: function (group, role) { //"get,post,put:/mypermission"
+      var options = {
+        method:'DELETE',
+        endpoint:'roles/'+role+'/groups/'+group
+      };
+      this.client().request(options, function (err, data) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error removing role from the group');
+        } else {
+          $rootScope.$broadcast('role-update-received');
+        }
+      });
+    },
+    createAndroidNotifier: function (name, APIkey) {
+      var options = {
+        method:'POST',
+        endpoint:'notifiers',
+        body:{"apiKey":APIkey,"name":name,"provider":"google"}
+      };
+      this.client().request(options, function (err, data) {
+        if (err) {
+          console.error(data);
+          $rootScope.$broadcast('alert', 'error', 'error creating notifier ');
+        } else {
+          $rootScope.$broadcast('alert', 'success', 'New notifier created successfully.');
+          $rootScope.$broadcast('notifier-update');
+        }
+      });
+
+    },
+    createAppleNotifier: function (file, name, environment, certificatePassword ) {
+
+      var provider = 'apple';
+
+      var formData = new FormData();
+      formData.append("p12Certificate", file);
+
+      formData.append('name', name);
+      formData.append('provider', provider);
+      formData.append('environment', environment);
+      formData.append('certificatePassword', certificatePassword);
+
+      var options = {
+        method:'POST',
+        endpoint:'notifiers',
+        body:'{"apiKey":APIkey,"name":name,"provider":"google"}',
+        formData:formData
+      };
+      this.client().request(options, function (err, data) {
+        if (err) {
+          console.error(data);
+          $rootScope.$broadcast('alert', 'error', 'error creating notifier.' );
+        } else {
+          $rootScope.$broadcast('alert', 'success', 'New notifier created successfully.');
+          $rootScope.$broadcast('notifier-update');
+        }
+      });
+
+    },
+    deleteNotifier: function (name) {
+      var options = {
+        method:'DELETE',
+        endpoint: 'notifiers/'+name
+      };
+      this.client().request(options, function (err, data) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error deleting notifier');
+        } else {
+          $rootScope.$broadcast('notifier-update');
+        }
+      });
+
+    },
+    initializeCurrentUser: function (callback) {
+      callback = callback || function(){};
+      if($rootScope.currentUser && !$rootScope.currentUser.reset){
+        callback($rootScope.currentUser);
+        return $rootScope.$broadcast('current-user-initialized', '');
+      }
+      var options = {
+        method:'GET',
+        endpoint:'management/users/'+ this.client().get('email'),
+        mQuery:true
+      };
+      this.client().request(options, function (err, data) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'Error getting user info');
+        } else {
+          $rootScope.currentUser = data.data;
+          $rootScope.currentUser.profileImg = utility.get_gravatar($rootScope.currentUser.email);
+          $rootScope.userEmail =$rootScope.currentUser.email;
+          callback($rootScope.currentUser);
+          $rootScope.$broadcast('current-user-initialized', $rootScope.currentUser);
+        }
+      });
+    },
+
+    updateUser: function (user) {
+      var body = $rootScope.currentUser;
+      body.username = user.username;
+      body.name = user.name;
+      body.email = user.email;
+      var options = {
+        method:'PUT',
+        endpoint:'management/users/' + user.uuid + '/',
+        mQuery:true,
+        body:body
+      };
+      var self = this;
+      this.client().request(options, function (err, data) {
+        self.client().set('email',user.email);
+        self.client().set('username',user.username);
+        if (err) {
+          return $rootScope.$broadcast('user-update-error',data);
+        }
+        $rootScope.currentUser.reset = true;
+        self.initializeCurrentUser(function(){
+          $rootScope.$broadcast('user-update-success', $rootScope.currentUser);
+        });
+      });
+    },
+
+    resetUserPassword: function (user) {
+      var pwdata = {};
+      pwdata.oldpassword = user.oldPassword;
+      pwdata.newpassword = user.newPassword;
+      pwdata.username = user.username;
+      var options = {
+        method:'PUT',
+        endpoint:'users/' + pwdata.uuid + '/',
+        body:pwdata
+      }
+      this.client().request(options, function (err, data) {
+        if (err) {
+         return  $rootScope.$broadcast('alert', 'error', 'Error resetting password');
+        }
+        //remove old and new password fields so they don't end up as part of the entity object
+        $rootScope.currentUser.oldPassword = '';
+        $rootScope.currentUser.newPassword = '';
+        $rootScope.$broadcast('user-reset-password-success', $rootScope.currentUser);
+      });
+
+    },
+    getOrgCredentials: function () {
+      var options = {
+        method:'GET',
+        endpoint:'management/organizations/'+this.client().get('orgName')+'/credentials',
+        mQuery:true
+      };
+      this.client().request(options, function (err, data) {
+        if (err && data.credentials) {
+          $rootScope.$broadcast('alert', 'error', 'Error getting credentials');
+        } else {
+          $rootScope.$broadcast('org-creds-updated', data.credentials);
+        }
+      });
+    },
+    regenerateOrgCredentials: function () {
+      var self = this;
+      var options = {
+        method:'POST',
+        endpoint:'management/organizations/'+ this.client().get('orgName') + '/credentials',
+        mQuery:true
+      };
+      this.client().request(options, function(err, data) {
+        if (err && data.credentials) {
+          $rootScope.$broadcast('alert', 'error', 'Error regenerating credentials');
+        } else {
+          $rootScope.$broadcast('alert', 'success', 'Regeneration of credentials complete.');
+          $rootScope.$broadcast('org-creds-updated', data.credentials);
+        }
+      });
+    },
+    getAppCredentials: function () {
+      var options = {
+        method:'GET',
+        endpoint:'credentials'
+      };
+      this.client().request(options, function (err, data) {
+        if (err && data.credentials) {
+          $rootScope.$broadcast('alert', 'error', 'Error getting credentials');
+        } else {
+          $rootScope.$broadcast('app-creds-updated', data.credentials);
+        }
+      });
+    },
+
+    regenerateAppCredentials: function () {
+      var self = this;
+      var options = {
+        method:'POST',
+        endpoint:'credentials'
+      };
+      this.client().request(options, function(err, data) {
+        if (err && data.credentials) {
+          $rootScope.$broadcast('alert', 'error', 'Error regenerating credentials');
+        } else {
+          $rootScope.$broadcast('alert', 'success', 'Regeneration of credentials complete.');
+          $rootScope.$broadcast('app-creds-updated', data.credentials);
+        }
+      });
+    },
+
+    signUpUser: function(orgName,userName,name,email,password){
+      var formData = {
+        "organization": orgName,
+        "username": userName,
+        "name": name,
+        "email": email,
+        "password": password
+      };
+      var options = {
+        method:'POST',
+        endpoint:'management/organizations',
+        body:formData,
+        mQuery:true
+      };
+      var client = this.client();
+      client.request(options, function(err, data) {
+        if (err) {
+          $rootScope.$broadcast('register-error', data);
+        } else {
+          $rootScope.$broadcast('register-success',data);
+        }
+      });
+    },
+    resendActivationLink: function(id){
+      var options = {
+        method: 'GET',
+        endpoint: 'management/users/'+id+'/reactivate',
+        mQuery:true
+      };
+      this.client().request(options, function (err, data) {
+        if (err) {
+          $rootScope.$broadcast('resend-activate-error', data);
+        } else {
+          $rootScope.$broadcast('resend-activate-success',data);
+        }
+      });
+    },
+    getAppSettings: function(){
+      $rootScope.$broadcast('app-settings-received',{});
+    },
+    getActivities: function(){
+        this.client().request({method:'GET',endpoint:'activities', qs:{limit:200}},function(err,data){
+          if(err) return $rootScope.$broadcast('app-activities-error',data);
+          var entities = data.entities;
+          //set picture if there is none and change gravatar to secure
+          entities.forEach(function(entity) {
+            if (!entity.actor.picture) {
+              entity.actor.picture = window.location.protocol+ "//" + window.location.host + window.location.pathname + "img/user_profile.png"
+            } else {
+              entity.actor.picture = entity.actor.picture.replace(/^http:\/\/www.gravatar/i, 'https://secure.gravatar');
+              //note: changing this to use the image on apigee.com - since the gravatar default won't work on any non-public domains such as localhost
+              //this_data.picture = this_data.picture + encodeURI("?d="+window.location.protocol+"//" + window.location.host + window.location.pathname + "images/user_profile.png");
+              if (~entity.actor.picture.indexOf('http')) {
+                entity.actor.picture = entity.actor.picture;
+              } else {
+                entity.actor.picture = 'https://apigee.com/usergrid/img/user_profile.png';
+              }
+            }
+        });
+          $rootScope.$broadcast('app-activities-received',data.entities);
+        });
+    },
+    getEntityActivities: function(entity){
+        var endpoint = entity.get('type') + '/' + entity.get('uuid') + '/activities' ;
+        var options = {
+          method:'GET',
+          endpoint:endpoint,
+          qs:{limit:200}
+        };
+        this.client().request(options, function (err, data) {
+          if(err){
+            $rootScope.$broadcast(entity.get('type')+'-activities-error',data);
+          }
+          data.entities.forEach(function(entityInstance) {
+            entityInstance.createdDate = (new Date( entityInstance.created)).toUTCString();
+          });
+          $rootScope.$broadcast(entity.get('type')+'-activities-received',data.entities);
+        });
+    },
+    addUserActivity:function(user,content){
+      var options = {
+        "actor": {
+        "displayName": user.get('username'),
+        "uuid": user.get('uuid'),
+        "username":user.get('username')
+          },
+        "verb": "post",
+        "content": content
+      };
+      this.client().createUserActivity(user.get('username'), options, function(err, activity) { //first argument can be 'me', a uuid, or a username
+        if (err) {
+          $rootScope.$broadcast('user-activity-add-error', err);
+        } else {
+          $rootScope.$broadcast('user-activity-add-success', activity);
+        }
+      });
+    },
+    runShellQuery:function(method,path,payload){
+      var options = {
+        "verb": method,
+          "endpoint":path
+      };
+      if(payload){
+        options["body"]=payload;
+      }
+      this.client().request(options,function(err,data){
+        if(err) {
+          $rootScope.$broadcast('shell-error', data);
+        }else{
+          $rootScope.$broadcast('shell-success', data);
+        }
+      });
+    },
+    addOrganization:function(user,orgName){
+      var options = {
+        method: 'POST',
+        endpoint: 'management/users/'+user.uuid+'/organizations',
+        body:{organization:orgName},
+        mQuery:true
+          }, client = this.client(),self=this;
+      client.request(options,function(err,data){
+        if(err){
+          $rootScope.$broadcast('user-add-org-error', data);
+        }else{
+          $rootScope.$broadcast('user-add-org-success', $rootScope.organizations);
+        }
+      });
+    },
+    leaveOrganization:function(user,org){
+      var options = {
+        method: 'DELETE',
+        endpoint: 'management/users/'+user.uuid+'/organizations/'+org.uuid,
+        mQuery:true
+      }
+      this.client().request(options,function(err,data){
+        if(err){
+          $rootScope.$broadcast('user-leave-org-error', data);
+        }else{
+          delete  $rootScope.organizations[org.name];
+          $rootScope.$broadcast('user-leave-org-success', $rootScope.organizations);
+        }
+      });
+    }
+  }
+});

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/global/util-directive.js
----------------------------------------------------------------------
diff --git a/portal/js/global/util-directive.js b/portal/js/global/util-directive.js
new file mode 100644
index 0000000..bbae34e
--- /dev/null
+++ b/portal/js/global/util-directive.js
@@ -0,0 +1,24 @@
+"use strict";
+
+AppServices.Directives.directive('ngFocus', ["$parse", function ($parse) {
+  return function(scope, element, attr) {
+    var fn = $parse(attr['ngFocus']);
+    element.bind('focus', function(event) {
+      scope.$apply(function() {
+        fn(scope, {$event:event});
+      });
+    });
+  }
+}])
+
+
+AppServices.Directives.directive('ngBlur', ["$parse", function ($parse) {
+  return function(scope, element, attr) {
+    var fn = $parse(attr['ngBlur']);
+    element.bind('blur', function(event) {
+      scope.$apply(function() {
+        fn(scope, {$event:event});
+      });
+    });
+  }
+}])
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/global/utility-service.js
----------------------------------------------------------------------
diff --git a/portal/js/global/utility-service.js b/portal/js/global/utility-service.js
new file mode 100755
index 0000000..254f5d2
--- /dev/null
+++ b/portal/js/global/utility-service.js
@@ -0,0 +1,52 @@
+AppServices.Services.factory('utility', function (configuration, $q, $http, $resource) {
+
+  return {
+
+    keys: function(o) {
+      var a = [];
+      for (var propertyName in o) {
+        a.push(propertyName);
+      }
+      return a;
+    },
+    get_gravatar: function(email, size) {
+      try {
+        var size = size || 50;
+        if (email.length) {
+          return 'https://secure.gravatar.com/avatar/' + MD5(email) + '?s=' + size ;
+        } else {
+          return 'https://apigee.com/usergrid/images/user_profile.png';
+        }
+      } catch(e) {
+        return 'https://apigee.com/usergrid/images/user_profile.png';
+      }
+    },
+    get_qs_params: function() {
+      var queryParams = {};
+      if (window.location.search) {
+        // split up the query string and store in an associative array
+        var params = window.location.search.slice(1).split("&");
+        for (var i = 0; i < params.length; i++) {
+          var tmp = params[i].split("=");
+          queryParams[tmp[0]] = unescape(tmp[1]);
+        }
+      }
+      return queryParams;
+    },
+
+    safeApply: function(fn) {
+      var phase = this.$root.$$phase;
+      if(phase == '$apply' || phase == '$digest') {
+        if(fn && (typeof(fn) === 'function')) {
+          fn();
+        }
+      } else {
+        this.$apply(fn);
+      }
+    }
+  };
+
+})
+
+
+

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/global/validate-directive.js
----------------------------------------------------------------------
diff --git a/portal/js/global/validate-directive.js b/portal/js/global/validate-directive.js
new file mode 100644
index 0000000..bf8e384
--- /dev/null
+++ b/portal/js/global/validate-directive.js
@@ -0,0 +1,40 @@
+
+AppServices.Directives.directive('ugValidate', ["$rootScope", function ($rootScope) {
+  return{
+    scope:true,
+    restrict: 'A',
+    require:'ng-model',
+    replace: true,
+    link: function linkFn(scope, element, attrs, ctrl) {
+      var validate = function(){
+        var id = element.attr('id');
+        var validator = id+'-validator';
+        var title = element.attr('title');
+        title = title && title.length  ? title : 'Please enter data' ;
+        $('#'+validator).remove();
+        if(!ctrl.$valid){
+          var validatorElem = '<div id="'+validator+'"><span  class="validator-error-message">'+title+'</span></div>';
+          $( '#'+id ).after( validatorElem);
+          element.addClass('has-error');
+        }else{
+          element.removeClass('has-error');
+          $('#'+validator).remove();
+        }
+      };
+
+      var firing = false;
+      element.bind('blur', function (evt) {
+        validate(scope,element,attrs,ctrl);
+      }).bind('input', function (evt) {
+            if(firing){
+              return ;
+            }
+            firing = true;
+            setTimeout(function(){
+              validate(scope,element,attrs,ctrl);
+              firing=false;
+            },500)
+          });
+    }
+  };
+}]);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/groups/groups-activities-controller.js
----------------------------------------------------------------------
diff --git a/portal/js/groups/groups-activities-controller.js b/portal/js/groups/groups-activities-controller.js
new file mode 100644
index 0000000..f9b3c94
--- /dev/null
+++ b/portal/js/groups/groups-activities-controller.js
@@ -0,0 +1,27 @@
+'use strict'
+
+AppServices.Controllers.controller('GroupsActivitiesCtrl', ['ug', '$scope', '$rootScope', '$location',
+  function (ug, $scope, $rootScope, $location) {
+
+    $scope.activitiesSelected = 'active';
+
+    if (!$rootScope.selectedGroup) {
+      $location.path('/groups');
+      return;
+    } else {
+      $rootScope.selectedGroup.activities = [];
+      $rootScope.selectedGroup.getActivities(function(err, data){
+        if (err) {
+
+        } else {
+//          $rootScope.selectedGroup.activities = data;
+          if(!$rootScope.$$phase) {
+            $rootScope.$apply();
+          }
+        }
+
+      });
+    }
+
+
+  }]);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/groups/groups-activities.html
----------------------------------------------------------------------
diff --git a/portal/js/groups/groups-activities.html b/portal/js/groups/groups-activities.html
new file mode 100644
index 0000000..675bedf
--- /dev/null
+++ b/portal/js/groups/groups-activities.html
@@ -0,0 +1,24 @@
+<div class="content-page" ng-controller="GroupsActivitiesCtrl">
+
+  <br>
+  <div>
+    <table class="table table-striped">
+      <tbody>
+      <tr class="table-header">
+        <td>Date</td>
+        <td>Content</td>
+        <td>Verb</td>
+        <td>UUID</td>
+      </tr>
+      <tr class="zebraRows" ng-repeat="activity in selectedGroup.activities">
+        <td>{{activity.createdDate}}</td>
+        <td>{{activity.content}}</td>
+        <td>{{activity.verb}}</td>
+        <td>{{activity.uuid}}</td>
+      </tr>
+      </tbody>
+    </table>
+  </div>
+
+
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/groups/groups-controller.js
----------------------------------------------------------------------
diff --git a/portal/js/groups/groups-controller.js b/portal/js/groups/groups-controller.js
new file mode 100644
index 0000000..ae9ab35
--- /dev/null
+++ b/portal/js/groups/groups-controller.js
@@ -0,0 +1,119 @@
+'use strict'
+
+AppServices.Controllers.controller('GroupsCtrl', ['ug', '$scope', '$rootScope', '$location', '$route',
+  function (ug, $scope, $rootScope, $location, $route) {
+
+    $scope.groupsCollection = {};
+    $rootScope.selectedGroup = {};
+    $scope.previous_display = 'none';
+    $scope.next_display = 'none';
+    $scope.hasGroups = false;
+    $scope.newGroup = {path:'',title:''}
+
+    ug.getGroups();
+
+    $scope.currentGroupsPage = {};
+//  $scope.$route = $route;
+
+    $scope.selectGroupPage = function(route){
+      //lokup the template URL with the route. trying to preserve routes in the markup and not hard link to .html
+      $scope.currentGroupsPage.template = $route.routes[route].templateUrl;
+      $scope.currentGroupsPage.route = route;
+    }
+
+    $scope.newGroupDialog = function(modalId,form){
+      //todo: put more validate here
+      if ($scope.newGroup.path && $scope.newGroup.title) {
+        //$scope.path = $scope.path.replace(' ','');
+        ug.createGroup($scope.removeFirstSlash($scope.newGroup.path), $scope.newGroup.title);
+        $scope.hideModal(modalId);
+        $scope.newGroup = {path:'',title:''}
+      } else {
+        $rootScope.$broadcast('alert', 'error', 'Missing required information.');
+      }
+    };
+
+    $scope.deleteGroupsDialog = function(modalId){
+      $scope.deleteEntities($scope.groupsCollection, 'group-deleted', 'error deleting group');
+      $scope.hideModal(modalId);
+      $scope.newGroup = {path:'',title:''}
+    };
+    $scope.$on('group-deleted',function(){
+      $rootScope.$broadcast('alert', 'success', 'Group deleted successfully.');
+    });
+    $scope.$on('group-deleted-error',function(){
+      ug.getGroups();
+    });
+
+    $scope.$on("groups-create-success",function(){
+      $rootScope.$broadcast('alert', 'success', 'Group created successfully.');
+    });
+
+    $scope.$on("groups-create-error",function(){
+      $rootScope.$broadcast('alert', 'error', 'Error creating group. Make sure you don\'t have spaces in the path.');
+    });
+
+    $scope.$on('groups-received', function(event, groups) {
+      $scope.groupBoxesSelected = false;
+      $scope.groupsCollection = groups;
+      $scope.newGroup.path = '';
+      $scope.newGroup.title = '';
+      if(groups._list.length > 0 && (!$rootScope.selectedGroup._data || !groups._list.some(function(group){ return $rootScope.selectedGroup._data.uuid === group._data.uuid }))){ // if groups have been received already do not reselect
+        $scope.selectGroup(groups._list[0]._data.uuid)
+      }
+      $scope.hasGroups = groups._list.length > 0;
+      $scope.received = true;
+
+      $scope.checkNextPrev();
+      $scope.applyScope();
+    });
+
+    $scope.resetNextPrev = function() {
+      $scope.previous_display = 'none';
+      $scope.next_display = 'none';
+    }
+    $scope.checkNextPrev = function() {
+      $scope.resetNextPrev();
+      if ($scope.groupsCollection.hasPreviousPage()) {
+        $scope.previous_display = 'block';
+      }
+      if($scope.groupsCollection.hasNextPage()) {
+        $scope.next_display = 'block';
+      }
+    }
+
+    $scope.selectGroup = function(uuid){
+      $rootScope.selectedGroup = $scope.groupsCollection.getEntityByUUID(uuid);
+      $scope.currentGroupsPage.template = 'groups/groups-details.html';
+      $scope.currentGroupsPage.route = '/groups/details';
+      $rootScope.$broadcast('group-selection-changed', $rootScope.selectedGroup);
+    }
+
+    $scope.getPrevious = function () {
+      $scope.groupsCollection.getPreviousPage(function(err) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error getting previous page of groups');
+        }
+        $scope.checkNextPrev();
+        $scope.applyScope();
+      });
+    };
+
+    $scope.getNext = function () {
+
+      $scope.groupsCollection.getNextPage(function(err) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error getting next page of groups');
+        }
+        $scope.checkNextPrev();
+        $scope.applyScope();
+      });
+    };
+
+    $scope.$on('group-deleted', function(event) {
+      $route.reload();
+      $scope.master = '';
+    });
+
+
+  }]);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/groups/groups-details-controller.js
----------------------------------------------------------------------
diff --git a/portal/js/groups/groups-details-controller.js b/portal/js/groups/groups-details-controller.js
new file mode 100644
index 0000000..e9164ac
--- /dev/null
+++ b/portal/js/groups/groups-details-controller.js
@@ -0,0 +1,38 @@
+'use strict'
+
+AppServices.Controllers.controller('GroupsDetailsCtrl', ['ug', '$scope', '$rootScope', '$location',
+  function (ug, $scope, $rootScope, $location) {
+
+    var selectedGroup = $rootScope.selectedGroup.clone();
+    $scope.detailsSelected = 'active';
+    $scope.json = selectedGroup._json;
+    $scope.group = selectedGroup._data;
+    $scope.group.path =  $scope.group.path.indexOf('/')!=0 ? '/'+$scope.group.path : $scope.group.path;
+    $scope.group.title = $scope.group.title;
+
+    if (!$rootScope.selectedGroup) {
+      $location.path('/groups');
+      return;
+    }
+    $scope.$on('group-selection-changed',function(evt,selectedGroup){
+      $scope.group.path =  selectedGroup._data.path.indexOf('/')!=0 ? '/'+selectedGroup._data.path : selectedGroup._data.path;
+      $scope.group.title = selectedGroup._data.title;
+      $scope.detailsSelected = 'active';
+      $scope.json = selectedGroup._json || selectedGroup._data.stringifyJSON();
+
+    });
+
+    $rootScope.saveSelectedGroup = function(){
+      $rootScope.selectedGroup._data.title = $scope.group.title;
+      $rootScope.selectedGroup._data.path = $scope.removeFirstSlash( $scope.group.path);
+      $rootScope.selectedGroup.save(function(err) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error saving group');
+        } else {
+          $rootScope.$broadcast('alert', 'success', 'group saved');
+        }
+      });
+
+    }
+
+  }]);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/groups/groups-details.html
----------------------------------------------------------------------
diff --git a/portal/js/groups/groups-details.html b/portal/js/groups/groups-details.html
new file mode 100644
index 0000000..1e55822
--- /dev/null
+++ b/portal/js/groups/groups-details.html
@@ -0,0 +1,28 @@
+<div class="content-page" ng-controller="GroupsDetailsCtrl">
+
+  <div>
+      <form name="updateGroupDetailForm" ng-submit="saveSelectedGroup()" novalidate>
+          <div style="float: left; padding-right: 30px;">
+              <h4 class="ui-dform-legend">Group Information</h4>
+              <label for="group-title" class="ui-dform-label">Group Title</label>
+              <input type="text" id="group-title" ng-pattern="titleRegex" ng-attr-title="{{titleRegexDescription}}" required class="ui-dform-text" ng-model="group.title" ug-validate>
+              <br/>
+            <label for="group-path" class="ui-dform-label">Group Path</label>
+            <input type="text" id="group-path" required ng-attr-title="{{pathRegexDescription}}" placeholder="ex: /mydata" ng-pattern="pathRegex" class="ui-dform-text" ng-model="group.path" ug-validate>
+            <br/>
+          </div>
+          <br style="clear:both"/>
+
+          <div style="width:100%;float:left;padding: 20px 0">
+              <input type="submit" value="Save Group" style="margin-right: 15px;" ng-disabled="!updateGroupDetailForm.$valid" class="btn btn-primary" />
+          </div>
+
+          <div class="content-container">
+              <h4>JSON Group Object</h4>
+              <pre>{{json}}</pre>
+          </div>
+      </form>
+  </div>
+
+
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/groups/groups-members-controller.js
----------------------------------------------------------------------
diff --git a/portal/js/groups/groups-members-controller.js b/portal/js/groups/groups-members-controller.js
new file mode 100644
index 0000000..b4c4c16
--- /dev/null
+++ b/portal/js/groups/groups-members-controller.js
@@ -0,0 +1,113 @@
+'use strict'
+
+AppServices.Controllers.controller('GroupsMembersCtrl', ['ug', '$scope', '$rootScope', '$location',
+  function (ug, $scope, $rootScope, $location) {
+
+    $scope.membersSelected = 'active';
+    $scope.previous_display = 'none';
+    $scope.next_display = 'none';
+    $scope.user = '';
+    $scope.master = '';
+    $scope.hasMembers = false
+
+
+    //todo find others and combine this into one controller
+    ug.getUsersTypeAhead();
+
+    $scope.usersTypeaheadValues = [];
+    $scope.$on('users-typeahead-received', function(event, users) {
+      $scope.usersTypeaheadValues = users;
+      $scope.applyScope();
+    });
+
+    $scope.addGroupToUserDialog = function(modalId){
+      if ($scope.user) {
+        var path =  $rootScope.selectedGroup.get('path');
+        ug.addUserToGroup($scope.user.uuid, path);
+        $scope.user = '';
+        $scope.hideModal(modalId)
+      } else {
+        $rootScope.$broadcast('alert', 'error', 'Please select a user.');
+      }
+    };
+
+    $scope.removeUsersFromGroupDialog = function(modalId){
+      $scope.deleteEntities($scope.groupsCollection.users, 'group-update-received', 'Error removing user from group');
+      $scope.hideModal(modalId)
+    };
+
+    $scope.get = function() {
+      if(!$rootScope.selectedGroup.get){
+        return;
+      }
+      var options = {
+        type:'groups/'+$rootScope.selectedGroup.get('path') +'/users'
+      }
+      $scope.groupsCollection.addCollection('users', options, function(err) {
+          $scope.groupMembersSelected = false;
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error getting users for group');
+        } else {
+          $scope.hasMembers =  $scope.groupsCollection.users._list.length > 0;
+          $scope.checkNextPrev();
+          $scope.applyScope();
+        }
+      });
+    }
+
+    $scope.resetNextPrev = function() {
+      $scope.previous_display = 'none';
+      $scope.next_display = 'none';
+    }
+    $scope.checkNextPrev = function() {
+      $scope.resetNextPrev();
+      if ($scope.groupsCollection.users.hasPreviousPage()) {
+        $scope.previous_display = 'block';
+      }
+
+      if($scope.groupsCollection.users.hasNextPage()) {
+        $scope.next_display = 'block';
+      }
+    }
+
+    if (!$rootScope.selectedGroup) {
+      $location.path('/groups');
+      return;
+    } else {
+      $scope.get();
+    }
+
+    $scope.getPrevious = function () {
+      $scope.groupsCollection.users.getPreviousPage(function(err) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error getting previous page of users');
+        }
+        $scope.checkNextPrev();
+        if(!$rootScope.$$phase) {
+          $rootScope.$apply();
+        }
+      });
+    };
+
+    $scope.getNext = function () {
+      $scope.groupsCollection.users.getNextPage(function(err) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error getting next page of users');
+        }
+        $scope.checkNextPrev();
+        if(!$rootScope.$$phase) {
+          $rootScope.$apply();
+        }
+      });
+    };
+
+    $scope.$on('group-update-received', function(event) {
+      $scope.get();
+    });
+
+    $scope.$on('user-added-to-group-received', function(event) {
+      $scope.get();
+    });
+
+
+  }]);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/groups/groups-members.html
----------------------------------------------------------------------
diff --git a/portal/js/groups/groups-members.html b/portal/js/groups/groups-members.html
new file mode 100644
index 0000000..d2f5644
--- /dev/null
+++ b/portal/js/groups/groups-members.html
@@ -0,0 +1,60 @@
+<div class="content-page" ng-controller="GroupsMembersCtrl">
+
+
+  <bsmodal id="removeFromGroup"
+           title="Confirmation"
+           close="hideModal"
+           closelabel="Cancel"
+           extrabutton="removeUsersFromGroupDialog"
+           extrabuttonlabel="Delete"
+           ng-cloak>
+    <p>Are you sure you want to remove the users from the seleted group(s)?</p>
+  </bsmodal>
+
+  <bsmodal id="addGroupToUser"
+           title="Add user to group"
+           close="hideModal"
+           closelabel="Cancel"
+           extrabutton="addGroupToUserDialog"
+           extrabuttonlabel="Add"
+           ng-cloak>
+    <div class="btn-group">
+      <a class="btn dropdown-toggle filter-selector" data-toggle="dropdown">
+        <span class="filter-label">{{$parent.user != '' ? $parent.user.username : 'Select a user...'}}</span>
+        <span class="caret"></span>
+      </a>
+      <ul class="dropdown-menu">
+        <li ng-repeat="user in $parent.usersTypeaheadValues" class="filterItem"><a ng-click="$parent.$parent.user = user">{{user.username}}</a></li>
+      </ul>
+    </div>
+  </bsmodal>
+
+
+  <div class="button-strip">
+    <button class="btn btn-primary"  ng-click="showModal('addGroupToUser')">Add User to Group</button>
+    <button class="btn btn-primary" ng-disabled="!hasMembers || !valueSelected(groupsCollection.users._list)" ng-click="showModal('removeFromGroup')">Remove User(s) from Group</button>
+  </div>
+  <table class="table table-striped">
+    <tr class="table-header">
+      <td style="width: 30px;"><input type="checkbox" ng-show="hasMembers" id="selectAllCheckbox" ng-model="groupMembersSelected" ng-click="selectAllEntities(groupsCollection.users._list,this,'groupMembersSelected')"></td>
+      <td style="width: 50px;"></td>
+      <td>Username</td>
+      <td>Display Name</td>
+    </tr>
+    <tr class="zebraRows" ng-repeat="user in groupsCollection.users._list">
+      <td>
+        <input
+          type="checkbox"
+          ng-model="user.checked"
+          >
+      </td>
+      <td><img style="width:30px;height:30px;" ng-src="{{user._portal_image_icon}}"></td>
+      <td>{{user.get('username')}}</td>
+      <td>{{user.get('name')}}</td>
+    </tr>
+  </table>
+  <div style="padding: 10px 5px 10px 5px">
+    <button class="btn btn-primary" ng-click="getPrevious()" style="display:{{previous_display}}">< Previous</button>
+    <button class="btn btn-primary" ng-click="getNext()" style="display:{{next_display}}; float:right;">Next ></button>
+  </div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/groups/groups-roles-controller.js
----------------------------------------------------------------------
diff --git a/portal/js/groups/groups-roles-controller.js b/portal/js/groups/groups-roles-controller.js
new file mode 100644
index 0000000..e3a8004
--- /dev/null
+++ b/portal/js/groups/groups-roles-controller.js
@@ -0,0 +1,184 @@
+'use strict'
+
+AppServices.Controllers.controller('GroupsRolesCtrl', ['ug', '$scope', '$rootScope', '$location',
+  function (ug, $scope, $rootScope, $location) {
+
+
+    $scope.rolesSelected = 'active';
+    $scope.roles_previous_display = 'none';
+    $scope.roles_next_display = 'none';
+    $scope.name = '';
+    $scope.master = '';
+    $scope.hasRoles = false;
+    $scope.hasPermissions = false;
+    $scope.permissions = {};
+
+
+    $scope.addGroupToRoleDialog = function(modalId){
+      if ($scope.name) {
+        var path =  $rootScope.selectedGroup.get('path');
+        ug.addGroupToRole(path, $scope.name);
+        $scope.hideModal(modalId)
+        $scope.name='';
+      } else {
+        $rootScope.$broadcast('alert', 'error', 'You must specify a role name.');
+      }
+    };
+
+
+    $scope.leaveRoleDialog = function(modalId){
+      var path =  $rootScope.selectedGroup.get('path');
+      var roles = $scope.groupsCollection.roles._list;
+      for (var i=0;i<roles.length;i++) {
+        if (roles[i].checked) {
+          ug.removeUserFromGroup(path, roles[i]._data.name);
+        }
+      }
+      $scope.hideModal(modalId)
+    };
+
+    $scope.addGroupPermissionDialog = function(modalId){
+      if ($scope.permissions.path) {
+
+        var permission = $scope.createPermission(null,null,$scope.removeFirstSlash($scope.permissions.path),$scope.permissions);
+        var path =  $rootScope.selectedGroup.get('path');
+        ug.newGroupPermission(permission, path);
+        $scope.hideModal(modalId)
+        if($scope.permissions){
+          $scope.permissions = {};
+        }
+      } else {
+        $rootScope.$broadcast('alert', 'error', 'You must specify a name for the permission.');
+      }
+    };
+
+    $scope.deleteGroupPermissionDialog = function(modalId){
+      var path =  $rootScope.selectedGroup.get('path');
+      var permissions = $rootScope.selectedGroup.permissions;
+      for (var i=0;i<permissions.length;i++) {
+        if (permissions[i].checked) {
+          ug.deleteGroupPermission(permissions[i].perm, path);
+        }
+      }
+      $scope.hideModal(modalId)
+    };
+
+    $scope.resetNextPrev = function() {
+      $scope.roles_previous_display = 'none';
+      $scope.roles_next_display = 'none';
+      $scope.permissions_previous_display = 'none';
+      $scope.permissions_next_display = 'none';
+    }
+    $scope.resetNextPrev();
+    $scope.checkNextPrevRoles = function() {
+      $scope.resetNextPrev();
+      if ($scope.groupsCollection.roles.hasPreviousPage()) {
+        $scope.roles_previous_display = 'block';
+      }
+      if($scope.groupsCollection.roles.hasNextPage()) {
+        $scope.roles_next_display = 'block';
+      }
+    }
+    $scope.checkNextPrevPermissions = function() {
+      if ($scope.groupsCollection.permissions.hasPreviousPage()) {
+        $scope.permissions_previous_display = 'block';
+      }
+      if($scope.groupsCollection.permissions.hasNextPage()) {
+        $scope.permissions_next_display = 'block';
+      }
+    }
+
+    $scope.getRoles = function() {
+
+      var path = $rootScope.selectedGroup.get('path');
+      var options = {
+        type:'groups/'+ path +'/roles'
+      }
+      $scope.groupsCollection.addCollection('roles', options, function(err) {
+        $scope.groupRoleSelected = false;
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error getting roles for group');
+        } else {
+          $scope.hasRoles = $scope.groupsCollection.roles._list.length > 0;
+          $scope.checkNextPrevRoles();
+          $scope.applyScope();
+        }
+      });
+    }
+
+    $scope.getPermissions = function() {
+
+      $rootScope.selectedGroup.permissions = [];
+      $rootScope.selectedGroup.getPermissions(function(err, data){
+        $scope.groupPermissionsSelected = false;
+        $scope.hasPermissions = $scope.selectedGroup.permissions.length;
+        if (err) {
+
+        } else {
+          $scope.applyScope();
+        }
+      });
+
+    }
+
+    $scope.getPreviousRoles = function () {
+      $scope.groupsCollection.roles.getPreviousPage(function(err) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error getting previous page of roles');
+        }
+        $scope.checkNextPrevRoles();
+        $scope.applyScope();
+      });
+    };
+    $scope.getNextRoles = function () {
+      $scope.groupsCollection.roles.getNextPage(function(err) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error getting next page of roles');
+        }
+        $scope.checkNextPrevRoles();
+        $scope.applyScope();
+      });
+    };
+    $scope.getPreviousPermissions = function () {
+      $scope.groupsCollection.permissions.getPreviousPage(function(err) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error getting previous page of permissions');
+        }
+        $scope.checkNextPrevPermissions();
+        $scope.applyScope();
+      });
+    };
+    $scope.getNextPermissions = function () {
+      $scope.groupsCollection.permissions.getNextPage(function(err) {
+        if (err) {
+          $rootScope.$broadcast('alert', 'error', 'error getting next page of permissions');
+        }
+        $scope.checkNextPrevPermissions();
+        $scope.applyScope();
+      });
+    };
+
+    $scope.$on('role-update-received', function(event) {
+      $scope.getRoles();
+    });
+
+    $scope.$on('permission-update-received', function(event) {
+      $scope.getPermissions();
+    });
+
+    $scope.$on('groups-received',function(evt,data){
+      $scope.groupsCollection = data;
+      $scope.getRoles();
+      $scope.getPermissions();
+    })
+
+    if (!$rootScope.selectedGroup) {
+      $location.path('/groups');
+      return;
+    } else {
+      ug.getRolesTypeAhead();
+      ug.getGroups();
+    }
+
+
+  }]);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/groups/groups-roles.html
----------------------------------------------------------------------
diff --git a/portal/js/groups/groups-roles.html b/portal/js/groups/groups-roles.html
new file mode 100644
index 0000000..f8b586b
--- /dev/null
+++ b/portal/js/groups/groups-roles.html
@@ -0,0 +1,127 @@
+<div class="content-page" ng-controller="GroupsRolesCtrl">
+
+  <bsmodal id="addGroupToRole"
+           title="Add group to role"
+           close="hideModal"
+           closelabel="Cancel"
+           extrabutton="addGroupToRoleDialog"
+           extrabuttonlabel="Add"
+           ng-cloak>
+    <div class="btn-group">
+      <a class="btn dropdown-toggle filter-selector" data-toggle="dropdown">
+        <span class="filter-label">{{$parent.name != '' ? $parent.name : 'Role name...'}}</span>
+        <span class="caret"></span>
+      </a>
+      <ul class="dropdown-menu">
+        <li ng-repeat="role in $parent.rolesTypeaheadValues" class="filterItem"><a ng-click="$parent.$parent.name = role.name">{{role.name}}</a></li>
+      </ul>
+    </div>
+  </bsmodal>
+
+  <bsmodal id="leaveRoleFromGroup"
+           title="Confirmation"
+           close="hideModal"
+           closelabel="Cancel"
+           extrabutton="leaveRoleDialog"
+           extrabuttonlabel="Leave"
+           ng-cloak>
+    <p>Are you sure you want to remove the group from the role(s)?</p>
+  </bsmodal>
+
+
+  <div class="button-strip">
+    <button class="btn btn-primary" ng-click="showModal('addGroupToRole')">Add Role to Group</button>
+    <button class="btn btn-primary" ng-disabled="!hasRoles || !valueSelected(groupsCollection.roles._list)" ng-click="showModal('leaveRoleFromGroup')">Remove Role(s) from Group</button>
+  </div>
+  <h4>Roles</h4>
+  <table class="table table-striped">
+    <tbody>
+    <tr class="table-header">
+      <td style="width: 30px;"><input type="checkbox" ng-show="hasRoles" id="groupsSelectAllCheckBox" ng-model="groupRoleSelected" ng-click="selectAllEntities(groupsCollection.roles._list,this,'groupRoleSelected')" ></td>
+      <td>Role Name</td>
+      <td>Role title</td>
+    </tr>
+    <tr class="zebraRows" ng-repeat="role in groupsCollection.roles._list">
+      <td>
+        <input
+          type="checkbox"
+          ng-model="role.checked"
+          >
+      </td>
+      <td>{{role._data.name}}</td>
+      <td>{{role._data.title}}</td>
+    </tr>
+    </tbody>
+  </table>
+  <div style="padding: 10px 5px 10px 5px">
+    <button class="btn btn-primary" ng-click="getPreviousRoles()" style="display:{{roles_previous_display}}">< Previous</button>
+    <button class="btn btn-primary" ng-click="getNextRoles()" style="display:{{roles_next_display}};float:right;">Next ></button>
+  </div>
+
+
+  <bsmodal id="deletePermission"
+           title="Confirmation"
+           close="hideModal"
+           closelabel="Cancel"
+           extrabutton="deleteGroupPermissionDialog"
+           extrabuttonlabel="Delete"
+           ng-cloak>
+    <p>Are you sure you want to delete the permission(s)?</p>
+  </bsmodal>
+
+
+  <bsmodal id="addPermission"
+           title="New Permission"
+           close="hideModal"
+           closelabel="Cancel"
+           extrabutton="addGroupPermissionDialog"
+           extrabuttonlabel="Add"
+           ng-cloak>
+    <p>Path: <input ng-model="$parent.permissions.path" placeholder="ex: /mydata" id="groupsrolespermissions" type="text" ng-pattern="pathRegex" ng-attr-title="{{pathRegexDescription}}" required ug-validate  /></p>
+    <div class="control-group">
+      <input type="checkbox" ng-model="$parent.permissions.getPerm"> GET
+    </div>
+    <div class="control-group">
+      <input type="checkbox" ng-model="$parent.permissions.postPerm"> POST
+    </div>
+    <div class="control-group">
+      <input type="checkbox" ng-model="$parent.permissions.putPerm"> PUT
+    </div>
+    <div class="control-group">
+      <input type="checkbox" ng-model="$parent.permissions.deletePerm"> DELETE
+    </div>
+  </bsmodal>
+
+
+  <div class="button-strip">
+    <button class="btn btn-primary" ng-click="showModal('addPermission')">Add Permission</button>
+    <button class="btn btn-primary" ng-disabled="!hasPermissions || !valueSelected(selectedGroup.permissions)" ng-click="showModal('deletePermission')">Delete Permission(s)</button>
+  </div>
+  <h4>Permissions</h4>
+  <table class="table table-striped">
+    <tbody>
+    <tr class="table-header">
+      <td style="width: 30px;"><input ng-show="hasPermissions" type="checkbox" id="permissionsSelectAllCheckBox" ng-model="groupPermissionsSelected" ng-click="selectAllEntities(selectedGroup.permissions,this,'groupPermissionsSelected')"  ></td>
+      <td>Path</td>
+      <td>GET</td>
+      <td>POST</td>
+      <td>PUT</td>
+      <td>DELETE</td>
+    </tr>
+    <tr class="zebraRows" ng-repeat="permission in selectedGroup.permissions">
+      <td>
+        <input
+          type="checkbox"
+          ng-model="permission.checked"
+          >
+      </td>
+      <td>{{permission.path}}</td>
+      <td>{{permission.operations.get}}</td>
+      <td>{{permission.operations.post}}</td>
+      <td>{{permission.operations.put}}</td>
+      <td>{{permission.operations.delete}}</td>
+    </tr>
+    </tbody>
+  </table>
+
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/groups/groups-tabs.html
----------------------------------------------------------------------
diff --git a/portal/js/groups/groups-tabs.html b/portal/js/groups/groups-tabs.html
new file mode 100644
index 0000000..79d0a78
--- /dev/null
+++ b/portal/js/groups/groups-tabs.html
@@ -0,0 +1,31 @@
+<div class="content-page">
+
+  <section class="row-fluid">
+
+    <div class="span12">
+      <div class="page-filters">
+        <h1 class="title" class="pull-left"><i class="pictogram title">&#128101;</i> Groups</h1>
+      </div>
+    </div>
+
+  </section>
+
+  <div id="user-panel" class="panel-buffer">
+    <ul id="user-panel-tab-bar" class="nav nav-tabs">
+      <li><a href="javaScript:void(0);" ng-click="gotoPage('groups')">Group List</a></li>
+      <li ng-class="detailsSelected"><a href="javaScript:void(0);" ng-click="gotoPage('groups/details')">Details</a></li>
+      <li ng-class="membersSelected"><a href="javaScript:void(0);" ng-click="gotoPage('groups/members')">Users</a></li>
+      <li ng-class="activitiesSelected"><a href="javaScript:void(0);" ng-click="gotoPage('groups/activities')">Activities</a></li>
+      <li ng-class="rolesSelected"><a href="javaScript:void(0);" ng-click="gotoPage('groups/roles')">Roles &amp; Permissions</a></li>
+    </ul>
+  </div>
+
+  <div style="float: left; margin-right: 10px;">
+    <div style="float: left;">
+      <div class="user-header-title"><strong>Group Path: </strong>{{selectedGroup.get('path')}}</div>
+      <div class="user-header-title"><strong>Group Title: </strong>{{selectedGroup.get('title')}}</div>
+    </div>
+  </div>
+</div>
+<br>
+<br>

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/groups/groups.html
----------------------------------------------------------------------
diff --git a/portal/js/groups/groups.html b/portal/js/groups/groups.html
new file mode 100644
index 0000000..4f81b18
--- /dev/null
+++ b/portal/js/groups/groups.html
@@ -0,0 +1,92 @@
+<div class="content-page">
+
+  <section class="row-fluid">
+
+    <div class="span12">
+      <div class="page-filters">
+        <h1 class="title" class="pull-left"><i class="pictogram title">&#128101;</i> Groups</h1>
+      </div>
+    </div>
+
+  </section>
+
+
+
+  <bsmodal id="newGroup"
+           title="New Group"
+           close="hideModal"
+           closelabel="Cancel"
+           extrabutton="newGroupDialog"
+           extrabuttonlabel="Add"
+           ng-model="dialog"
+           ng-cloak>
+    <fieldset>
+      <div class="control-group">
+        <label for="title">Title</label>
+        <div class="controls">
+          <input type="text" id="title" ng-pattern="titleRegex" ng-attr-title="{{titleRegexDescription}}" required ng-model="newGroup.title"class="input-xlarge" ug-validate/>
+        </div>
+      </div>
+      <div class="control-group">
+        <label for="path">Path</label>
+        <div class="controls">
+          <input id="path" type="text" ng-attr-title="{{pathRegexDescription}}" placeholder="ex: /mydata" ng-pattern="pathRegex" required ng-model="newGroup.path" class="input-xlarge" ug-validate/>
+        </div>
+      </div>
+    </fieldset>
+  </bsmodal>
+
+  <bsmodal id="deleteGroup"
+           title="Delete Group"
+           close="hideModal"
+           closelabel="Cancel"
+           extrabutton="deleteGroupsDialog"
+           extrabuttonlabel="Delete"
+           ng-cloak>
+    <p>Are you sure you want to delete the group(s)?</p>
+  </bsmodal>
+
+
+  <section class="row-fluid">
+    <div class="span3 user-col">
+
+      <div class="button-toolbar span12">
+        <a title="Select All" class="btn btn-primary select-all toolbar" ng-show="hasGroups" ng-click="selectAllEntities(groupsCollection._list,this,'groupBoxesSelected',true)"> <i class="pictogram">&#8863;</i></a>
+        <button title="Delete" class="btn btn-primary toolbar" ng-disabled="!hasGroups || !valueSelected(groupsCollection._list)" ng-click="showModal('deleteGroup')"><i class="pictogram">&#9749;</i></button>
+        <button title="Add" class="btn btn-primary toolbar" ng-click="showModal('newGroup')"><i class="pictogram">&#59136;</i></button>
+      </div>
+      <ul class="user-list">
+        <li ng-class="selectedGroup._data.uuid === group._data.uuid ? 'selected' : ''" ng-repeat="group in groupsCollection._list" ng-click="selectGroup(group._data.uuid)">
+          <input
+              type="checkbox"
+              ng-value="group._data.uuid"
+              ng-checked="group.checked"
+              ng-model="group.checked"
+              >
+          <a href="javaScript:void(0)" >{{group.get('title')}}</a>
+          <br/>
+          <span ng-if="group.get('path')" class="label">Path:</span>/{{group.get('path')}}
+        </li>
+      </ul>
+
+
+      <div style="padding: 10px 5px 10px 5px">
+        <button class="btn btn-primary" ng-click="getPrevious()" style="display:{{previous_display}}">< Previous</button>
+        <button class="btn btn-primary" ng-click="getNext()" style="display:{{next_display}}; float:right;">Next ></button>
+      </div>
+
+    </div>
+
+    <div class="span9 tab-content" ng-show="selectedGroup.get" >
+      <div class="menu-toolbar">
+        <ul class="inline" >
+          <li class="tab" ng-class="currentGroupsPage.route === '/groups/details' ? 'selected' : ''"><a class="btn btn-primary toolbar" ng-click="selectGroupPage('/groups/details')"><i class="pictogram">&#59170;</i>Details</a></li>
+          <li class="tab" ng-class="currentGroupsPage.route === '/groups/members' ? 'selected' : ''"><a class="btn btn-primary toolbar" ng-click="selectGroupPage('/groups/members')"><i class="pictogram">&#128101;</i>Users</a></li>
+          <li class="tab" ng-class="currentGroupsPage.route === '/groups/activities' ? 'selected' : ''"><a class="btn btn-primary toolbar" ng-click="selectGroupPage('/groups/activities')"><i class="pictogram">&#59194;</i>Activities</a></li>
+          <li class="tab" ng-class="currentGroupsPage.route === '/groups/roles' ? 'selected' : ''"><a class="btn btn-primary toolbar" ng-click="selectGroupPage('/groups/roles')"><i class="pictogram">&#127758;</i>Roles &amp; Permissions</a></li>
+        </ul>
+      </div>
+      <span ng-include="currentGroupsPage.template"></span>
+
+  </section>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/lib/MD5.min.js
----------------------------------------------------------------------
diff --git a/portal/js/lib/MD5.min.js b/portal/js/lib/MD5.min.js
deleted file mode 100644
index 0bfc085..0000000
--- a/portal/js/lib/MD5.min.js
+++ /dev/null
@@ -1 +0,0 @@
-var MD5=function(a){function n(a){a=a.replace(/\r\n/g,"\n");var b="";for(var c=0;c<a.length;c++){var d=a.charCodeAt(c);if(d<128){b+=String.fromCharCode(d)}else if(d>127&&d<2048){b+=String.fromCharCode(d>>6|192);b+=String.fromCharCode(d&63|128)}else{b+=String.fromCharCode(d>>12|224);b+=String.fromCharCode(d>>6&63|128);b+=String.fromCharCode(d&63|128)}}return b}function m(a){var b="",c="",d,e;for(e=0;e<=3;e++){d=a>>>e*8&255;c="0"+d.toString(16);b=b+c.substr(c.length-2,2)}return b}function l(a){var b;var c=a.length;var d=c+8;var e=(d-d%64)/64;var f=(e+1)*16;var g=Array(f-1);var h=0;var i=0;while(i<c){b=(i-i%4)/4;h=i%4*8;g[b]=g[b]|a.charCodeAt(i)<<h;i++}b=(i-i%4)/4;h=i%4*8;g[b]=g[b]|128<<h;g[f-2]=c<<3;g[f-1]=c>>>29;return g}function k(a,d,e,f,h,i,j){a=c(a,c(c(g(d,e,f),h),j));return c(b(a,i),d)}function j(a,d,e,g,h,i,j){a=c(a,c(c(f(d,e,g),h),j));return c(b(a,i),d)}function i(a,d,f,g,h,i,j){a=c(a,c(c(e(d,f,g),h),j));return c(b(a,i),d)}function h(a,e,f,g,h,i,j){a=c(a,c(c(d(e,f,g),h),j));re
 turn c(b(a,i),e)}function g(a,b,c){return b^(a|~c)}function f(a,b,c){return a^b^c}function e(a,b,c){return a&c|b&~c}function d(a,b,c){return a&b|~a&c}function c(a,b){var c,d,e,f,g;e=a&2147483648;f=b&2147483648;c=a&1073741824;d=b&1073741824;g=(a&1073741823)+(b&1073741823);if(c&d){return g^2147483648^e^f}if(c|d){if(g&1073741824){return g^3221225472^e^f}else{return g^1073741824^e^f}}else{return g^e^f}}function b(a,b){return a<<b|a>>>32-b}var o=Array();var p,q,r,s,t,u,v,w,x;var y=7,z=12,A=17,B=22;var C=5,D=9,E=14,F=20;var G=4,H=11,I=16,J=23;var K=6,L=10,M=15,N=21;a=n(a);o=l(a);u=1732584193;v=4023233417;w=2562383102;x=271733878;for(p=0;p<o.length;p+=16){q=u;r=v;s=w;t=x;u=h(u,v,w,x,o[p+0],y,3614090360);x=h(x,u,v,w,o[p+1],z,3905402710);w=h(w,x,u,v,o[p+2],A,606105819);v=h(v,w,x,u,o[p+3],B,3250441966);u=h(u,v,w,x,o[p+4],y,4118548399);x=h(x,u,v,w,o[p+5],z,1200080426);w=h(w,x,u,v,o[p+6],A,2821735955);v=h(v,w,x,u,o[p+7],B,4249261313);u=h(u,v,w,x,o[p+8],y,1770035416);x=h(x,u,v,w,o[p+9],z,2336552
 879);w=h(w,x,u,v,o[p+10],A,4294925233);v=h(v,w,x,u,o[p+11],B,2304563134);u=h(u,v,w,x,o[p+12],y,1804603682);x=h(x,u,v,w,o[p+13],z,4254626195);w=h(w,x,u,v,o[p+14],A,2792965006);v=h(v,w,x,u,o[p+15],B,1236535329);u=i(u,v,w,x,o[p+1],C,4129170786);x=i(x,u,v,w,o[p+6],D,3225465664);w=i(w,x,u,v,o[p+11],E,643717713);v=i(v,w,x,u,o[p+0],F,3921069994);u=i(u,v,w,x,o[p+5],C,3593408605);x=i(x,u,v,w,o[p+10],D,38016083);w=i(w,x,u,v,o[p+15],E,3634488961);v=i(v,w,x,u,o[p+4],F,3889429448);u=i(u,v,w,x,o[p+9],C,568446438);x=i(x,u,v,w,o[p+14],D,3275163606);w=i(w,x,u,v,o[p+3],E,4107603335);v=i(v,w,x,u,o[p+8],F,1163531501);u=i(u,v,w,x,o[p+13],C,2850285829);x=i(x,u,v,w,o[p+2],D,4243563512);w=i(w,x,u,v,o[p+7],E,1735328473);v=i(v,w,x,u,o[p+12],F,2368359562);u=j(u,v,w,x,o[p+5],G,4294588738);x=j(x,u,v,w,o[p+8],H,2272392833);w=j(w,x,u,v,o[p+11],I,1839030562);v=j(v,w,x,u,o[p+14],J,4259657740);u=j(u,v,w,x,o[p+1],G,2763975236);x=j(x,u,v,w,o[p+4],H,1272893353);w=j(w,x,u,v,o[p+7],I,4139469664);v=j(v,w,x,u,o[p+10],J,320
 0236656);u=j(u,v,w,x,o[p+13],G,681279174);x=j(x,u,v,w,o[p+0],H,3936430074);w=j(w,x,u,v,o[p+3],I,3572445317);v=j(v,w,x,u,o[p+6],J,76029189);u=j(u,v,w,x,o[p+9],G,3654602809);x=j(x,u,v,w,o[p+12],H,3873151461);w=j(w,x,u,v,o[p+15],I,530742520);v=j(v,w,x,u,o[p+2],J,3299628645);u=k(u,v,w,x,o[p+0],K,4096336452);x=k(x,u,v,w,o[p+7],L,1126891415);w=k(w,x,u,v,o[p+14],M,2878612391);v=k(v,w,x,u,o[p+5],N,4237533241);u=k(u,v,w,x,o[p+12],K,1700485571);x=k(x,u,v,w,o[p+3],L,2399980690);w=k(w,x,u,v,o[p+10],M,4293915773);v=k(v,w,x,u,o[p+1],N,2240044497);u=k(u,v,w,x,o[p+8],K,1873313359);x=k(x,u,v,w,o[p+15],L,4264355552);w=k(w,x,u,v,o[p+6],M,2734768916);v=k(v,w,x,u,o[p+13],N,1309151649);u=k(u,v,w,x,o[p+4],K,4149444226);x=k(x,u,v,w,o[p+11],L,3174756917);w=k(w,x,u,v,o[p+2],M,718787259);v=k(v,w,x,u,o[p+9],N,3951481745);u=c(u,q);v=c(v,r);w=c(w,s);x=c(x,t)}var O=m(u)+m(v)+m(w)+m(x);return O.toLowerCase()}
\ No newline at end of file