diff --git a/gms-ui/src/App.vue b/gms-ui/src/App.vue
index 98a824186070dd313fff1c0085d45468f54584c4..6bc6368123010a1fa486de03fe63bf4f711141a6 100644
--- a/gms-ui/src/App.vue
+++ b/gms-ui/src/App.vue
@@ -6,7 +6,7 @@
     </div>
     <div id="loading" v-if="loading">
       <div id="spinner-wrapper">
-        <font-awesome-icon icon="spinner" spin />
+        <b-spinner variant="primary" style="width: 3rem; height: 3rem;" label="Loading"></b-spinner>
       </div>
     </div>
   </div>
@@ -61,7 +61,6 @@ export default {
 }
 
 #loading {
-  font-size: 40px;
   position: absolute;
   top: 0;
   bottom: 0;
diff --git a/gms-ui/src/api/server/index.js b/gms-ui/src/api/server/index.js
index 46344016b417e96b5e11d043d66c2b4dbfa55250..1c9b15ceaa9c961317d759f48ea4819737fccb57 100644
--- a/gms-ui/src/api/server/index.js
+++ b/gms-ui/src/api/server/index.js
@@ -55,10 +55,39 @@ export default {
       }
     });
   },
-  fetchPanel (input) {
+  fetchGroupsTab (input) {
     let url = BASE_API_URL
-            + input.selectedTab
-            + '?groupId=' + input.selectedGroupId
+            + 'groups?groupId=' + input.selectedGroupId
+            + '&paginatorPageSize=' + input.paginatorPageSize
+            + '&paginatorPage=' + input.paginatorPage;
+    return apiRequest(url, {
+      method: 'GET',
+      cache: 'no-cache',
+      credentials: 'include',
+      headers: {
+        'Content-Type': 'application/json',
+        'Accept': 'application/json',
+      }
+    });
+  },
+  fetchMembersPanel (input) {
+    let url = BASE_API_URL
+            + 'members?groupId=' + input.selectedGroupId
+            + '&paginatorPageSize=' + input.paginatorPageSize
+            + '&paginatorPage=' + input.paginatorPage;
+    return apiRequest(url, {
+      method: 'GET',
+      cache: 'no-cache',
+      credentials: 'include',
+      headers: {
+        'Content-Type': 'application/json',
+        'Accept': 'application/json',
+      }
+    });
+  },
+  fetchPermissionsPanel (input) {
+    let url = BASE_API_URL
+            + 'permissions?groupId=' + input.selectedGroupId
             + '&paginatorPageSize=' + input.paginatorPageSize
             + '&paginatorPage=' + input.paginatorPage;
     return apiRequest(url, {
diff --git a/gms-ui/src/components/GroupsBreadcrumb.vue b/gms-ui/src/components/GroupsBreadcrumb.vue
index ea10fc645f301b70829d84f9ea9f664613c02a4c..e443dff98fe1ca00ff3d3bcbbaf84e219a629433 100644
--- a/gms-ui/src/components/GroupsBreadcrumb.vue
+++ b/gms-ui/src/components/GroupsBreadcrumb.vue
@@ -41,9 +41,9 @@ export default {
     changeBreadcrumb: function(groupId) {
       this.input.selectedGroupId = groupId;
       if(this.input.selectedTab === 'groups') {
-        client.fetchPanel(this.input)
+        client.fetchGroupsTab(this.input)
           .then(model => {
-            this.$store.commit('updateGroupsPanel', model);
+            this.$store.commit('updateGroups', model);
           });
       } else {
         this.$store.commit('setTabIndex', 0);
diff --git a/gms-ui/src/components/GroupsPanel.vue b/gms-ui/src/components/GroupsPanel.vue
index cdc739a87eb93183db2e484b4840bc0d424594a9..0010c380ad0d70a714d7cabbda03ee4220a9fb92 100644
--- a/gms-ui/src/components/GroupsPanel.vue
+++ b/gms-ui/src/components/GroupsPanel.vue
@@ -20,33 +20,9 @@
           </span>
         </b-list-group-item>
       </b-list-group>
+      <p v-if="model.groupsPanel.items.length === 0">No groups</p>
     </div>
-    <div class="row" v-if="model.groupsPanel !== null">
-      <div class="col-md-9">
-        <b-pagination
-          v-model="model.groupsPanel.currentPage"
-          :total-rows="model.groupsPanel.totalItems"
-          :per-page="model.groupsPanel.pageSize"
-          aria-controls="groups-list"
-          align="center"
-          v-on:change="setPage"
-        ></b-pagination>
-      </div>
-      <div class="col-md-3">
-        <b-container fluid>
-          <b-row>
-            <b-col sm="5">
-              <label for="page-size">Page size:</label>
-            </b-col>
-            <b-col sm="6">
-              <b-form-select id="page-size" v-model="input.paginatorPageSize" :options="pageSizeOptions" v-on:change="changePageSize"></b-form-select>
-            </b-col>
-          </b-row>
-        </b-container>
-      </div>
-    </div>
-    <div class="text-center">
-    </div>
+    <Paginator :paginatedPanel="model.groupsPanel" :onUpdate="updatePagination" />
     <RenameGroupModal ref="renameGroupModal" />
     <ConfirmRemoveGroupModal ref="confirmRemoveGroupModal" />
   </b-tab>
@@ -55,6 +31,7 @@
 <script>
 import RenameGroupModal from './modals/RenameGroupModal.vue';
 import ConfirmRemoveGroupModal from './modals/ConfirmRemoveGroupModal.vue';
+import Paginator from './Paginator.vue';
 import { mapState, mapActions } from 'vuex';
 import client from 'api-client';
 
@@ -62,7 +39,8 @@ export default {
   name: 'GroupsPanel',
   components: {
     RenameGroupModal,
-    ConfirmRemoveGroupModal
+    ConfirmRemoveGroupModal,
+    Paginator
   },
   computed: mapState({
     model: state => state.model,
@@ -70,20 +48,15 @@ export default {
   }),
   data: function() {
     return {
-      pageSizeOptions: [
-        { value: 20, text: "20" },
-        { value: 50, text: "50" },
-        { value: 100, text: "100" }
-      ],
       groupFilter: ''
     };
   },
   methods: {
     openGroup: function(group) {
       this.$store.state.input.selectedGroupId = group.groupId;
-      client.fetchPanel(this.input)
+      client.fetchGroupsTab(this.input)
         .then(model => {
-          this.$store.commit('updateGroupsPanel', model);
+          this.$store.commit('updateGroups', model);
         });
     },
     openRenameGroupModal: function(group) {
@@ -92,14 +65,14 @@ export default {
     openRemoveGroupModal: function(group) {
       this.$refs.confirmRemoveGroupModal.openRemoveGroupModal(group);
     },
-    setPage: function(page) {
-      console.log('setPage ', page);
-    },
-    changePageSize: function(pageSize) {
-      console.log('changePageSize', pageSize);
-    },
     filterGroups: function() {
       console.log('filterGroups', this.groupFilter);
+    },
+    updatePagination: function() {
+      client.fetchGroupsTab(this.input)
+        .then(model => {
+          this.$store.commit('updateGroupsPanel', model.groupsPanel);
+        });
     }
   }
 }
diff --git a/gms-ui/src/components/Main.vue b/gms-ui/src/components/Main.vue
index 1e9a6f17976c2185571db0c488f3ecd8fc446e56..ef00c185da029002f31cdc195cc018b6753ebacf 100644
--- a/gms-ui/src/components/Main.vue
+++ b/gms-ui/src/components/Main.vue
@@ -66,20 +66,26 @@ export default {
       }
       if(this.input.selectedTab !== tab) {
         this.input.selectedTab = tab;
-        client.fetchPanel(this.input)
-          .then(model => {
-            switch(this.input.selectedTab) {
-              case 'groups':
-                this.$store.commit('updateGroupsPanel', model);
-                break;
-              case 'members':
-                this.$store.commit('updateMembersPanel', model);
-                break;
-              case 'permissions':
-                this.$store.commit('updatePermissionsPanel', model);
-                break;
-            }
-          });
+        switch(this.input.selectedTab) {
+          case 'groups':
+            client.fetchGroupsTab(this.input)
+              .then(model => {
+                this.$store.commit('updateGroups', model);
+              });
+            break;
+          case 'members':
+            client.fetchMembersPanel(this.input)
+              .then(panel => {
+                this.$store.commit('updateMembersPanel', panel);
+              });
+            break;
+          case 'permissions':
+            client.fetchPermissionsPanel(this.input)
+              .then(panel => {
+                this.$store.commit('updatePermissionsPanel', panel);
+              });
+            break;
+        }
       }
     },
     openAddGroupModal: function() {
diff --git a/gms-ui/src/components/MembersPanel.vue b/gms-ui/src/components/MembersPanel.vue
index d60a9e794017299d76781421c2d36bc067fa5062..41811cc66d4b11f4d0c461313d0168ef163f9a9c 100644
--- a/gms-ui/src/components/MembersPanel.vue
+++ b/gms-ui/src/components/MembersPanel.vue
@@ -1,5 +1,5 @@
 <template>
-  <b-tab title="Members">
+  <b-tab title="Members" v-if="model.permission === 'ADMIN' || model.permission === 'MANAGE_MEMBERS' || model.permission === 'VIEW_MEMBERS'">
     <div v-if="model.membersPanel !== null">
       <b-list-group v-for="member in model.membersPanel.items" id="members-list">
         <b-list-group-item href="#">
@@ -13,17 +13,9 @@
           </span>
         </b-list-group-item>
       </b-list-group>
+      <p v-if="model.membersPanel.items.length === 0">No direct members</p>
     </div>
-    <div class="text-center" v-if="model.membersPanel !== null">
-      <b-pagination
-        v-model="model.membersPanel.page"
-        :total-rows="model.membersPanel.totalItems"
-        :per-page="model.membersPanel.pageSize"
-        aria-controls="members-list"
-        align="center"
-        v-on:change="setPage"
-      ></b-pagination>
-    </div>
+    <Paginator :paginatedPanel="model.membersPanel" :onUpdate="updatePagination" />
     <ConfirmRemoveMemberModal ref="confirmRemoveMemberModal" />
   </b-tab>
 </template>
@@ -31,17 +23,20 @@
 <script>
 import client from 'api-client';
 import User from './User.vue';
-import ConfirmRemoveMemberModal from './modals/ConfirmRemoveMemberModal.vue'
+import Paginator from './Paginator.vue';
+import ConfirmRemoveMemberModal from './modals/ConfirmRemoveMemberModal.vue';
 import { mapState } from 'vuex';
 
 export default {
   name: 'MembersPanel',
   components: {
     User,
-    ConfirmRemoveMemberModal
+    ConfirmRemoveMemberModal,
+    Paginator
   },
   computed: mapState({
-    model: state => state.model
+    model: state => state.model,
+    input: state => state.input
   }),
   methods: {
     openRemoveMemberModal: function(member) {
@@ -50,8 +45,11 @@ export default {
           this.$refs.confirmRemoveMemberModal.openRemoveMemberModal(member, memberPermission);
         });
     },
-    setPage: function(page) {
-      console.log('setPage ', page);
+    updatePagination: function() {
+      client.fetchMembersPanel(this.input)
+        .then(panel => {
+          this.$store.commit('updateMembersPanel', panel);
+        });
     }
   }
 }
diff --git a/gms-ui/src/components/Paginator.vue b/gms-ui/src/components/Paginator.vue
new file mode 100644
index 0000000000000000000000000000000000000000..cc634a9836143b74ee5d8090bb06d4cf4a629a7c
--- /dev/null
+++ b/gms-ui/src/components/Paginator.vue
@@ -0,0 +1,60 @@
+<template>
+  <div class="row mt-4" v-if="paginatedPanel !== null && paginatedPanel.totalItems > 0">
+    <div class="col-md-8">
+      <b-pagination
+        v-model="paginatedPanel.currentPage"
+        :total-rows="paginatedPanel.totalItems"
+        :per-page="paginatedPanel.pageSize"
+        aria-controls="groups-list"
+        align="center"
+        v-on:change="setPage"
+      ></b-pagination>
+      <p>Total items: {{paginatedPanel.totalItems}}</p>
+    </div>
+    <div class="col-md-4">
+        <b-row>
+          <b-col sm="5">
+            <label for="page-size">Page size:</label>
+          </b-col>
+          <b-col sm="6">
+            <b-form-select id="page-size" v-model="input.paginatorPageSize" :options="pageSizeOptions" v-on:change="changePageSize"></b-form-select>
+          </b-col>
+        </b-row>
+    </div>
+  </div>
+</template>
+
+<script>
+import { mapState } from 'vuex';
+
+export default {
+  name: 'Paginator',
+  props: {
+    paginatedPanel: Object,
+    onUpdate: Function
+  },
+  computed: mapState({
+    input: state => state.input
+  }),
+  data: function() {
+    return {
+      pageSizeOptions: [
+        { value: 5, text: "5" },
+        { value: 20, text: "20" },
+        { value: 50, text: "50" },
+        { value: 100, text: "100" }
+      ]
+    };
+  },
+  methods: {
+    setPage: function(page) {
+      this.input.paginatorPage = page;
+      this.onUpdate();
+    },
+    changePageSize: function(pageSize) {
+      this.input.paginatorPageSize = pageSize;
+      this.onUpdate();
+    }
+  }
+}
+</script>
diff --git a/gms-ui/src/components/PermissionsPanel.vue b/gms-ui/src/components/PermissionsPanel.vue
index 9c555838941cd31d911dacbf5b9e07eca3e13c78..72b57e8b299c3f6484eaa1e7257b3ab7cf5cbd0e 100644
--- a/gms-ui/src/components/PermissionsPanel.vue
+++ b/gms-ui/src/components/PermissionsPanel.vue
@@ -1,5 +1,5 @@
 <template>
-  <b-tab title="Permissions">
+  <b-tab title="Permissions" v-if="model.permission === 'ADMIN'">
     <div v-if="model.permissionsPanel !== null">
       <table class="table b-table table-striped table-hover">
         <thead>
@@ -23,23 +23,17 @@
           </tr>
         </tbody>
       </table>
+      <p v-if="model.permissionsPanel.items.length === 0">No direct permissions</p>
     </div>
-    <div class="text-center" v-if="model.permissionsPanel !== null">
-      <b-pagination
-        v-model="model.permissionsPanel.currentPage"
-        :total-rows="model.permissionsPanel.totalItems"
-        :per-page="model.permissionsPanel.pageSize"
-        aria-controls="permissions-list"
-        align="center"
-        v-on:change="setPage"
-      ></b-pagination>
-    </div>
+    <Paginator :paginatedPanel="model.permissionsPanel" :onUpdate="updatePagination" />
     <ConfirmRemovePermissionModal ref="confirmRemovePermissionModal" />
   </b-tab>
 </template>
 
 <script>
+import client from 'api-client';
 import User from './User.vue';
+import Paginator from './Paginator.vue';
 import ConfirmRemovePermissionModal from './modals/ConfirmRemovePermissionModal.vue'
 import { mapState } from 'vuex';
 
@@ -47,17 +41,22 @@ export default {
   name: 'PermissionsPanel',
   components: {
     User,
-    ConfirmRemovePermissionModal
+    ConfirmRemovePermissionModal,
+    Paginator
   },
   computed: mapState({
     model: state => state.model,
+    input: state => state.input
   }),
   methods: {
     openRemovePermissionModal: function(user) {
       this.$refs.confirmRemovePermissionModal.openRemovePermissionModal(user);
     },
-    setPage: function(page) {
-      console.log('setPage ', page);
+    updatePagination: function() {
+      client.fetchPermissionsPanel(this.input)
+        .then(panel => {
+          this.$store.commit('updatePermissionsPanel', panel);
+        });
     }
   }
 }
diff --git a/gms-ui/src/store.js b/gms-ui/src/store.js
index 48e0677fe93f176c094fd219f80b298b7a9f8087..58d25d24360808a4c836883d0c98ee8d2a114013 100644
--- a/gms-ui/src/store.js
+++ b/gms-ui/src/store.js
@@ -33,11 +33,14 @@ export default new Vuex.Store({
       this.state.model.permission = model.permission;
       this.state.model.user = model.user;
     },
-    updateGroupsPanel(state, model) {
+    updateGroups(state, model) {
       this.state.model.breadcrumbs = model.breadcrumbs;
       this.state.model.groupsPanel = model.groupsPanel;
       this.state.model.permission = model.permission;
     },
+    updateGroupsPanel(state, groupsPanel) {
+      this.state.model.groupsPanel = groupsPanel;
+    },
     updatePermissionsPanel(state, permissionsPanel) {
       this.state.model.permissionsPanel = permissionsPanel;
     },