diff --git a/gms-ui/.env.production b/gms-ui/.env.production index e86ff0cf8400a8ad9c810068c5173b54aba64879..384270e51d87fb2718f8550b2360037269c9ee7f 100644 --- a/gms-ui/.env.production +++ b/gms-ui/.env.production @@ -1,2 +1,2 @@ VUE_APP_API_CLIENT = 'server' -BASE_API_URL = 'http://localhost:8081/' +VUE_APP_API_BASE_URL = 'http://localhost:8081/' diff --git a/gms-ui/src/App.vue b/gms-ui/src/App.vue index ed823404a633fc965f37c41d34c5a7d70b9994b2..98a824186070dd313fff1c0085d45468f54584c4 100644 --- a/gms-ui/src/App.vue +++ b/gms-ui/src/App.vue @@ -4,6 +4,11 @@ <div class="container"> <Main /> </div> + <div id="loading" v-if="loading"> + <div id="spinner-wrapper"> + <font-awesome-icon icon="spinner" spin /> + </div> + </div> </div> </template> @@ -11,7 +16,7 @@ import TopMenu from './components/TopMenu.vue'; import Main from './components/Main.vue'; import { mapState } from 'vuex'; -//import { ToastPlugin } from 'bootstrap-vue'; +import client from 'api-client'; export default { name: 'app', @@ -20,11 +25,11 @@ export default { Main }, computed: mapState({ - model: state => state.model + model: state => state.model, + input: state => state.input, + loading: state => state.loading }), mounted: function() { - this.$store.commit('fetchGroupsModel'); - var self = this; document.addEventListener('apiError', function (event) { self.$bvToast.toast(event.message, { @@ -32,12 +37,21 @@ export default { variant: 'danger', solid: true }); - }) + }); + document.addEventListener('loading', function (event) { + self.$store.commit('setLoading', event.value); + }); + + // retrieve the initial model + client.fetchHomePageModel(this.input) + .then(model => { + this.$store.commit('updateHomePageModel', model); + }); } } </script> -<style> +<style scoped> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; @@ -45,4 +59,23 @@ export default { text-align: center; color: #2c3e50; } + +#loading { + font-size: 40px; + position: absolute; + top: 0; + bottom: 0; + right: 0; + left: 0; + background-color: rgba(255, 255, 255, 0.7); + z-index: 1000; +} + +#spinner-wrapper { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + width: 100%; +} </style> diff --git a/gms-ui/src/api/server/index.js b/gms-ui/src/api/server/index.js index 0e883d9bc93198aee4247f2be3b2c02e5d291e59..46344016b417e96b5e11d043d66c2b4dbfa55250 100644 --- a/gms-ui/src/api/server/index.js +++ b/gms-ui/src/api/server/index.js @@ -1,9 +1,11 @@ -const BASE_API_URL = "http://localhost:8081/" +const BASE_API_URL = process.env.VUE_APP_API_BASE_URL; function apiRequest(url, data) { + loading(true); return new Promise((resolve, reject) => { fetch(url, data) .then(response => { + loading(false); if([200, 201, 204, 400].includes(response.status)) { // valid status codes resolve(response.json()); } else { @@ -11,6 +13,7 @@ function apiRequest(url, data) { } }) .catch(error => { + loading(false); dispatchApiErrorEvent(error); }); }); @@ -29,14 +32,35 @@ function dispatchApiErrorEvent(error) { document.dispatchEvent(event); } +/* For loading animation */ +function loading(value) { + let event = new CustomEvent('loading'); + event.value = value; + document.dispatchEvent(event); +} + export default { - fetchGroupsModel (input) { + fetchHomePageModel (input) { + let url = BASE_API_URL + + 'home?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', + } + }); + }, + fetchPanel (input) { let url = BASE_API_URL - + 'groups?groupId=' + input.selectedGroupId - + '&tab=' + input.selectedTab + + input.selectedTab + + '?groupId=' + input.selectedGroupId + '&paginatorPageSize=' + input.paginatorPageSize - + '&paginatorPage=' + input.paginatorPage - + '&page=' + input.page; + + '&paginatorPage=' + input.paginatorPage; return apiRequest(url, { method: 'GET', cache: 'no-cache', diff --git a/gms-ui/src/components/GroupsBreadcrumb.vue b/gms-ui/src/components/GroupsBreadcrumb.vue index b2458774e7ad2a5cc01a00a0c7f9adffdbe22fd5..ea10fc645f301b70829d84f9ea9f664613c02a4c 100644 --- a/gms-ui/src/components/GroupsBreadcrumb.vue +++ b/gms-ui/src/components/GroupsBreadcrumb.vue @@ -11,6 +11,7 @@ <script> import { mapState } from 'vuex'; +import client from 'api-client'; function buildItems(values) { let groups = []; @@ -22,7 +23,9 @@ function buildItems(values) { } // Activate the last item - groups[groups.length - 1].active = true; + if(groups.length > 0) { + groups[groups.length - 1].active = true; + } return groups; } @@ -31,12 +34,20 @@ export default { name: 'GroupsBreadcrumb', computed: mapState({ model: state => state.model, + input: state => state.input, groups: state => buildItems(state.model.breadcrumbs) }), methods: { changeBreadcrumb: function(groupId) { - this.$store.state.input.selectedGroupId = groupId; - this.$store.commit('fetchGroupsModel'); + this.input.selectedGroupId = groupId; + if(this.input.selectedTab === 'groups') { + client.fetchPanel(this.input) + .then(model => { + this.$store.commit('updateGroupsPanel', model); + }); + } else { + this.$store.commit('setTabIndex', 0); + } } } } diff --git a/gms-ui/src/components/GroupsPanel.vue b/gms-ui/src/components/GroupsPanel.vue index 04b55fc0d7214cc337a9563a8cae9fd451dbf59e..cdc739a87eb93183db2e484b4840bc0d424594a9 100644 --- a/gms-ui/src/components/GroupsPanel.vue +++ b/gms-ui/src/components/GroupsPanel.vue @@ -56,6 +56,7 @@ import RenameGroupModal from './modals/RenameGroupModal.vue'; import ConfirmRemoveGroupModal from './modals/ConfirmRemoveGroupModal.vue'; import { mapState, mapActions } from 'vuex'; +import client from 'api-client'; export default { name: 'GroupsPanel', @@ -80,7 +81,10 @@ export default { methods: { openGroup: function(group) { this.$store.state.input.selectedGroupId = group.groupId; - this.$store.commit('fetchGroupsModel'); + client.fetchPanel(this.input) + .then(model => { + this.$store.commit('updateGroupsPanel', model); + }); }, openRenameGroupModal: function(group) { this.$refs.renameGroupModal.openRenameGroupModal(group); diff --git a/gms-ui/src/components/Main.vue b/gms-ui/src/components/Main.vue index 27862fd25a5a512f9aefc7d2d0a681d2e586e895..1e9a6f17976c2185571db0c488f3ecd8fc446e56 100644 --- a/gms-ui/src/components/Main.vue +++ b/gms-ui/src/components/Main.vue @@ -2,7 +2,7 @@ <div> <GroupsBreadcrumb /> <b-card no-body> - <b-tabs content-class="mt-3" card v-on:input="tabChanged"> + <b-tabs content-class="mt-3" card v-on:input="tabChanged" v-model="input.tabIndex"> <GroupsPanel /> <MembersPanel /> <PermissionsPanel /> @@ -29,6 +29,7 @@ import AddGroupModal from './modals/AddGroupModal.vue' import AddMemberModal from './modals/AddMemberModal.vue' import AddPermissionModal from './modals/AddPermissionModal.vue' import { mapState } from 'vuex'; +import client from 'api-client'; export default { name: 'Main', @@ -43,6 +44,7 @@ export default { }, computed: mapState({ model: state => state.model, + input: state => state.input, showAddMemberBtn: state => state.model.permission === 'ADMIN' && state.input.selectedTab === 'members', showAddCollaboratorBtn: state => state.model.permission === 'MANAGE_MEMBERS' && state.input.selectedTab === 'members', showAddGroupBtn: state => state.model.permission === 'ADMIN' && state.input.selectedTab === 'groups', @@ -62,9 +64,22 @@ export default { tab = 'permissions'; break; } - if(this.$store.state.input.selectedTab !== tab) { - this.$store.state.input.selectedTab = tab; - this.$store.commit('fetchGroupsModel'); + 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; + } + }); } }, openAddGroupModal: function() { diff --git a/gms-ui/src/main.js b/gms-ui/src/main.js index 9fb81c612d27ae5ef1f51dd5516bd8cab7609e85..1d6dc7e58bba7da507cc76cf736a39f203640430 100644 --- a/gms-ui/src/main.js +++ b/gms-ui/src/main.js @@ -5,10 +5,10 @@ import store from './store.js' import './plugins/bootstrap-vue' import App from './App.vue' import { library } from '@fortawesome/fontawesome-svg-core' -import { faTrash, faEdit } from '@fortawesome/free-solid-svg-icons' +import { faTrash, faEdit, faSpinner } from '@fortawesome/free-solid-svg-icons' import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' -library.add(faTrash, faEdit); +library.add(faTrash, faEdit, faSpinner); Vue.component('font-awesome-icon', FontAwesomeIcon); diff --git a/gms-ui/src/plugins/bootstrap-vue.js b/gms-ui/src/plugins/bootstrap-vue.js index c29a066a30dc2d0588c297632514a86edc1989cd..b9d414fabeae0480e997423c3a02ef5414b5a0b3 100644 --- a/gms-ui/src/plugins/bootstrap-vue.js +++ b/gms-ui/src/plugins/bootstrap-vue.js @@ -5,4 +5,3 @@ import 'bootstrap/dist/css/bootstrap.min.css'; import 'bootstrap-vue/dist/bootstrap-vue.css'; Vue.use(BootstrapVue); -//Vue.use(ToastPlugin); diff --git a/gms-ui/src/store.js b/gms-ui/src/store.js index ddfed3b0703b3cb8d4014d015f11f62efafcb57e..48e0677fe93f176c094fd219f80b298b7a9f8087 100644 --- a/gms-ui/src/store.js +++ b/gms-ui/src/store.js @@ -2,38 +2,54 @@ import Vue from 'vue'; import Vuex from 'vuex'; -import client from 'api-client'; Vue.use(Vuex); export default new Vuex.Store({ state: { // values populated from API calls - model: null, + model: { + breadcrumbs: [], + groupsPanel: null, + permissionsPanel: null, + membersPanel: null, + permission: null, + user: null + }, // values used to perform API calls input: { selectedGroupId: 'ROOT', paginatorPageSize: 20, paginatorPage: 1, selectedTab: 'groups', - page: 'main' - } + tabIndex: 0 + }, + loading: false }, mutations: { - fetchGroupsModel(state) { - client.fetchGroupsModel(this.state.input) - .then(model => { - this.state.model = model; - }); + updateHomePageModel(state, model) { + this.state.model.breadcrumbs = model.breadcrumbs; + this.state.model.groupsPanel = model.groupsPanel; + this.state.model.permission = model.permission; + this.state.model.user = model.user; }, - updateGroupsPanel(state, groupsPanel) { - this.state.model.groupsPanel = groupsPanel; + updateGroupsPanel(state, model) { + this.state.model.breadcrumbs = model.breadcrumbs; + this.state.model.groupsPanel = model.groupsPanel; + this.state.model.permission = model.permission; }, updatePermissionsPanel(state, permissionsPanel) { this.state.model.permissionsPanel = permissionsPanel; }, updateMembersPanel(state, membersPanel) { this.state.model.membersPanel = membersPanel; + }, + setTabIndex(state, tabIndex) { + // this will trigger the tabChanged() method in Main.vue + this.state.input.tabIndex = tabIndex; + }, + setLoading(state, loading) { + this.state.loading = loading; } }, getters: { diff --git a/gms-ui/vue.config.js b/gms-ui/vue.config.js index 06fc6e5e208122a1281dfe9eecba8a89a43b69d4..faddadb79b9d4c35bd8c0740575b9ffff2550f98 100644 --- a/gms-ui/vue.config.js +++ b/gms-ui/vue.config.js @@ -1,4 +1,4 @@ -const path = require('path') +const path = require('path'); module.exports = { chainWebpack: config => {