Commit 1e9783fd authored by Sonia Zorba's avatar Sonia Zorba
Browse files

Added panel showing the pending invited registration requests; Added lock...

Added panel showing the pending invited registration requests; Added lock attribute on groups to avoid removal of special groups; Other improvements
parent 6c395713
...@@ -45,7 +45,9 @@ ...@@ -45,7 +45,9 @@
"plugin:vue/essential", "plugin:vue/essential",
"eslint:recommended" "eslint:recommended"
], ],
"rules": {}, "rules": {
"no-console": "warn"
},
"parserOptions": { "parserOptions": {
"parser": "babel-eslint" "parser": "babel-eslint"
} }
......
...@@ -17,7 +17,7 @@ function apiRequest(options, showLoading = true, handleValidationErrors = false) ...@@ -17,7 +17,7 @@ function apiRequest(options, showLoading = true, handleValidationErrors = false)
loading(false); loading(false);
}) })
.catch(error => { .catch(error => {
if(handleValidationErrors && error.response && error.response.status === 400) { if (handleValidationErrors && error.response && error.response.status === 400) {
reject(error.response.data); reject(error.response.data);
} else { } else {
dispatchApiErrorEvent(error); dispatchApiErrorEvent(error);
...@@ -363,5 +363,19 @@ export default { ...@@ -363,5 +363,19 @@ export default {
'Cache-Control': 'no-cache' 'Cache-Control': 'no-cache'
} }
}, false); }, false);
},
deleteInvitedRegistration(requestId, groupId) {
let url = BASE_API_URL + 'registration?' +
'request_id=' + requestId + '&group_id=' + groupId;
return apiRequest({
method: 'DELETE',
url: url,
withCredentials: true,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Cache-Control': 'no-cache'
}
});
} }
}; };
<template> <template>
<div class="mt-sm-3"> <div class="mt-sm-3">
<div> <div>
<p>Search results:</p> <div v-if="model.genericSearchResults.items && model.genericSearchResults.items.length > 0">
<b-list-group v-for="item in model.genericSearchResults.items" v-bind:key="item.id"> <p>Search results:</p>
<b-list-group-item href="#" v-on:click="openSearchResult(item)"> <b-list-group v-for="item in model.genericSearchResults.items" v-bind:key="item.id">
<span class="float-left"> <b-list-group-item href="#" v-on:click="openSearchResult(item)">
<font-awesome-icon icon="folder" v-if="item.type === 'GROUP'"></font-awesome-icon> <span class="float-left">
<font-awesome-icon icon="user" v-if="item.type === 'USER'"></font-awesome-icon> <font-awesome-icon icon="folder" v-if="item.type === 'GROUP'"></font-awesome-icon>
{{item.label}} <font-awesome-icon icon="user" v-if="item.type === 'USER'"></font-awesome-icon>
</span> {{item.label}}
</b-list-group-item> </span>
</b-list-group> </b-list-group-item>
<Paginator :paginatedPanel="model.genericSearchResults" :onUpdate="updateSearchResults" :paginatorInput="input.genericSearch" /> </b-list-group>
<Paginator :paginatedPanel="model.genericSearchResults" :onUpdate="updateSearchResults" :paginatorInput="input.genericSearch" />
</div>
</div> </div>
<p v-if="model.genericSearchResults.items && model.genericSearchResults.items.length === 0">No entries were found matching your search</p>
</div> </div>
</template> </template>
...@@ -29,7 +32,7 @@ export default { ...@@ -29,7 +32,7 @@ export default {
model: state => state.model, model: state => state.model,
input: state => state.input input: state => state.input
}), }),
created () { created() {
this.updateSearchResults(); this.updateSearchResults();
}, },
watch: { watch: {
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
<font-awesome-icon icon="edit"></font-awesome-icon> <font-awesome-icon icon="edit"></font-awesome-icon>
</a> </a>
&nbsp; &nbsp;
<a href="#" v-on:click.stop.prevent="openRemoveGroupModal(group)" class="text-danger" title="Delete"> <a href="#" v-on:click.stop.prevent="openRemoveGroupModal(group)" class="text-danger" title="Delete" v-if="!group.locked">
<font-awesome-icon icon="trash"></font-awesome-icon> <font-awesome-icon icon="trash"></font-awesome-icon>
</a> </a>
</span> </span>
......
<template>
<b-tab :title="'Invited (' + registrations.length + ')'" :title-link-class="{ 'd-none': registrations.length === 0 }">
<table class="table b-table table-striped table-hover" v-if="registrations">
<thead>
<tr>
<th>Email</th>
<th>Permission</th>
<th>Submitted at</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="(reg, index) in registrations" v-bind:key="index">
<td>{{reg.email}}</td>
<td>{{reg.permission}}</td>
<td>{{reg.creationTime}}</td>
<td>
<a href="#" v-on:click.stop="openConfirmDeleteInvitedModal(reg)" class="text-danger" title="Remove permission">
<font-awesome-icon icon="trash"></font-awesome-icon>
</a>
</td>
</tr>
</tbody>
</table>
<ConfirmDeleteInvitedModal ref="confirmDeleteInvitedModal" />
</b-tab>
</template>
<script>
import ConfirmDeleteInvitedModal from './modals/ConfirmDeleteInvitedModal.vue';
export default {
name: 'InvitedRegistrationPanel',
components: {
ConfirmDeleteInvitedModal
},
computed: {
registrations() {
return this.$store.state.model.invitedRegistrations === null ? [] : this.$store.state.model.invitedRegistrations;
}
},
methods: {
openConfirmDeleteInvitedModal(reg) {
let breadcrumbs = this.$store.state.model.breadcrumbs;
let currentGroupId = breadcrumbs[breadcrumbs.length - 1].groupId;
this.$refs.confirmDeleteInvitedModal.openConfirmDeleteInvitedModal(reg, currentGroupId);
}
}
}
</script>
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
<GroupsPanel /> <GroupsPanel />
<MembersPanel /> <MembersPanel />
<PermissionsPanel /> <PermissionsPanel />
<InvitedRegistrationPanel />
<template slot="tabs-end"> <template slot="tabs-end">
<b-button variant="primary" class="in-tabs-header-btn" v-if="showAddGroupBtn" v-on:click="openAddGroupModal">Add group</b-button> <b-button variant="primary" class="in-tabs-header-btn" v-if="showAddGroupBtn" v-on:click="openAddGroupModal">Add group</b-button>
<b-button variant="primary" class="in-tabs-header-btn" v-if="showAddMemberBtn" v-on:click="openAddMemberModal">Add member</b-button> <b-button variant="primary" class="in-tabs-header-btn" v-if="showAddMemberBtn" v-on:click="openAddMemberModal">Add member</b-button>
...@@ -25,6 +26,7 @@ import GroupsBreadcrumb from './GroupsBreadcrumb.vue' ...@@ -25,6 +26,7 @@ import GroupsBreadcrumb from './GroupsBreadcrumb.vue'
import GroupsPanel from './GroupsPanel.vue' import GroupsPanel from './GroupsPanel.vue'
import MembersPanel from './MembersPanel.vue' import MembersPanel from './MembersPanel.vue'
import PermissionsPanel from './PermissionsPanel.vue' import PermissionsPanel from './PermissionsPanel.vue'
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'
...@@ -37,6 +39,7 @@ export default { ...@@ -37,6 +39,7 @@ export default {
GroupsPanel, GroupsPanel,
MembersPanel, MembersPanel,
PermissionsPanel, PermissionsPanel,
InvitedRegistrationPanel,
AddGroupModal, AddGroupModal,
AddMemberModal, AddMemberModal,
AddPermissionModal AddPermissionModal
......
<template> <template>
<b-tab title="Permissions" :title-link-class="{ 'd-none': (model.permission !== 'ADMIN') }"> <b-tab title="Permissions" :title-link-class="{ 'd-none': (model.permission !== 'ADMIN') }">
<div v-if="model.permissionsPanel !== null"> <div v-if="model.permissionsPanel !== null">
<table class="table b-table table-striped table-hover"> <table class="table b-table table-striped table-hover" v-if="model.permissionsPanel.items.length > 0">
<thead> <thead>
<tr> <tr>
<th>User</th> <th>User</th>
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
<b-row> <b-row>
<b-col lg="10"> <b-col lg="10">
<b-list-group> <b-list-group>
<b-list-group-item v-for="(identity, index) in user.identities" v-bind:key="index"> <b-list-group-item :class="{ 'list-group-item-info': identity.primary }" v-for="(identity, index) in user.identities" v-bind:key="index">
<dl class="mb-0 ml-0 row"> <dl class="mb-0 ml-0 row">
<dt class="col-3">Type</dt><dd class="col-9">{{identity.type}}</dd> <dt class="col-3">Type</dt><dd class="col-9">{{identity.type}}</dd>
<dt class="col-3">Email</dt><dd class="col-9">{{identity.email}}</dd> <dt class="col-3">Email</dt><dd class="col-9">{{identity.email}}</dd>
......
<template> <template>
<b-modal id="add-member-modal" title="Add member" ok-title="Add" @shown="afterShow" @ok="addMember"> <b-modal id="add-member-modal" title="Add member" ok-title="Add" @shown="afterShow" @ok="addMember" size="lg">
<SearchUser ref="searchUser" @searchUserEnter="addMember" /> <SearchUser ref="searchUser" @searchUserEnter="addMember" />
</b-modal> </b-modal>
</template> </template>
......
<template> <template>
<b-modal id="add-permission-modal" title="Add permission" @show="beforeShow" @shown="afterShow" :ok-title="okTitle" @ok="addPermission" :ok-variant="okBtnVariant"> <b-modal id="add-permission-modal" title="Add permission" @show="beforeShow" @shown="afterShow" :ok-title="okTitle" @ok="addPermission" :ok-variant="okBtnVariant" size="lg">
<SearchUser ref="searchUser" @searchUserEnter="addPermission" /> <SearchUser ref="searchUser" @searchUserEnter="addPermission" />
<b-alert :show="!!existingPermission" variant="warning" class="mt-3"> <b-alert :show="!!existingPermission" variant="warning" class="mt-3">
<strong>Warning</strong>: the user has already a permission ({{existingPermission}}). Click confirm to override it. <strong>Warning</strong>: the user has already a permission ({{existingPermission}}). Click confirm to override it.
......
<template>
<b-modal id="confirm-delete-invited-modal" title="Confirm action" ok-title="Delete" @ok="deleteInvitedRegistration" ok-variant="danger">
<p v-if="invitedRegistration">Are you sure that you want to remove the invited registration for {{invitedRegistration.email}}?</p>
</b-modal>
</template>
<script>
import client from 'api-client';
export default {
name: 'ConfirmDeleteInvitedModal',
data: function() {
return {
invitedRegistration: null,
groupId: null
}
},
methods: {
openConfirmDeleteInvitedModal(invitedRegistration, groupId) {
this.invitedRegistration = invitedRegistration;
this.groupId = groupId;
this.$bvModal.show('confirm-delete-invited-modal');
},
deleteInvitedRegistration() {
let regId = this.invitedRegistration.id;
client.deleteInvitedRegistration(regId, this.groupId)
.then(() => {
let model = this.$store.state.model;
this.$store.commit('removeInvitedRegistration', regId);
if (model.invitedRegistrations.length === 0) {
this.$store.commit('setTabIndex', model.leaf ? 1 : 0);
}
});
}
}
}
</script>
...@@ -50,7 +50,7 @@ export default { ...@@ -50,7 +50,7 @@ export default {
let user = res[i]; let user = res[i];
this.users.push({ this.users.push({
value: user.id, value: user.id,
text: user.displayName text: user.displayName + ' [' + user.id + ']'
}); });
} }
if (this.users.length > 0) { if (this.users.length > 0) {
......
...@@ -15,6 +15,7 @@ export default new Vuex.Store({ ...@@ -15,6 +15,7 @@ export default new Vuex.Store({
groupsPanel: null, groupsPanel: null,
permissionsPanel: null, permissionsPanel: null,
membersPanel: null, membersPanel: null,
invitedRegistrations: null,
permission: null, permission: null,
leaf: false, leaf: false,
user: null, user: null,
...@@ -45,12 +46,14 @@ export default new Vuex.Store({ ...@@ -45,12 +46,14 @@ export default new Vuex.Store({
state.model.breadcrumbs = model.breadcrumbs; state.model.breadcrumbs = model.breadcrumbs;
state.model.groupsPanel = model.groupsPanel; state.model.groupsPanel = model.groupsPanel;
state.model.permission = model.permission; state.model.permission = model.permission;
state.model.invitedRegistrations = model.invitedRegistrations;
state.model.user = model.user; state.model.user = model.user;
}, },
updateGroups(state, model) { updateGroups(state, model) {
state.model.breadcrumbs = model.breadcrumbs; state.model.breadcrumbs = model.breadcrumbs;
state.model.groupsPanel = model.groupsPanel; state.model.groupsPanel = model.groupsPanel;
state.model.permission = model.permission; state.model.permission = model.permission;
state.model.invitedRegistrations = model.invitedRegistrations;
state.model.leaf = model.leaf; state.model.leaf = model.leaf;
}, },
updateGroupsPanel(state, groupsPanel) { updateGroupsPanel(state, groupsPanel) {
...@@ -59,7 +62,7 @@ export default new Vuex.Store({ ...@@ -59,7 +62,7 @@ export default new Vuex.Store({
}, },
updatePermissionsPanel(state, permissionsPanel) { updatePermissionsPanel(state, permissionsPanel) {
state.model.permissionsPanel = permissionsPanel; state.model.permissionsPanel = permissionsPanel;
for(let up of permissionsPanel.items) { for (let up of permissionsPanel.items) {
Vue.set(up, 'editable', false); Vue.set(up, 'editable', false);
} }
state.input.paginatorPage = permissionsPanel.currentPage; state.input.paginatorPage = permissionsPanel.currentPage;
...@@ -94,6 +97,18 @@ export default new Vuex.Store({ ...@@ -94,6 +97,18 @@ export default new Vuex.Store({
}, },
setGenericSearchFilter(state, filter) { setGenericSearchFilter(state, filter) {
state.input.genericSearch.filter = filter; state.input.genericSearch.filter = filter;
},
removeInvitedRegistration(state, regId) {
let index = -1;
for (var i = 0; i < state.model.invitedRegistrations.length; i++) {
if (state.model.invitedRegistrations[i].id === regId) {
index = i;
break;
}
}
if (index !== -1) {
state.model.invitedRegistrations.splice(index, 1);
}
} }
}, },
actions: { actions: {
......
...@@ -2,6 +2,7 @@ package it.inaf.ia2.gms.controller; ...@@ -2,6 +2,7 @@ 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.manager.GroupsManager; import it.inaf.ia2.gms.manager.GroupsManager;
import it.inaf.ia2.gms.manager.InvitedRegistrationManager;
import it.inaf.ia2.gms.manager.PermissionsManager; import it.inaf.ia2.gms.manager.PermissionsManager;
import it.inaf.ia2.gms.model.Permission; import it.inaf.ia2.gms.model.Permission;
import it.inaf.ia2.gms.model.request.GroupsRequest; import it.inaf.ia2.gms.model.request.GroupsRequest;
...@@ -30,6 +31,9 @@ public class GroupsTabResponseBuilder { ...@@ -30,6 +31,9 @@ public class GroupsTabResponseBuilder {
@Autowired @Autowired
private GroupsTreeBuilder groupsListBuilder; private GroupsTreeBuilder groupsListBuilder;
@Autowired
private InvitedRegistrationManager invitedRegistrationManager;
public GroupsTabResponse getGroupsTab(GroupsRequest request) { public GroupsTabResponse getGroupsTab(GroupsRequest request) {
GroupEntity group = groupsService.getGroupById(request.getGroupId()); GroupEntity group = groupsService.getGroupById(request.getGroupId());
...@@ -46,6 +50,8 @@ public class GroupsTabResponseBuilder { ...@@ -46,6 +50,8 @@ public class GroupsTabResponseBuilder {
response.setLeaf(group.isLeaf()); response.setLeaf(group.isLeaf());
response.setInvitedRegistrations(invitedRegistrationManager.getInvitedRegistrationsForGroup(group));
return response; return response;
} }
} }
...@@ -10,7 +10,9 @@ import javax.servlet.http.HttpServletRequest; ...@@ -10,7 +10,9 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
...@@ -55,6 +57,12 @@ public class InvitedRegistrationController { ...@@ -55,6 +57,12 @@ public class InvitedRegistrationController {
} }
} }
@DeleteMapping(value = "/registration", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> deleteInvitedRegistration(@RequestParam("request_id") String requestId, @RequestParam("group_id") String groupId) {
invitedRegistrationManager.deleteInvitedRegistration(requestId, groupId);
return ResponseEntity.noContent().build();
}
private String getFileContent(String templateFileName) throws IOException { private String getFileContent(String templateFileName) throws IOException {
try (InputStream in = InvitedRegistrationController.class.getClassLoader().getResourceAsStream("templates/" + templateFileName)) { try (InputStream in = InvitedRegistrationController.class.getClassLoader().getResourceAsStream("templates/" + templateFileName)) {
Scanner s = new Scanner(in).useDelimiter("\\A"); Scanner s = new Scanner(in).useDelimiter("\\A");
......
package it.inaf.ia2.gms.manager; package it.inaf.ia2.gms.manager;
import it.inaf.ia2.gms.exception.BadRequestException;
import it.inaf.ia2.gms.exception.NotFoundException; import it.inaf.ia2.gms.exception.NotFoundException;
import it.inaf.ia2.gms.exception.UnauthorizedException; import it.inaf.ia2.gms.exception.UnauthorizedException;
import it.inaf.ia2.gms.model.Permission; import it.inaf.ia2.gms.model.Permission;
import it.inaf.ia2.gms.model.response.InvitedRegistrationItem;
import it.inaf.ia2.gms.persistence.GroupsDAO; import it.inaf.ia2.gms.persistence.GroupsDAO;
import it.inaf.ia2.gms.persistence.InvitedRegistrationDAO; import it.inaf.ia2.gms.persistence.InvitedRegistrationDAO;
import it.inaf.ia2.gms.persistence.LoggingDAO; import it.inaf.ia2.gms.persistence.LoggingDAO;
...@@ -14,8 +16,10 @@ import it.inaf.ia2.gms.service.PermissionsService; ...@@ -14,8 +16,10 @@ import it.inaf.ia2.gms.service.PermissionsService;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Base64; import java.util.Base64;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
...@@ -119,4 +123,47 @@ public class InvitedRegistrationManager extends UserAwareComponent { ...@@ -119,4 +123,47 @@ public class InvitedRegistrationManager extends UserAwareComponent {
return Optional.ofNullable(invitedRegistration); return Optional.ofNullable(invitedRegistration);
} }
public List<InvitedRegistrationItem> getInvitedRegistrationsForGroup(GroupEntity group) {
if (permissionsManager.getCurrentUserPermission(group) != Permission.ADMIN) {
return null;
}
List<InvitedRegistrationItem> items = new ArrayList<>();
for (InvitedRegistration reg : invitedRegistrationDAO.getPendingInvitedRegistrationsForGroup(group.getId())) {
Map<String, Permission> map = reg.getGroupsPermissions();
if (map != null) {
for (Permission permission : map.values()) {
InvitedRegistrationItem item = new InvitedRegistrationItem()
.setId(reg.getId())
.setEmail(reg.getEmail())
.setPermission(permission)
.setCreationTime(reg.getCreationTime());
items.add(item);
}
}
}
return items;
}
public void deleteInvitedRegistration(String registrationId, String groupId) {
GroupEntity group = groupsDAO.findGroupById(groupId)
.orElseThrow(() -> new BadRequestException("No group found for given id: " + groupId));
if (permissionsManager.getUserPermission(group, getCurrentUserId()) != Permission.ADMIN) {
throw new UnauthorizedException("Only administrators can delete invited registrations!");
}
invitedRegistrationDAO.deleteInvitedRegistrationRequest(registrationId, groupId);
loggingDAO.logAction("Deleted invited registration request. "
+ "[request_id=" + registrationId + ", group_id=" + groupId
+ ", group_name=" + group.getName() + "]");
}
} }
...@@ -7,6 +7,7 @@ public class GroupNode { ...@@ -7,6 +7,7 @@ public class GroupNode {
private Permission permission; private Permission permission;
private boolean hasChildren; private boolean hasChildren;
private boolean leaf; private boolean leaf;
private boolean locked;
public String getGroupId() { public String getGroupId() {
return groupId; return groupId;
...@@ -47,4 +48,12 @@ public class GroupNode { ...@@ -47,4 +48,12 @@ public class GroupNode {
public void setLeaf(boolean leaf) { public void setLeaf(boolean leaf) {
this.leaf = leaf; this.leaf = leaf;
} }
public boolean isLocked() {
return locked;
}
public void setLocked(boolean locked) {
this.locked = locked;
}
} }
...@@ -11,6 +11,7 @@ public class GroupsTabResponse { ...@@ -11,6 +11,7 @@ public class GroupsTabResponse {
private PaginatedData<GroupNode> groupsPanel; private PaginatedData<GroupNode> groupsPanel;
// current group permissions // current group permissions
private Permission permission;