Skip to content
...@@ -50,9 +50,9 @@ public class GetUserGroupsTest extends BaseGmsClientTest { ...@@ -50,9 +50,9 @@ public class GetUserGroupsTest extends BaseGmsClientTest {
CompletableFuture response = CompletableFuture.completedFuture(getMockedStreamResponse(200, body)); CompletableFuture response = CompletableFuture.completedFuture(getMockedStreamResponse(200, body));
when(httpClient.sendAsync(any(), any())).thenReturn(response); when(httpClient.sendAsync(any(), any())).thenReturn(response);
List<String> groups = gmsClient.listGroups("LBT."); List<String> groups = gmsClient.listGroups("LBT", false);
verify(httpClient, times(1)).sendAsync(endpointEq("GET", "ws/jwt/list/LBT."), any()); verify(httpClient, times(1)).sendAsync(endpointEq("GET", "groups?parent=LBT&recursive=false"), any());
assertEquals(2, groups.size()); assertEquals(2, groups.size());
assertEquals("INAF", groups.get(0)); assertEquals("INAF", groups.get(0));
......
...@@ -28,6 +28,6 @@ public class RemoveMemberTest extends BaseGmsClientTest { ...@@ -28,6 +28,6 @@ public class RemoveMemberTest extends BaseGmsClientTest {
when(httpClient.sendAsync(any(), any())).thenReturn(response); when(httpClient.sendAsync(any(), any())).thenReturn(response);
gmsClient.removeMember("LBT.INAF", "user"); gmsClient.removeMember("LBT.INAF", "user");
verify(httpClient, times(1)).sendAsync(endpointEq("DELETE", "ws/jwt/membership/LBT.INAF?user_id=user"), any()); verify(httpClient, times(1)).sendAsync(endpointEq("DELETE", "membership?group=LBT.INAF&user_id=user"), any());
} }
} }
...@@ -28,6 +28,6 @@ public class RemovePermissionTest extends BaseGmsClientTest { ...@@ -28,6 +28,6 @@ public class RemovePermissionTest extends BaseGmsClientTest {
when(httpClient.sendAsync(any(), any())).thenReturn(response); when(httpClient.sendAsync(any(), any())).thenReturn(response);
gmsClient.removePermission("LBT.INAF", "user"); gmsClient.removePermission("LBT.INAF", "user");
verify(httpClient, times(1)).sendAsync(endpointEq("DELETE", "ws/jwt/permission/LBT.INAF?user_id=user"), any()); verify(httpClient, times(1)).sendAsync(endpointEq("DELETE", "permission?group=LBT.INAF&user_id=user"), any());
} }
} }
...@@ -9,6 +9,12 @@ ...@@ -9,6 +9,12 @@
<b-spinner variant="primary" style="width: 3rem; height: 3rem;" label="Loading"></b-spinner> <b-spinner variant="primary" style="width: 3rem; height: 3rem;" label="Loading"></b-spinner>
</div> </div>
</div> </div>
<div class="mt-4">
<p class="text-center">&mdash;&nbsp; Powered by
<img src="@/assets/logo-ia2-small.jpg" alt="logo IA2" /> &nbsp;
<a href="https://www.ia2.inaf.it/" target="_blank">IA2</a> &nbsp;&mdash;
</p>
</div>
</div> </div>
</template> </template>
......
...@@ -54,7 +54,7 @@ function loading(value) { ...@@ -54,7 +54,7 @@ function loading(value) {
export default { export default {
fetchHomePageModel(input) { fetchHomePageModel(input) {
let url = BASE_API_URL + let url = BASE_API_URL +
'home?groupId=' + input.selectedGroupId + 'ui/home?groupId=' + input.selectedGroupId +
'&paginatorPageSize=' + input.paginatorPageSize + '&paginatorPageSize=' + input.paginatorPageSize +
'&paginatorPage=' + input.paginatorPage; '&paginatorPage=' + input.paginatorPage;
return apiRequest({ return apiRequest({
...@@ -70,7 +70,7 @@ export default { ...@@ -70,7 +70,7 @@ export default {
}, },
fetchGroupsTab(input) { fetchGroupsTab(input) {
let url = BASE_API_URL + let url = BASE_API_URL +
'groups?groupId=' + input.selectedGroupId + 'ui/groups?groupId=' + input.selectedGroupId +
'&paginatorPageSize=' + input.paginatorPageSize + '&paginatorPageSize=' + input.paginatorPageSize +
'&paginatorPage=' + input.paginatorPage + '&paginatorPage=' + input.paginatorPage +
'&onlyPanel=false'; '&onlyPanel=false';
...@@ -90,7 +90,7 @@ export default { ...@@ -90,7 +90,7 @@ export default {
}, },
fetchGroupsPanel(input) { fetchGroupsPanel(input) {
let url = BASE_API_URL + let url = BASE_API_URL +
'groups?groupId=' + input.selectedGroupId + 'ui/groups?groupId=' + input.selectedGroupId +
'&paginatorPageSize=' + input.paginatorPageSize + '&paginatorPageSize=' + input.paginatorPageSize +
'&paginatorPage=' + input.paginatorPage + '&paginatorPage=' + input.paginatorPage +
'&onlyPanel=true'; '&onlyPanel=true';
...@@ -110,7 +110,7 @@ export default { ...@@ -110,7 +110,7 @@ export default {
}, },
fetchMembersPanel(input) { fetchMembersPanel(input) {
let url = BASE_API_URL + let url = BASE_API_URL +
'members?groupId=' + input.selectedGroupId + 'ui/members?groupId=' + input.selectedGroupId +
'&paginatorPageSize=' + input.paginatorPageSize + '&paginatorPageSize=' + input.paginatorPageSize +
'&paginatorPage=' + input.paginatorPage; '&paginatorPage=' + input.paginatorPage;
return apiRequest({ return apiRequest({
...@@ -126,7 +126,7 @@ export default { ...@@ -126,7 +126,7 @@ export default {
}, },
fetchPermissionsPanel(input) { fetchPermissionsPanel(input) {
let url = BASE_API_URL + let url = BASE_API_URL +
'permissions?groupId=' + input.selectedGroupId + 'ui/permissions?groupId=' + input.selectedGroupId +
'&paginatorPageSize=' + input.paginatorPageSize + '&paginatorPageSize=' + input.paginatorPageSize +
'&paginatorPage=' + input.paginatorPage; '&paginatorPage=' + input.paginatorPage;
return apiRequest({ return apiRequest({
...@@ -141,7 +141,7 @@ export default { ...@@ -141,7 +141,7 @@ export default {
}); });
}, },
addGroup(newGroupName, leaf, input) { addGroup(newGroupName, leaf, input) {
let url = BASE_API_URL + 'group'; let url = BASE_API_URL + 'ui/group';
return apiRequest({ return apiRequest({
method: 'POST', method: 'POST',
url: url, url: url,
...@@ -162,7 +162,7 @@ export default { ...@@ -162,7 +162,7 @@ export default {
}, true, true); }, true, true);
}, },
updateGroup(groupId, newGroupName, leaf, input) { updateGroup(groupId, newGroupName, leaf, input) {
let url = BASE_API_URL + 'group/' + groupId; let url = BASE_API_URL + 'ui/group/' + groupId;
return apiRequest({ return apiRequest({
method: 'PUT', method: 'PUT',
url: url, url: url,
...@@ -182,7 +182,7 @@ export default { ...@@ -182,7 +182,7 @@ export default {
}, true, true); }, true, true);
}, },
removeGroup(groupId, input) { removeGroup(groupId, input) {
let url = BASE_API_URL + 'group/' + groupId + let url = BASE_API_URL + 'ui/group/' + groupId +
'?paginatorPageSize=' + input.paginatorPageSize + '?paginatorPageSize=' + input.paginatorPageSize +
'&paginatorPage=' + input.paginatorPage; '&paginatorPage=' + input.paginatorPage;
if (input.searchFilter !== null) { if (input.searchFilter !== null) {
...@@ -200,7 +200,7 @@ export default { ...@@ -200,7 +200,7 @@ export default {
}); });
}, },
searchUser(searchInput) { searchUser(searchInput) {
let url = BASE_API_URL + 'users?search=' + searchInput; let url = BASE_API_URL + 'ui/users?search=' + searchInput;
return apiRequest({ return apiRequest({
method: 'GET', method: 'GET',
...@@ -213,7 +213,7 @@ export default { ...@@ -213,7 +213,7 @@ export default {
}); });
}, },
addPermission(userId, permission, input, override) { addPermission(userId, permission, input, override) {
let url = BASE_API_URL + 'permission'; let url = BASE_API_URL + 'ui/permission';
return apiRequest({ return apiRequest({
method: 'POST', method: 'POST',
...@@ -235,7 +235,7 @@ export default { ...@@ -235,7 +235,7 @@ export default {
}); });
}, },
updatePermission(groupId, userId, permission) { updatePermission(groupId, userId, permission) {
let url = BASE_API_URL + 'permission'; let url = BASE_API_URL + 'ui/permission';
return apiRequest({ return apiRequest({
method: 'PUT', method: 'PUT',
...@@ -254,7 +254,7 @@ export default { ...@@ -254,7 +254,7 @@ export default {
}); });
}, },
getPermission(groupId, userId) { getPermission(groupId, userId) {
let url = BASE_API_URL + 'permission?groupId=' + groupId + '&userId=' + userId; let url = BASE_API_URL + 'ui/permission?groupId=' + groupId + '&userId=' + userId;
return apiRequest({ return apiRequest({
method: 'GET', method: 'GET',
...@@ -268,7 +268,7 @@ export default { ...@@ -268,7 +268,7 @@ export default {
}); });
}, },
addMember(userId, permission, input) { addMember(userId, permission, input) {
let url = BASE_API_URL + 'member'; let url = BASE_API_URL + 'ui/member';
return apiRequest({ return apiRequest({
method: 'POST', method: 'POST',
...@@ -289,7 +289,7 @@ export default { ...@@ -289,7 +289,7 @@ export default {
}); });
}, },
removeMember(userId, removeAlsoPermission, input) { removeMember(userId, removeAlsoPermission, input) {
let url = BASE_API_URL + 'member' + let url = BASE_API_URL + 'ui/member' +
'?groupId=' + input.selectedGroupId + '?groupId=' + input.selectedGroupId +
'&userId=' + userId + '&userId=' + userId +
'&removeAlsoPermission=' + removeAlsoPermission + '&removeAlsoPermission=' + removeAlsoPermission +
...@@ -307,7 +307,7 @@ export default { ...@@ -307,7 +307,7 @@ export default {
}); });
}, },
removePermission(userId, input) { removePermission(userId, input) {
let url = BASE_API_URL + 'permission' + let url = BASE_API_URL + 'ui/permission' +
'?groupId=' + input.selectedGroupId + '?groupId=' + input.selectedGroupId +
'&userId=' + userId + '&userId=' + userId +
'&paginatorPageSize=' + input.paginatorPageSize + '&paginatorPageSize=' + input.paginatorPageSize +
...@@ -324,8 +324,10 @@ export default { ...@@ -324,8 +324,10 @@ export default {
}); });
}, },
search(input) { search(input) {
let url = BASE_API_URL + 'search?query=' + input.genericSearch.filter + let url = BASE_API_URL + 'ui/search?query=' + input.genericSearch.filter +
'&page=' + input.genericSearch.paginatorPage + '&pageSize=' + input.genericSearch.paginatorPageSize; '&paginatorPage=' + input.genericSearch.paginatorPage +
'&paginatorPageSize=' + input.genericSearch.paginatorPageSize +
'&users=' + input.genericSearch.users + "&groups=" + input.genericSearch.groups;
return apiRequest({ return apiRequest({
method: 'GET', method: 'GET',
...@@ -339,7 +341,7 @@ export default { ...@@ -339,7 +341,7 @@ export default {
}); });
}, },
openUserSearchResult(userId) { openUserSearchResult(userId) {
let url = BASE_API_URL + 'search/user/' + userId; let url = BASE_API_URL + 'ui/search/user/' + userId;
return apiRequest({ return apiRequest({
method: 'GET', method: 'GET',
...@@ -353,7 +355,7 @@ export default { ...@@ -353,7 +355,7 @@ export default {
}); });
}, },
keepAlive() { keepAlive() {
let url = BASE_API_URL + 'keepAlive'; let url = BASE_API_URL + 'ui/keepAlive';
return apiRequest({ return apiRequest({
method: 'GET', method: 'GET',
...@@ -365,7 +367,7 @@ export default { ...@@ -365,7 +367,7 @@ export default {
}, false); }, false);
}, },
deleteInvitedRegistration(requestId, groupId) { deleteInvitedRegistration(requestId, groupId) {
let url = BASE_API_URL + 'registration?' + let url = BASE_API_URL + 'ui/registration?' +
'request_id=' + requestId + '&group_id=' + groupId; 'request_id=' + requestId + '&group_id=' + groupId;
return apiRequest({ return apiRequest({
method: 'DELETE', method: 'DELETE',
......
<template> <template>
<div class="mt-sm-3"> <div class="mt-sm-3">
<b-form inline>
Includes:
<b-form-checkbox class="ml-4" v-model="input.genericSearch.users" @input="updateTypeFilter">users</b-form-checkbox>
<b-form-checkbox class="ml-4" v-model="input.genericSearch.groups" @input="updateTypeFilter">groups</b-form-checkbox>
</b-form>
<div> <div>
<div v-if="model.genericSearchResults.items && model.genericSearchResults.items.length > 0"> <div v-if="model.genericSearchResults.items && model.genericSearchResults.items.length > 0">
<p>Search results:</p> <p>Search results:</p>
...@@ -40,7 +45,7 @@ export default { ...@@ -40,7 +45,7 @@ export default {
'$route': 'updateSearchResults' '$route': 'updateSearchResults'
}, },
methods: { methods: {
openSearchResult: function(result) { openSearchResult(result) {
switch (result.type) { switch (result.type) {
case 'GROUP': case 'GROUP':
this.$store.dispatch('openGroup', result.id); this.$store.dispatch('openGroup', result.id);
...@@ -50,7 +55,11 @@ export default { ...@@ -50,7 +55,11 @@ export default {
break; break;
} }
}, },
updateSearchResults: function() { updateTypeFilter() {
this.input.genericSearch.paginatorPage = 1;
this.updateSearchResults();
},
updateSearchResults() {
this.$store.dispatch('search', this.$route.query.q); this.$store.dispatch('search', this.$route.query.q);
} }
} }
......
...@@ -6,15 +6,20 @@ ...@@ -6,15 +6,20 @@
<span v-if="group.active">{{group.groupName}}</span> <span v-if="group.active">{{group.groupName}}</span>
</li> </li>
</ol> </ol>
<a v-if="currentGroup" :href="'group/status?groupId=' + currentGroup.groupId" :download="currentGroup.groupName + '.csv'" id="csv-status-download" title="Download CSV"> <span id="breadcrumb-buttons">
<font-awesome-icon icon="download"></font-awesome-icon> <a href="#" v-on:click.stop.prevent="openEditGroupModal(currentGroup)" title="Edit" v-if="currentGroup && isAdmin && currentGroup.groupId !== 'ROOT'">
</a> <font-awesome-icon icon="edit"></font-awesome-icon>
</a>
&nbsp;
<a v-if="currentGroup" :href="'group/status?groupId=' + currentGroup.groupId" :download="currentGroup.groupName + '.csv'" title="Download CSV">
<font-awesome-icon icon="download"></font-awesome-icon>
</a>
</span>
</nav> </nav>
</template> </template>
<script> <script>
import { mapState } from 'vuex'; import { mapState } from 'vuex';
import client from 'api-client';
function buildItems(values) { function buildItems(values) {
let groups = []; let groups = [];
...@@ -44,16 +49,11 @@ export default { ...@@ -44,16 +49,11 @@ export default {
}), }),
methods: { methods: {
changeBreadcrumb: function(groupId) { changeBreadcrumb: function(groupId) {
this.input.selectedGroupId = groupId; this.$store.dispatch('changeBreadcrumb', groupId);
this.input.searchFilter = null; },
if (this.input.tabIndex === 0) { openEditGroupModal: function(group) {
client.fetchGroupsTab(this.input) group.leaf = this.model.leaf;
.then(model => { this.$parent.$refs.editGroupModal.openEditGroupModal(group, false);
this.$store.commit('updateGroups', model);
});
} else {
this.$store.dispatch('changeTab', 0);
}
} }
} }
} }
...@@ -65,7 +65,7 @@ export default { ...@@ -65,7 +65,7 @@ export default {
position: relative; position: relative;
} }
#csv-status-download { #breadcrumb-buttons {
position: absolute; position: absolute;
right: 18px; right: 18px;
top: 12px; top: 12px;
......
...@@ -23,13 +23,11 @@ ...@@ -23,13 +23,11 @@
<p v-if="model.groupsPanel.items.length === 0">No groups</p> <p v-if="model.groupsPanel.items.length === 0">No groups</p>
</div> </div>
<Paginator :paginatedPanel="model.groupsPanel" :onUpdate="updatePagination" :paginatorInput="input" /> <Paginator :paginatedPanel="model.groupsPanel" :onUpdate="updatePagination" :paginatorInput="input" />
<EditGroupModal ref="editGroupModal" />
<ConfirmRemoveGroupModal ref="confirmRemoveGroupModal" /> <ConfirmRemoveGroupModal ref="confirmRemoveGroupModal" />
</b-tab> </b-tab>
</template> </template>
<script> <script>
import EditGroupModal from './modals/EditGroupModal.vue';
import ConfirmRemoveGroupModal from './modals/ConfirmRemoveGroupModal.vue'; import ConfirmRemoveGroupModal from './modals/ConfirmRemoveGroupModal.vue';
import Paginator from './Paginator.vue'; import Paginator from './Paginator.vue';
import { mapState } from 'vuex'; import { mapState } from 'vuex';
...@@ -39,7 +37,6 @@ import debounce from 'debounce'; // for delaying the input event (search filter) ...@@ -39,7 +37,6 @@ import debounce from 'debounce'; // for delaying the input event (search filter)
export default { export default {
name: 'GroupsPanel', name: 'GroupsPanel',
components: { components: {
EditGroupModal,
ConfirmRemoveGroupModal, ConfirmRemoveGroupModal,
Paginator Paginator
}, },
...@@ -52,7 +49,7 @@ export default { ...@@ -52,7 +49,7 @@ export default {
this.$store.dispatch('openGroup', group.groupId); this.$store.dispatch('openGroup', group.groupId);
}, },
openEditGroupModal: function(group) { openEditGroupModal: function(group) {
this.$refs.editGroupModal.openEditGroupModal(group); this.$parent.$parent.$refs.editGroupModal.openEditGroupModal(group, true);
}, },
openRemoveGroupModal: function(group) { openRemoveGroupModal: function(group) {
this.$refs.confirmRemoveGroupModal.openRemoveGroupModal(group); this.$refs.confirmRemoveGroupModal.openRemoveGroupModal(group);
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
<AddGroupModal /> <AddGroupModal />
<AddMemberModal /> <AddMemberModal />
<AddPermissionModal /> <AddPermissionModal />
<EditGroupModal ref="editGroupModal" />
</div> </div>
</template> </template>
...@@ -30,6 +31,7 @@ import InvitedRegistrationPanel from './InvitedRegistrationPanel.vue' ...@@ -30,6 +31,7 @@ import InvitedRegistrationPanel from './InvitedRegistrationPanel.vue'
import AddGroupModal from './modals/AddGroupModal.vue' import AddGroupModal from './modals/AddGroupModal.vue'
import AddMemberModal from './modals/AddMemberModal.vue' import AddMemberModal from './modals/AddMemberModal.vue'
import AddPermissionModal from './modals/AddPermissionModal.vue' import AddPermissionModal from './modals/AddPermissionModal.vue'
import EditGroupModal from './modals/EditGroupModal.vue';
import { mapState } from 'vuex'; import { mapState } from 'vuex';
export default { export default {
...@@ -42,7 +44,8 @@ export default { ...@@ -42,7 +44,8 @@ export default {
InvitedRegistrationPanel, InvitedRegistrationPanel,
AddGroupModal, AddGroupModal,
AddMemberModal, AddMemberModal,
AddPermissionModal AddPermissionModal,
EditGroupModal
}, },
computed: mapState({ computed: mapState({
model: state => state.model, model: state => state.model,
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
<div class="col-md-4"> <div class="col-md-4">
<b-row> <b-row>
<b-col sm="5"> <b-col sm="5">
<label for="page-size">Page size:</label> <label for="page-size" class="mt-2">Page size:</label>
</b-col> </b-col>
<b-col sm="6"> <b-col sm="6">
<b-form-select id="page-size" v-model="paginatorInput.paginatorPageSize" :options="pageSizeOptions" v-on:change="changePageSize"></b-form-select> <b-form-select id="page-size" v-model="paginatorInput.paginatorPageSize" :options="pageSizeOptions" v-on:change="changePageSize"></b-form-select>
......
...@@ -9,10 +9,10 @@ ...@@ -9,10 +9,10 @@
<!-- Right aligned nav items --> <!-- Right aligned nav items -->
<b-navbar-nav class="ml-auto"> <b-navbar-nav class="ml-auto">
<b-nav-item href="help/index.html" target="blank_" class="mr-4">Help</b-nav-item> <b-nav-item href="help/index.html" target="_blank" class="mr-4">Help</b-nav-item>
<b-nav-form> <b-nav-form>
<b-form-input size="sm" class="mr-sm-2" placeholder="Search" v-model="input.genericSearch.filter" @keydown.native.enter.prevent="genericSearch"></b-form-input> <b-form-input size="sm" class="mr-sm-2" placeholder="Search" v-model.trim="input.genericSearch.filter" @keydown.native.enter.prevent="genericSearch"></b-form-input>
<b-button size="sm" class="my-2 my-sm-0" type="button" v-on:click="genericSearch()">Search</b-button> <b-button size="sm" type="button" v-on:click="genericSearch()">Search</b-button>
</b-nav-form> </b-nav-form>
<b-nav-item-dropdown :text="user" right v-if="user"> <b-nav-item-dropdown :text="user" right v-if="user">
<b-dropdown-item href="logout">Logout</b-dropdown-item> <b-dropdown-item href="logout">Logout</b-dropdown-item>
...@@ -36,13 +36,16 @@ export default { ...@@ -36,13 +36,16 @@ export default {
}), }),
methods: { methods: {
showMainPage() { showMainPage() {
if (this.$store.state.input.selectedGroupId !== 'ROOT') {
this.$store.dispatch('changeBreadcrumb', 'ROOT');
} else {
this.$store.dispatch('changeTab', 0);
}
this.$router.push('/', () => {}); this.$router.push('/', () => {});
}, },
genericSearch() { genericSearch() {
this.input.genericSearch.page = 1;
this.input.genericSearch.pageSize = 20;
this.$router.push({ path: '/search', query: { q: this.input.genericSearch.filter } }, () => {}); this.$router.push({ path: '/search', query: { q: this.input.genericSearch.filter } }, () => {});
this.$store.dispatch('search', this.input.genericSearch.filter); this.$store.commit('setGenericSearchFilter', this.input.genericSearch.filter);
} }
} }
} }
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
<b-form-input v-model="newGroupName" id="new-group-name-input" ref="newGroupNameInput" class="w-75" aria-describedby="new-group-name-input-feedback" :state="newGroupNameState" v-on:input="resetError" @keydown.native.enter="addGroup"> <b-form-input v-model="newGroupName" id="new-group-name-input" ref="newGroupNameInput" class="w-75" aria-describedby="new-group-name-input-feedback" :state="newGroupNameState" v-on:input="resetError" @keydown.native.enter="addGroup">
</b-form-input> </b-form-input>
<b-form-invalid-feedback id="new-group-name-input-feedback" class="text-right">{{newGroupNameError}}</b-form-invalid-feedback> <b-form-invalid-feedback id="new-group-name-input-feedback" class="text-right">{{newGroupNameError}}</b-form-invalid-feedback>
<b-form-checkbox class="mt-3 ml-3" v-model="leaf">is leaf</b-form-checkbox> <b-form-checkbox class="mt-3 ml-3" v-model="allowChildGroups">allow child groups</b-form-checkbox>
</b-form> </b-form>
</b-modal> </b-modal>
</template> </template>
...@@ -27,13 +27,13 @@ export default { ...@@ -27,13 +27,13 @@ export default {
return { return {
newGroupName: '', newGroupName: '',
newGroupNameError: '', newGroupNameError: '',
leaf: true allowChildGroups: false
}; };
}, },
methods: { methods: {
resetModal: function() { resetModal: function() {
this.newGroupName = null; this.newGroupName = null;
this.leaf = true; this.allowChildGroups = false;
this.resetError(); this.resetError();
}, },
afterShow: function() { afterShow: function() {
...@@ -49,7 +49,7 @@ export default { ...@@ -49,7 +49,7 @@ export default {
if (!this.newGroupName) { if (!this.newGroupName) {
this.newGroupNameError = "Group name is required"; this.newGroupNameError = "Group name is required";
} else { } else {
client.addGroup(this.newGroupName, this.leaf, this.$store.state.input) client.addGroup(this.newGroupName, !this.allowChildGroups, this.$store.state.input)
.then(res => { .then(res => {
this.$store.commit('updateGroupsPanel', res); this.$store.commit('updateGroupsPanel', res);
this.$bvModal.hide('add-group-modal'); this.$bvModal.hide('add-group-modal');
......
...@@ -2,10 +2,10 @@ ...@@ -2,10 +2,10 @@
<b-modal id="edit-group-modal" title="Edit group" ok-title="Update" @ok="updateGroup"> <b-modal id="edit-group-modal" title="Edit group" ok-title="Update" @ok="updateGroup">
<b-form inline> <b-form inline>
<label class="w-25" for="new-group-name-input">Group name:</label> <label class="w-25" for="new-group-name-input">Group name:</label>
<b-form-input v-model="newGroupName" id="new-group-name-input" class="w-75" aria-describedby="new-group-name-input-feedback" :state="newGroupNameState" v-on:input="resetError"> <b-form-input v-model="newGroupName" id="new-group-name-input" class="w-75" aria-describedby="new-group-name-input-feedback" :state="newGroupNameState" v-on:input="resetError" @keydown.native.enter.prevent="updateGroup">
</b-form-input> </b-form-input>
<b-form-invalid-feedback id="new-group-name-input-feedback" class="text-right">{{newGroupNameError}}</b-form-invalid-feedback> <b-form-invalid-feedback id="new-group-name-input-feedback" class="text-right">{{newGroupNameError}}</b-form-invalid-feedback>
<b-form-checkbox class="mt-3 ml-3" v-model="leaf">is leaf</b-form-checkbox> <b-form-checkbox class="mt-3 ml-3" v-model="allowChildGroups">allow child groups</b-form-checkbox>
</b-form> </b-form>
</b-modal> </b-modal>
</template> </template>
...@@ -29,18 +29,20 @@ export default { ...@@ -29,18 +29,20 @@ export default {
oldGroupName: '', oldGroupName: '',
newGroupName: '', newGroupName: '',
newGroupNameError: '', newGroupNameError: '',
leaf: false allowChildGroups: false,
reloadChildren: false
}; };
}, },
methods: { methods: {
resetError: function() { resetError: function() {
this.newGroupNameError = null; this.newGroupNameError = null;
}, },
openEditGroupModal: function(group) { openEditGroupModal: function(group, reloadChildren) {
this.newGroupName = group.groupName; this.newGroupName = group.groupName;
this.groupId = group.groupId; this.groupId = group.groupId;
this.leaf = group.leaf; this.allowChildGroups = !group.leaf;
this.$bvModal.show('edit-group-modal'); this.$bvModal.show('edit-group-modal');
this.reloadChildren = reloadChildren;
}, },
updateGroup: function(event) { updateGroup: function(event) {
// Prevent modal from closing // Prevent modal from closing
...@@ -56,9 +58,16 @@ export default { ...@@ -56,9 +58,16 @@ export default {
return; return;
} }
client.updateGroup(this.groupId, this.newGroupName, this.leaf, this.$store.state.input) client.updateGroup(this.groupId, this.newGroupName, !this.allowChildGroups, this.$store.state.input)
.then(res => { .then(res => {
this.$store.commit('updateGroupsPanel', res); if (this.reloadChildren) {
this.$store.commit('updateGroupsPanel', res);
} else {
this.$store.dispatch('updateCurrentGroup', {
newGroupName: this.newGroupName,
leaf: !this.allowChildGroups
});
}
this.$bvModal.hide('edit-group-modal'); this.$bvModal.hide('edit-group-modal');
}) })
.catch(res => { .catch(res => {
......
...@@ -32,11 +32,14 @@ export default new Vuex.Store({ ...@@ -32,11 +32,14 @@ export default new Vuex.Store({
paginatorPageSize: 20, paginatorPageSize: 20,
paginatorPage: 1, paginatorPage: 1,
tabIndex: 0, tabIndex: 0,
previousTabIndex: null,
searchFilter: '', searchFilter: '',
genericSearch: { genericSearch: {
filter: '', filter: '',
paginatorPage: 1, paginatorPage: 1,
paginatorPageSize: 20 paginatorPageSize: 20,
users: true,
groups: true
} }
}, },
loading: false loading: false
...@@ -97,6 +100,10 @@ export default new Vuex.Store({ ...@@ -97,6 +100,10 @@ export default new Vuex.Store({
}, },
setGenericSearchFilter(state, filter) { setGenericSearchFilter(state, filter) {
state.input.genericSearch.filter = filter; state.input.genericSearch.filter = filter;
state.input.genericSearch.paginatorPage = 1;
state.input.genericSearch.paginatorPageSize = 20;
state.input.genericSearch.users = true;
state.input.genericSearch.groups = true;
}, },
removeInvitedRegistration(state, regId) { removeInvitedRegistration(state, regId) {
let index = -1; let index = -1;
...@@ -117,12 +124,26 @@ export default new Vuex.Store({ ...@@ -117,12 +124,26 @@ export default new Vuex.Store({
.then(model => commit('updateHomePageModel', model)); .then(model => commit('updateHomePageModel', model));
}, },
search({ commit, state }, filter) { search({ commit, state }, filter) {
commit('setGenericSearchFilter', filter); if (state.input.genericSearch.filter !== filter) {
commit('setGenericSearchFilter', filter);
}
client.search(state.input) client.search(state.input)
.then(results => commit('displaySearchResults', results)); .then(results => commit('displaySearchResults', results));
}, },
/**
* tabIndex parameter is equal to state.input.tabIndex when this method
* is called as a result of the @input event on the b-tabs component.
* For this reason the additional field state.input.previousTabIndex is used
* to check if the tab index changed and perform the AJAX calls only when
* they are really needed.
*/
changeTab({ commit, state }, tabIndex) { changeTab({ commit, state }, tabIndex) {
let skip = tabIndex === state.input.previousTabIndex;
state.input.previousTabIndex = tabIndex;
state.input.tabIndex = tabIndex; state.input.tabIndex = tabIndex;
if (skip) {
return;
}
// reset paginator // reset paginator
state.input.paginatorPage = 1; state.input.paginatorPage = 1;
switch (tabIndex) { switch (tabIndex) {
...@@ -140,18 +161,39 @@ export default new Vuex.Store({ ...@@ -140,18 +161,39 @@ export default new Vuex.Store({
break; break;
} }
}, },
changeBreadcrumb({ state, commit, dispatch }, groupId) {
state.input.selectedGroupId = groupId;
state.input.searchFilter = null;
if (state.input.tabIndex === 0) {
client.fetchGroupsTab(state.input)
.then(model => {
commit('updateGroups', model);
dispatch('changeTab', 0);
});
} else {
dispatch('changeTab', 0);
}
},
updateCurrentGroup({ dispatch, state }, data) {
state.model.breadcrumbs[state.model.breadcrumbs.length - 1].groupName = data.newGroupName;
state.model.leaf = data.leaf;
if (state.input.tabIndex === 0 && state.model.leaf) {
dispatch('changeTab', 1);
}
},
openGroup({ commit, dispatch, state }, groupId) { openGroup({ commit, dispatch, state }, groupId) {
let input = state.input; let input = state.input;
input.selectedGroupId = groupId; input.selectedGroupId = groupId;
input.searchFilter = ''; input.searchFilter = '';
input.paginatorPage = 1;
input.tabIndex = 0;
input.previousTabIndex = 0;
client.fetchGroupsTab(input) client.fetchGroupsTab(input)
.then(model => { .then(model => {
commit('updateGroups', model); commit('updateGroups', model);
if (model.leaf) { if (model.leaf) {
// If there are no subgroups show the members panel // If there are no subgroups show the members panel
dispatch('changeTab', 1); dispatch('changeTab', 1);
} else {
dispatch('changeTab', 0);
} }
router.push('/', () => {}); router.push('/', () => {});
}); });
......
...@@ -48,12 +48,6 @@ ...@@ -48,12 +48,6 @@
<artifactId>opencsv</artifactId> <artifactId>opencsv</artifactId>
<version>3.8</version> <version>3.8</version>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
......
...@@ -3,6 +3,7 @@ package it.inaf.ia2.gms.authn; ...@@ -3,6 +3,7 @@ package it.inaf.ia2.gms.authn;
import it.inaf.ia2.aa.UserManager; import it.inaf.ia2.aa.UserManager;
import it.inaf.ia2.aa.data.User; import it.inaf.ia2.aa.data.User;
import it.inaf.ia2.gms.persistence.LoggingDAO; import it.inaf.ia2.gms.persistence.LoggingDAO;
import it.inaf.ia2.gms.persistence.model.ActionType;
import java.io.IOException; import java.io.IOException;
import java.security.Principal; import java.security.Principal;
import java.util.Map; import java.util.Map;
...@@ -55,13 +56,14 @@ public class JWTFilter implements Filter { ...@@ -55,13 +56,14 @@ public class JWTFilter implements Filter {
Map<String, Object> claims = userManager.parseIdTokenClaims(token); Map<String, Object> claims = userManager.parseIdTokenClaims(token);
if (claims.get("sub") == null) { if (claims.get("sub") == null) {
loggingDAO.logAction("Attempt to access WS with invalid token", request); loggingDAO.logAction(ActionType.UNAUTHORIZED_ACCESS_ATTEMPT, "Attempt to access API with invalid token " + request.getRequestURI(), request);
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid access token: missing sub claim"); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid access token: missing sub claim");
return; return;
} }
ServletRequestWithJWTPrincipal wrappedRequest = new ServletRequestWithJWTPrincipal(request, token, claims); ServletRequestWithJWTPrincipal wrappedRequest = new ServletRequestWithJWTPrincipal(request, token, claims);
loggingDAO.logAction("WS access from " + wrappedRequest.getUserPrincipal().getName(), request);
loggingDAO.logAction(ActionType.API_CALL, request.getRequestURI() + " called by " + wrappedRequest.getUserPrincipal().getName(), request);
fc.doFilter(wrappedRequest, res); fc.doFilter(wrappedRequest, res);
} }
......
...@@ -14,9 +14,13 @@ import it.inaf.ia2.gms.model.request.RenameGroupRequest; ...@@ -14,9 +14,13 @@ import it.inaf.ia2.gms.model.request.RenameGroupRequest;
import it.inaf.ia2.gms.model.request.SearchFilterRequest; import it.inaf.ia2.gms.model.request.SearchFilterRequest;
import it.inaf.ia2.gms.persistence.model.GroupEntity; import it.inaf.ia2.gms.persistence.model.GroupEntity;
import it.inaf.ia2.gms.service.GroupNameService; import it.inaf.ia2.gms.service.GroupNameService;
import it.inaf.ia2.gms.service.GroupsService;
import it.inaf.ia2.gms.service.GroupsTreeBuilder; import it.inaf.ia2.gms.service.GroupsTreeBuilder;
import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.security.Principal;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
...@@ -46,6 +50,9 @@ public class GroupsController { ...@@ -46,6 +50,9 @@ public class GroupsController {
@Autowired @Autowired
private GroupsManager groupsManager; private GroupsManager groupsManager;
@Autowired
private GroupsService groupsService;
@Autowired @Autowired
private GroupsTreeBuilder groupsTreeBuilder; private GroupsTreeBuilder groupsTreeBuilder;
...@@ -56,9 +63,9 @@ public class GroupsController { ...@@ -56,9 +63,9 @@ public class GroupsController {
private GroupStatusManager groupStatusManager; private GroupStatusManager groupStatusManager;
@Autowired @Autowired
private GroupNameService groupNameService; protected GroupNameService groupNameService;
@GetMapping(value = "/groups", produces = MediaType.APPLICATION_JSON_VALUE) @GetMapping(value = "/ui/groups", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> getGroupsTab(@Valid GroupsRequest request) { public ResponseEntity<?> getGroupsTab(@Valid GroupsRequest request) {
if (request.isOnlyPanel()) { if (request.isOnlyPanel()) {
// Only groupsPanel // Only groupsPanel
...@@ -70,7 +77,7 @@ public class GroupsController { ...@@ -70,7 +77,7 @@ public class GroupsController {
} }
} }
@PostMapping(value = "/group", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @PostMapping(value = "/ui/group", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<PaginatedData<GroupNode>> createGroup(@Valid @RequestBody AddGroupRequest request) { public ResponseEntity<PaginatedData<GroupNode>> createGroup(@Valid @RequestBody AddGroupRequest request) {
GroupEntity parent = groupsManager.getGroupById(request.getParentGroupId()); GroupEntity parent = groupsManager.getGroupById(request.getParentGroupId());
...@@ -82,7 +89,7 @@ public class GroupsController { ...@@ -82,7 +89,7 @@ public class GroupsController {
return new ResponseEntity<>(groupsPanel, HttpStatus.CREATED); return new ResponseEntity<>(groupsPanel, HttpStatus.CREATED);
} }
@PutMapping(value = "/group/{groupId}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @PutMapping(value = "/ui/group/{groupId}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<PaginatedData<GroupNode>> updateGroup(@PathVariable("groupId") String groupId, @Valid @RequestBody RenameGroupRequest request) { public ResponseEntity<PaginatedData<GroupNode>> updateGroup(@PathVariable("groupId") String groupId, @Valid @RequestBody RenameGroupRequest request) {
GroupEntity updatedGroup = groupsManager.updateGroup(groupId, request.getNewGroupName(), request.isLeaf()); GroupEntity updatedGroup = groupsManager.updateGroup(groupId, request.getNewGroupName(), request.isLeaf());
...@@ -94,8 +101,8 @@ public class GroupsController { ...@@ -94,8 +101,8 @@ public class GroupsController {
return ResponseEntity.ok(groupsPanel); return ResponseEntity.ok(groupsPanel);
} }
@DeleteMapping(value = "/group/{groupId}", produces = MediaType.APPLICATION_JSON_VALUE) @DeleteMapping(value = "/ui/group/{groupId}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> deleteGroup(@PathVariable("groupId") String groupId, DeleteGroupRequest request) { public ResponseEntity<PaginatedData<GroupNode>> deleteGroup(@PathVariable("groupId") String groupId, DeleteGroupRequest request) {
GroupEntity parent = groupsManager.deleteGroup(groupId); GroupEntity parent = groupsManager.deleteGroup(groupId);
PaginatedData<GroupNode> groupsPanel = getGroupsPanel(parent, request); PaginatedData<GroupNode> groupsPanel = getGroupsPanel(parent, request);
...@@ -139,4 +146,55 @@ public class GroupsController { ...@@ -139,4 +146,55 @@ public class GroupsController {
private <T extends PaginatedModelRequest & SearchFilterRequest> PaginatedData<GroupNode> getGroupsPanel(GroupEntity parentGroup, T request) { private <T extends PaginatedModelRequest & SearchFilterRequest> PaginatedData<GroupNode> getGroupsPanel(GroupEntity parentGroup, T request) {
return groupsTreeBuilder.listSubGroups(parentGroup, request, servletRequest.getUserPrincipal().getName()); return groupsTreeBuilder.listSubGroups(parentGroup, request, servletRequest.getUserPrincipal().getName());
} }
@GetMapping(value = "/groups", produces = MediaType.TEXT_PLAIN_VALUE)
public void listChildGroups(@RequestParam("parent") Optional<String> groupNames,
@RequestParam(value = "recursive", defaultValue = "false") boolean recursive,
Principal principal, HttpServletResponse response) throws IOException {
GroupEntity parentGroup = groupNameService.getGroupFromNames(groupNames);
List<GroupEntity> visibleSubgroups = groupsManager.getChildGroups(parentGroup, recursive);
try ( PrintWriter pw = new PrintWriter(response.getOutputStream())) {
for (String groupName : groupNameService.getGroupsNames(visibleSubgroups)) {
pw.println(groupNameService.getShortGroupName(groupName, groupNames));
}
}
}
/**
* Creates a group and its ancestors if they are missing. It doesn't fail if
* the last group already exists. For CLI/API usage.
*/
@PostMapping(value = "/group", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public void createGroup(@RequestParam("name") String groupParam,
@RequestParam(value = "leaf", required = false, defaultValue = "false") boolean leaf,
HttpServletResponse response) throws IOException {
List<String> groupNames = groupNameService.extractGroupNames(groupParam);
GroupEntity group = groupsManager.getRoot();
for (int i = 0; i < groupNames.size(); i++) {
String name = groupNames.get(i);
Optional<GroupEntity> optGroup = groupsService.findGroupByParentAndName(group, name);
if (optGroup.isPresent()) {
group = optGroup.get();
} else {
group = groupsManager.createGroup(group, name, i == groupNames.size() - 1 ? leaf : false);
}
}
response.setStatus(HttpServletResponse.SC_CREATED);
try ( PrintWriter pw = new PrintWriter(response.getOutputStream())) {
pw.println(groupParam);
}
}
@DeleteMapping(value = "/group", produces = MediaType.TEXT_PLAIN_VALUE)
public ResponseEntity<?> deleteGroup(@RequestParam("name") String groupParam) {
GroupEntity group = groupNameService.getGroupFromNames(Optional.of(groupParam));
groupsManager.deleteGroup(group.getId());
return ResponseEntity.noContent().build();
}
} }
package it.inaf.ia2.gms.controller; package it.inaf.ia2.gms.controller;
import it.inaf.ia2.gms.authn.SessionData; import it.inaf.ia2.gms.authn.SessionData;
import it.inaf.ia2.gms.exception.UnauthorizedException;
import it.inaf.ia2.gms.manager.InvitedRegistrationManager; import it.inaf.ia2.gms.manager.InvitedRegistrationManager;
import it.inaf.ia2.gms.model.GroupBreadcrumb;
import it.inaf.ia2.gms.model.GroupNode;
import it.inaf.ia2.gms.model.Permission;
import it.inaf.ia2.gms.model.request.GroupsRequest; import it.inaf.ia2.gms.model.request.GroupsRequest;
import it.inaf.ia2.gms.model.response.GroupsTabResponse; import it.inaf.ia2.gms.model.response.GroupsTabResponse;
import it.inaf.ia2.gms.model.response.HomePageResponse; import it.inaf.ia2.gms.model.response.HomePageResponse;
import it.inaf.ia2.gms.model.response.PaginatedData;
import it.inaf.ia2.gms.persistence.model.InvitedRegistration; import it.inaf.ia2.gms.persistence.model.InvitedRegistration;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import javax.servlet.ServletException; import javax.servlet.ServletException;
...@@ -41,44 +35,21 @@ public class HomePageController { ...@@ -41,44 +35,21 @@ public class HomePageController {
private InvitedRegistrationManager invitedRegistrationManager; private InvitedRegistrationManager invitedRegistrationManager;
@ResponseBody @ResponseBody
@GetMapping(value = "/home", produces = MediaType.APPLICATION_JSON_VALUE) @GetMapping(value = "/ui/home", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<HomePageResponse> getMainPage(@Valid GroupsRequest request) { public ResponseEntity<HomePageResponse> getMainPage(@Valid GroupsRequest request) {
HomePageResponse response = new HomePageResponse(); HomePageResponse response = new HomePageResponse();
response.setUser(session.getUserName()); response.setUser(session.getUserName());
try { GroupsTabResponse groupsTabResponse = groupsTabResponseBuilder.getGroupsTab(request);
GroupsTabResponse groupsTabResponse = groupsTabResponseBuilder.getGroupsTab(request); response.setBreadcrumbs(groupsTabResponse.getBreadcrumbs());
response.setBreadcrumbs(groupsTabResponse.getBreadcrumbs()); response.setGroupsPanel(groupsTabResponse.getGroupsPanel());
response.setGroupsPanel(groupsTabResponse.getGroupsPanel()); response.setPermission(groupsTabResponse.getPermission());
response.setPermission(groupsTabResponse.getPermission());
} catch (UnauthorizedException ex) {
if ("ROOT".equals(request.getGroupId())) {
response.setBreadcrumbs(getRootBreadcrumbs());
response.setGroupsPanel(getEmptyGroupsPanel(request));
response.setPermission(Permission.TRAVERSE);
} else {
throw ex;
}
}
return ResponseEntity.ok(response); return ResponseEntity.ok(response);
} }
private List<GroupBreadcrumb> getRootBreadcrumbs() {
List<GroupBreadcrumb> breadcrumbs = new ArrayList<>();
GroupBreadcrumb breadcrumb = new GroupBreadcrumb();
breadcrumb.setGroupId("ROOT");
breadcrumb.setGroupName("ROOT");
breadcrumbs.add(breadcrumb);
return breadcrumbs;
}
private PaginatedData<GroupNode> getEmptyGroupsPanel(GroupsRequest request) {
return new PaginatedData<>(new ArrayList<>(), 1, request.getPaginatorPageSize());
}
@GetMapping(value = "/", produces = MediaType.TEXT_HTML_VALUE) @GetMapping(value = "/", produces = MediaType.TEXT_HTML_VALUE)
public String index(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { public String index(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
......
...@@ -59,7 +59,7 @@ public class InvitedRegistrationController { ...@@ -59,7 +59,7 @@ public class InvitedRegistrationController {
} }
} }
@DeleteMapping(value = "/registration", produces = MediaType.APPLICATION_JSON_VALUE) @DeleteMapping(value = "/ui/registration", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> deleteInvitedRegistration(@RequestParam("request_id") String requestId, @RequestParam("group_id") String groupId) { public ResponseEntity<?> deleteInvitedRegistration(@RequestParam("request_id") String requestId, @RequestParam("group_id") String groupId) {
invitedRegistrationManager.deleteInvitedRegistration(requestId, groupId); invitedRegistrationManager.deleteInvitedRegistration(requestId, groupId);
return ResponseEntity.noContent().build(); return ResponseEntity.noContent().build();
......