From 437188d3299542caa2bbc66a78f3ff7b80485f4f Mon Sep 17 00:00:00 2001 From: Luis Faria Date: Wed, 11 Feb 2026 12:37:18 +0000 Subject: [PATCH 01/37] Fix #3621, #3622 #3623 --- .../roda/core/data/common/RodaConstants.java | 3 + .../roda/core/common/CryptographyUtils.java | 40 ++ .../roda/core/model/DefaultModelService.java | 117 ++-- .../DefaultTransactionalModelService.java | 7 + .../org/roda/core/model/ModelService.java | 3 + .../roda/core/model/utils/LdapUtility.java | 7 + .../resources/config/roda-roles.properties | 6 + .../config/i18n/client/ClientMessages.java | 71 +++ .../api/v2/controller/MembersController.java | 140 ++++- .../wui/api/v2/services/IndexService.java | 1 - .../wui/api/v2/services/MembersService.java | 102 +++- .../org/roda/wui/client/browse/BrowseAIP.java | 1 - .../client/browse/tabs/RODAMemberTabs.java | 58 ++ .../org/roda/wui/client/browse/tabs/Tabs.java | 21 + .../common/RODAMemberActionsToolbar.java | 40 ++ .../common/actions/RODAMemberAction.java | 32 + .../common/actions/RODAMemberActions.java | 107 ++-- .../actions/RODAMemberToolbarActions.java | 406 +++++++++++++ .../callbacks/ActionNoAsyncCallback.java | 1 - .../common/dialogs/AccessKeyDialogs.java | 59 +- .../wui/client/common/dialogs/Dialogs.java | 54 -- .../common/dialogs/MemberSelectDialog.java | 2 +- .../common/dialogs/RODAMembersDialogs.java | 445 ++++++++++++++ .../client/common/lists/RodaMemberList.java | 10 +- .../common/lists/utils/ActionMenuCell.java | 54 ++ .../common/lists/utils/BasicTablePanel.java | 29 + .../common/lists/utils/SimpleActionCell.java | 58 ++ .../client/common/panels/PermissionPanel.java | 80 +++ .../common/panels/PermissionsPanel.java | 39 ++ .../common/panels/PermissionsPanel.ui.xml | 16 + .../roda/wui/client/common/resources/main.gss | 174 +++++- .../slider/DisseminationsSliderHelper.java | 251 -------- .../common/slider/InfoSliderHelper.java | 380 +----------- .../common/slider/OptionsSliderHelper.java | 76 --- .../wui/client/common/slider/SliderPanel.java | 161 ----- .../wui/client/common/slider/Sliders.java | 62 -- .../client/common/utils/FormUtilities.java | 38 ++ .../roda/wui/client/main/BreadcrumbUtils.java | 18 + .../roda/wui/client/main/ContentPanel.java | 17 +- .../client/main/ExpiredSessionDetector.java | 4 +- .../java/org/roda/wui/client/main/Header.java | 2 +- .../java/org/roda/wui/client/main/Login.java | 4 +- .../org/roda/wui/client/main/UserMenu.java | 4 +- .../wui/client/management/CreateGroup.java | 133 ----- .../wui/client/management/CreateGroup.ui.xml | 32 - .../wui/client/management/CreateUser.java | 138 ----- .../wui/client/management/CreateUser.ui.xml | 32 - .../client/management/CreateUserPanel.ui.xml | 62 -- .../roda/wui/client/management/EditGroup.java | 161 ----- .../wui/client/management/EditGroup.ui.xml | 35 -- .../roda/wui/client/management/EditUser.java | 241 -------- .../wui/client/management/EditUser.ui.xml | 40 -- .../wui/client/management/GroupDataPanel.java | 268 --------- .../wui/client/management/Management.java | 1 + .../roda/wui/client/management/ShowGroup.java | 178 ------ .../wui/client/management/ShowGroup.ui.xml | 61 -- .../wui/client/management/ShowLogEntry.java | 11 +- .../client/management/ShowNotification.java | 2 +- .../roda/wui/client/management/ShowUser.java | 192 ------ .../wui/client/management/ShowUser.ui.xml | 81 --- .../management/access/AccessKeyDataPanel.java | 192 ------ .../access/AccessKeyDataPanel.ui.xml | 47 -- .../access/AccessKeyTablePanel.java | 278 +++++++-- .../access/AccessKeyTablePanel.ui.xml | 3 - .../management/access/CreateAccessKey.java | 141 ----- .../management/access/CreateAccessKey.ui.xml | 34 -- .../management/access/ShowAccessKey.java | 284 --------- .../management/access/ShowAccessKey.ui.xml | 80 --- .../{ => members}/CreateUserPanel.java | 317 ++-------- .../management/members/CreateUserPanel.ui.xml | 49 ++ .../management/members/GroupDataPanel.java | 194 ++++++ .../{ => members}/GroupDataPanel.ui.xml | 21 +- .../management/{ => members}/GroupSelect.java | 2 +- .../{ => members}/MemberManagement.java | 77 +-- .../{ => members}/MemberManagement.ui.xml | 0 .../{ => members}/PasswordPanel.java | 2 +- .../{ => members}/PermissionsPanel.java | 2 +- .../management/{ => members}/Profile.java | 4 +- .../management/{ => members}/Profile.ui.xml | 2 +- .../{ => members}/RecoverLogin.java | 2 +- .../{ => members}/RecoverLogin.ui.xml | 0 .../management/{ => members}/Register.java | 4 +- .../management/{ => members}/Register.ui.xml | 2 +- .../{ => members}/ResetPassword.java | 22 +- .../{ => members}/ResetPassword.ui.xml | 2 +- .../management/{ => members}/SetPassword.java | 2 +- .../{ => members}/SetPassword.ui.xml | 2 +- .../client/management/members/ShowMember.java | 167 ++++++ .../management/members/ShowMember.ui.xml | 25 + .../{ => members}/UpdatePasswordPanel.java | 2 +- .../{ => members}/UpdatePasswordPanel.ui.xml | 0 .../UserAndGroupKeyDownHandler.java | 2 +- .../{ => members}/UserDataPanel.java | 255 ++------ .../{ => members}/UserDataPanel.ui.xml | 22 +- .../management/members/UserDetailsPanel.java | 8 + .../members/tabs/AccessKeysTab.java | 83 +++ .../members/tabs/AccessKeysTab.ui.xml | 17 + .../members/tabs/PermissionsPanel.java | 558 ++++++++++++++++++ .../members/tabs/RODAMemberDetailsPanel.java | 149 +++++ .../tabs/RODAMemberDetailsPanel.ui.xml | 16 + .../members/tabs/RODAMemberGroupsTab.java | 330 +++++++++++ .../members/tabs/RODAMemberGroupsTab.ui.xml | 17 + .../tabs/RODAMemberPermissionsTab.java | 67 +++ .../tabs/RODAMemberPermissionsTab.ui.xml | 16 + .../CreateRepresentationInformation.java | 3 +- .../roda/wui/client/planning/CreateRisk.java | 9 +- .../EditRepresentationInformation.java | 3 +- .../roda/wui/client/planning/EditRisk.java | 3 +- .../client/planning/EditRiskIncidence.java | 26 +- .../roda/wui/client/planning/RiskHistory.java | 3 +- .../ShowRepresentationInformation.java | 3 +- .../roda/wui/client/planning/ShowRisk.java | 5 +- .../client/planning/ShowRiskIncidence.java | 3 +- .../client/services/MembersRestService.java | 65 +- .../wui/common/client/tools/HistoryUtils.java | 5 +- .../i18n/client/ClientMessages.properties | 70 ++- .../client/ClientMessages_pt_PT.properties | 39 ++ .../main/resources/config/roda-wui.properties | 28 +- 118 files changed, 4339 insertions(+), 4293 deletions(-) create mode 100644 roda-core/roda-core/src/main/java/org/roda/core/common/CryptographyUtils.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/RODAMemberTabs.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/RODAMemberActionsToolbar.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberAction.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberToolbarActions.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/dialogs/RODAMembersDialogs.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/ActionMenuCell.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/SimpleActionCell.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/panels/PermissionPanel.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/panels/PermissionsPanel.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/panels/PermissionsPanel.ui.xml delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/slider/DisseminationsSliderHelper.java delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/slider/OptionsSliderHelper.java delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/slider/SliderPanel.java delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/slider/Sliders.java delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/CreateGroup.java delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/CreateGroup.ui.xml delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/CreateUser.java delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/CreateUser.ui.xml delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/CreateUserPanel.ui.xml delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/EditGroup.java delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/EditGroup.ui.xml delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/EditUser.java delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/EditUser.ui.xml delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/GroupDataPanel.java delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowGroup.java delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowGroup.ui.xml delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowUser.java delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowUser.ui.xml delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/AccessKeyDataPanel.java delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/AccessKeyDataPanel.ui.xml delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/CreateAccessKey.java delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/CreateAccessKey.ui.xml delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/ShowAccessKey.java delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/ShowAccessKey.ui.xml rename roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/{ => members}/CreateUserPanel.java (53%) create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateUserPanel.ui.xml create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/GroupDataPanel.java rename roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/{ => members}/GroupDataPanel.ui.xml (51%) rename roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/{ => members}/GroupSelect.java (99%) rename roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/{ => members}/MemberManagement.java (68%) rename roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/{ => members}/MemberManagement.ui.xml (100%) rename roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/{ => members}/PasswordPanel.java (99%) rename roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/{ => members}/PermissionsPanel.java (99%) rename roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/{ => members}/Profile.java (97%) rename roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/{ => members}/Profile.ui.xml (91%) rename roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/{ => members}/RecoverLogin.java (99%) rename roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/{ => members}/RecoverLogin.ui.xml (100%) rename roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/{ => members}/Register.java (98%) rename roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/{ => members}/Register.ui.xml (92%) rename roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/{ => members}/ResetPassword.java (72%) rename roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/{ => members}/ResetPassword.ui.xml (83%) rename roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/{ => members}/SetPassword.java (98%) rename roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/{ => members}/SetPassword.ui.xml (83%) create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/ShowMember.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/ShowMember.ui.xml rename roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/{ => members}/UpdatePasswordPanel.java (99%) rename roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/{ => members}/UpdatePasswordPanel.ui.xml (100%) rename roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/{ => members}/UserAndGroupKeyDownHandler.java (96%) rename roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/{ => members}/UserDataPanel.java (61%) rename roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/{ => members}/UserDataPanel.ui.xml (67%) create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/UserDetailsPanel.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/AccessKeysTab.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/AccessKeysTab.ui.xml create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/PermissionsPanel.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/RODAMemberDetailsPanel.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/RODAMemberDetailsPanel.ui.xml create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/RODAMemberGroupsTab.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/RODAMemberGroupsTab.ui.xml create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/RODAMemberPermissionsTab.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/RODAMemberPermissionsTab.ui.xml diff --git a/roda-common/roda-common-data/src/main/java/org/roda/core/data/common/RodaConstants.java b/roda-common/roda-common-data/src/main/java/org/roda/core/data/common/RodaConstants.java index 64715f8c18..7f90cd73d4 100644 --- a/roda-common/roda-common-data/src/main/java/org/roda/core/data/common/RodaConstants.java +++ b/roda-common/roda-common-data/src/main/java/org/roda/core/data/common/RodaConstants.java @@ -2161,6 +2161,9 @@ public enum RODA_TYPE { public static final String PERMISSION_METHOD_CREATE_GROUP = "org.roda.wui.api.v2.controller.MembersController.createGroup"; public static final String PERMISSION_METHOD_UPDATE_USER = "org.roda.wui.api.v2.controller.MembersController.updateUser"; public static final String PERMISSION_METHOD_DELETE_USER = "org.roda.wui.api.v2.controller.MembersController.deleteUser"; + public static final String PERMISSION_METHOD_REVOKE_ACCESS_TOKEN = "org.roda.wui.api.v2.controller.MembersController.regenerateAccessKey"; + public static final String PERMISSION_METHOD_DELETE_ACCESS_TOKEN = "org.roda.wui.api.v2.controller.MembersController.deleteAccessKey"; + public static final String PERMISSION_METHOD_REGENERATE_ACCESS_TOKEN = "org.roda.wui.api.v2.controller.MembersController.regenerateAccessKey"; public static final String PERMISSION_METHOD_CREATE_ACCESS_KEY = "org.roda.wui.api.v2.controller.MembersController.createAccessKey"; diff --git a/roda-core/roda-core/src/main/java/org/roda/core/common/CryptographyUtils.java b/roda-core/roda-core/src/main/java/org/roda/core/common/CryptographyUtils.java new file mode 100644 index 0000000000..bcd994f3ba --- /dev/null +++ b/roda-core/roda-core/src/main/java/org/roda/core/common/CryptographyUtils.java @@ -0,0 +1,40 @@ +package org.roda.core.common; + +import org.roda.core.data.exceptions.GenericException; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * + * @author Miguel Guimarães + */ +public class CryptographyUtils { + + private CryptographyUtils() { + // do nothing + } + + public static String hashTokenSHA256(String token) throws GenericException { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] encodedHash = digest.digest(token.getBytes(StandardCharsets.UTF_8)); + + // Convert the byte array into a hex string for easy storage + StringBuilder hexString = new StringBuilder(2 * encodedHash.length); + for (byte hash : encodedHash) { + String hex = Integer.toHexString(0xff & hash); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + return hexString.toString(); + + } catch (NoSuchAlgorithmException e) { + // Wrap and throw using your application's exception handling + throw new GenericException("Error initializing SHA-256 hashing algorithm", e); + } + } +} diff --git a/roda-core/roda-core/src/main/java/org/roda/core/model/DefaultModelService.java b/roda-core/roda-core/src/main/java/org/roda/core/model/DefaultModelService.java index 7da4af2b04..44b29806e4 100644 --- a/roda-core/roda-core/src/main/java/org/roda/core/model/DefaultModelService.java +++ b/roda-core/roda-core/src/main/java/org/roda/core/model/DefaultModelService.java @@ -52,6 +52,7 @@ import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.roda.core.RodaCoreFactory; +import org.roda.core.common.CryptographyUtils; import org.roda.core.common.JwtUtils; import org.roda.core.common.PremisV3Utils; import org.roda.core.common.ProvidesInputStream; @@ -214,6 +215,32 @@ public class DefaultModelService implements ModelService { private JobRepository jobRepository; private ReportRepository reportRepository; + public DefaultModelService(StorageService storage, EventsManager eventsManager, NodeType nodeType, + String instanceId) { + this.storage = storage; + this.eventsManager = eventsManager; + this.nodeType = nodeType; + this.instanceId = instanceId; + this.observers = new ArrayList<>(); + + if (RodaCoreFactory.checkIfWriteIsAllowed(nodeType)) { + ensureAllContainersExist(); + ensureAllDirectoriesExist(); + } + } + + private static void clearSpecificIndexes(IndexService index, Class objectClass, + IsModelObject rodaObject) throws AuthorizationDeniedException { + if (AIP.class.equals(objectClass)) { + List ids = Arrays.asList(rodaObject.getId()); + index.delete(IndexedRepresentation.class, + new Filter(new OneOfManyFilterParameter(RodaConstants.REPRESENTATION_AIP_ID, ids))); + index.delete(IndexedFile.class, new Filter(new OneOfManyFilterParameter(RodaConstants.FILE_AIP_ID, ids))); + index.delete(IndexedPreservationEvent.class, + new Filter(new OneOfManyFilterParameter(RodaConstants.PRESERVATION_EVENT_AIP_ID, ids))); + } + } + /** * Lazily retrieves the JobRepository bean from Spring context. */ @@ -241,32 +268,6 @@ private boolean isJpaAvailable() { return SpringContext.isContextInitialized(); } - public DefaultModelService(StorageService storage, EventsManager eventsManager, NodeType nodeType, - String instanceId) { - this.storage = storage; - this.eventsManager = eventsManager; - this.nodeType = nodeType; - this.instanceId = instanceId; - this.observers = new ArrayList<>(); - - if (RodaCoreFactory.checkIfWriteIsAllowed(nodeType)) { - ensureAllContainersExist(); - ensureAllDirectoriesExist(); - } - } - - private static void clearSpecificIndexes(IndexService index, Class objectClass, - IsModelObject rodaObject) throws AuthorizationDeniedException { - if (AIP.class.equals(objectClass)) { - List ids = Arrays.asList(rodaObject.getId()); - index.delete(IndexedRepresentation.class, - new Filter(new OneOfManyFilterParameter(RodaConstants.REPRESENTATION_AIP_ID, ids))); - index.delete(IndexedFile.class, new Filter(new OneOfManyFilterParameter(RodaConstants.FILE_AIP_ID, ids))); - index.delete(IndexedPreservationEvent.class, - new Filter(new OneOfManyFilterParameter(RodaConstants.PRESERVATION_EVENT_AIP_ID, ids))); - } - } - private void ensureAllContainersExist() { try { createContainerIfNotExists(RodaConstants.STORAGE_CONTAINER_AIP); @@ -2805,7 +2806,40 @@ public Group updateGroup(final Group group, boolean notify, boolean isHandlingEv } catch (IllegalOperationException e) { throw new AuthorizationDeniedException("Illegal operation", e); } + } + + @Override + public Group updateGroupMembers(String id, Set members, boolean notify, boolean isHandlingEvent) + throws AuthorizationDeniedException, NotFoundException, GenericException { + boolean writeIsAllowed = RodaCoreFactory.checkIfWriteIsAllowed(nodeType); + + if (!writeIsAllowed && !isHandlingEvent) { + RodaCoreFactory.throwExceptionIfWriteIsNotAllowed(); + } + Group group = UserUtility.getLdapUtility().getGroup(id); + group.getUsers().addAll(members); + + for (String member: members) { + User user = UserUtility.getLdapUtility().getUser(member); + user.getGroups().add(id); + notifyUserUpdated(user).failOnError(); + } + + try { + Group updatedGroup = UserUtility.getLdapUtility().modifyGroupMembers(group); + if (notify && writeIsAllowed) { + notifyGroupUpdated(updatedGroup).failOnError(); + } + if (!isHandlingEvent) { + // FIXME 20180813 hsilva: group is not the previous state of the group + eventsManager.notifyGroupUpdated(this, group, updatedGroup); + } + + return updatedGroup; + } catch (IllegalOperationException e) { + throw new AuthorizationDeniedException("Illegal operation", e); + } } @Override @@ -2942,8 +2976,8 @@ public void createOrUpdateJob(Job job) } /** - * Flushes a job and its reports from the database to the file storage. - * This method is called when a job reaches a final state. + * Flushes a job and its reports from the database to the file storage. This + * method is called when a job reaches a final state. */ private void flushJobToStorage(Job job) throws RequestNotValidException, GenericException, NotFoundException, AuthorizationDeniedException { @@ -3008,8 +3042,7 @@ public CloseableIterable> listJobReports(String jobId) if (jobRepo != null && reportRepo != null && jobRepo.existsById(jobId)) { // Return reports from database List dbReports = reportRepo.findByJobId(jobId); - List> wrappedReports = dbReports.stream() - .map(OptionalWithCause::of) + List> wrappedReports = dbReports.stream().map(OptionalWithCause::of) .collect(Collectors.toList()); return CloseableIterables.fromList(wrappedReports); } @@ -4081,13 +4114,13 @@ public CloseableIterable> list(Cla } else if (DescriptiveMetadata.class.equals(objectClass)) { ret = listDescriptiveMetadata(); } else if (Report.class.equals(objectClass)) { - // Include both DB reports (for running jobs) and storage reports (for completed jobs) + // Include both DB reports (for running jobs) and storage reports (for completed + // jobs) CloseableIterable> storageReports = ResourceParseUtils.convert(getStorage(), listReportResources(), Report.class); if (isJpaAvailable() && getReportRepository() != null) { List dbReports = getReportRepository().findAll(); - List> wrappedDbReports = dbReports.stream() - .map(OptionalWithCause::of) + List> wrappedDbReports = dbReports.stream().map(OptionalWithCause::of) .collect(Collectors.toList()); CloseableIterable> dbIterable = CloseableIterables.fromList(wrappedDbReports); ret = CloseableIterables.concat(dbIterable, storageReports); @@ -4102,8 +4135,7 @@ public CloseableIterable> list(Cla resourcesIterable, Job.class); if (isJpaAvailable() && getJobRepository() != null) { List dbJobs = getJobRepository().findAll(); - List> wrappedDbJobs = dbJobs.stream() - .map(OptionalWithCause::of) + List> wrappedDbJobs = dbJobs.stream().map(OptionalWithCause::of) .collect(Collectors.toList()); CloseableIterable> dbIterable = CloseableIterables.fromList(wrappedDbJobs); ret = CloseableIterables.concat(dbIterable, storageJobs); @@ -4144,13 +4176,13 @@ public CloseableIterable> storageReports = ResourceParseUtils.convertLite(getStorage(), listReportResources(), objectClass); if (isJpaAvailable() && getReportRepository() != null) { List dbReports = getReportRepository().findAll(); - List> wrappedDbReports = dbReports.stream() - .map(OptionalWithCause::of) + List> wrappedDbReports = dbReports.stream().map(OptionalWithCause::of) .collect(Collectors.toList()); CloseableIterable> dbIterable = LiteRODAObjectFactory .transformIntoLite(CloseableIterables.fromList(wrappedDbReports)); @@ -4171,8 +4203,7 @@ public CloseableIterable dbJobs = getJobRepository().findAll(); - List> wrappedDbJobs = dbJobs.stream() - .map(OptionalWithCause::of) + List> wrappedDbJobs = dbJobs.stream().map(OptionalWithCause::of) .collect(Collectors.toList()); CloseableIterable> dbIterable = LiteRODAObjectFactory .transformIntoLite(CloseableIterables.fromList(wrappedDbJobs)); @@ -5266,9 +5297,10 @@ public AccessKey createAccessKey(AccessKey accessKey, String createdBy) throws G accessKey.setExpirationDate(expirationDate); } - String token = JwtUtils.generateToken(accessKey.getUserName(), accessKey.getExpirationDate()); + String plainTextToken = JwtUtils.generateToken(accessKey.getUserName(), accessKey.getExpirationDate()); - accessKey.setKey(token); + String hashedToken = CryptographyUtils.hashTokenSHA256(plainTextToken); + accessKey.setKey(hashedToken); accessKey.setCreatedOn(new Date()); accessKey.setCreatedBy(createdBy); @@ -5279,6 +5311,7 @@ public AccessKey createAccessKey(AccessKey accessKey, String createdBy) throws G StoragePath accessKeyPath = ModelUtils.getAccessKeysStoragePath(accessKey.getId()); storage.createBinary(accessKeyPath, new StringContentPayload(accessKeyAsJson), false); + accessKey.setKey(plainTextToken); return accessKey; } diff --git a/roda-core/roda-core/src/main/java/org/roda/core/model/DefaultTransactionalModelService.java b/roda-core/roda-core/src/main/java/org/roda/core/model/DefaultTransactionalModelService.java index 14c4e31a3d..3b82df283a 100644 --- a/roda-core/roda-core/src/main/java/org/roda/core/model/DefaultTransactionalModelService.java +++ b/roda-core/roda-core/src/main/java/org/roda/core/model/DefaultTransactionalModelService.java @@ -15,6 +15,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import org.roda.core.common.ReturnWithExceptionsWrapper; import org.roda.core.common.iterables.CloseableIterable; @@ -2038,6 +2039,12 @@ public Group updateGroup(Group group, boolean notify, boolean isHandlingEvent) return mainModelService.updateGroup(group, notify, isHandlingEvent); } + @Override + public Group updateGroupMembers(String id, Set members, boolean notify, boolean isHandlingEvent) + throws GenericException, NotFoundException, AuthorizationDeniedException { + return mainModelService.updateGroupMembers(id, members, notify, isHandlingEvent); + } + @Override public void deleteGroup(String id, boolean notify) throws GenericException, AuthorizationDeniedException { mainModelService.deleteGroup(id, notify); diff --git a/roda-core/roda-core/src/main/java/org/roda/core/model/ModelService.java b/roda-core/roda-core/src/main/java/org/roda/core/model/ModelService.java index 02c1ae5ce0..0879aec09b 100644 --- a/roda-core/roda-core/src/main/java/org/roda/core/model/ModelService.java +++ b/roda-core/roda-core/src/main/java/org/roda/core/model/ModelService.java @@ -14,6 +14,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import org.roda.core.common.iterables.CloseableIterable; import org.roda.core.common.notifications.NotificationProcessor; @@ -547,6 +548,8 @@ Group updateGroup(Group group, boolean notify) Group updateGroup(Group group, boolean notify, boolean isHandlingEvent) throws GenericException, NotFoundException, AuthorizationDeniedException; + Group updateGroupMembers(String id, Set members, boolean notify,boolean isHandlingEvent) throws AuthorizationDeniedException, NotFoundException, GenericException; + void deleteGroup(String id, boolean notify) throws GenericException, AuthorizationDeniedException; void deleteGroup(String id, boolean notify, boolean isHandlingEvent) diff --git a/roda-core/roda-core/src/main/java/org/roda/core/model/utils/LdapUtility.java b/roda-core/roda-core/src/main/java/org/roda/core/model/utils/LdapUtility.java index 980fd4b228..cc94a2c875 100644 --- a/roda-core/roda-core/src/main/java/org/roda/core/model/utils/LdapUtility.java +++ b/roda-core/roda-core/src/main/java/org/roda/core/model/utils/LdapUtility.java @@ -709,6 +709,10 @@ public Group modifyGroup(final Group modifiedGroup) return modifyGroup(modifiedGroup, false); } + public Group modifyGroupMembers(final Group modifiedGroup) throws NotFoundException, IllegalOperationException, GenericException { + return modifyGroup(modifiedGroup, true); + } + /** * Removes a group. * @@ -1809,6 +1813,9 @@ private Name removeBaseDN(Name dn) { return LdapUtils.removeFirst(dn, LdapUtils.newLdapName(ldapRootDN)); } + public boolean isProtectedUser(String username) { + return this.ldapProtectedUsers.contains(username); + } public String transformExtra(Set values) { Handlebars handlebars = new Handlebars(); diff --git a/roda-core/roda-core/src/main/resources/config/roda-roles.properties b/roda-core/roda-core/src/main/resources/config/roda-roles.properties index 40ee50fc25..e54f2efb76 100644 --- a/roda-core/roda-core/src/main/resources/config/roda-roles.properties +++ b/roda-core/roda-core/src/main/resources/config/roda-roles.properties @@ -113,6 +113,12 @@ core.roles.org.roda.wui.api.v2.controller.MembersController.updateAccessKey = ac core.roles.org.roda.wui.api.v2.controller.MembersController.getAccessKeysByUser = access_key.read core.roles.org.roda.wui.api.v2.controller.MembersController.createAccessKey = access_key.manage core.roles.org.roda.wui.api.v2.controller.MembersController.changeActive = member.manage +core.roles.org.roda.wui.api.v2.controller.MembersController.addGroupsToUser = member.manage +core.roles.org.roda.wui.api.v2.controller.MembersController.removeGroupsFromUser = member.manage +core.roles.org.roda.wui.api.v2.controller.MembersController.getUserGroups = member.read +core.roles.org.roda.wui.api.v2.controller.MembersController.getGroupMembers = member.read +core.roles.org.roda.wui.api.v2.controller.MembersController.addMembersToGroup = member.manage +core.roles.org.roda.wui.api.v2.controller.MembersController.removeMembersFromGroup = member.manage core.roles.org.roda.wui.api.v2.services.MembersService.retrieveOwnUserExtra = member.read core.roles.org.roda.wui.api.v2.services.MembersService.retrieveUserExtra = member.read diff --git a/roda-ui/roda-wui/src/main/java/config/i18n/client/ClientMessages.java b/roda-ui/roda-wui/src/main/java/config/i18n/client/ClientMessages.java index 7847fb8bb5..135c05fdf1 100644 --- a/roda-ui/roda-wui/src/main/java/config/i18n/client/ClientMessages.java +++ b/roda-ui/roda-wui/src/main/java/config/i18n/client/ClientMessages.java @@ -417,6 +417,8 @@ public interface ClientMessages extends Messages { String cancelButton(); + String updateButton(); + String revertButton(); String removeButton(); @@ -1100,6 +1102,14 @@ SafeHtml searchPreFilterLongRangeFilterParameterGreaterThan(String searchPreFilt String userRemoveConfirmDialogMessage(); + String singleUserRemoveConfirmDialogTitle(); + + String singleGroupRemoveConfirmDialogTitle(); + + String singleUserRemoveConfirmDialogMessage(String username); + + String singleGroupRemoveConfirmDialogMessage(String username); + // Member management String addUserButton(); @@ -2499,6 +2509,8 @@ SafeHtml representationInformationAssociatedWithDescription(String field, String String createAccessKeyTitle(); + String regenerateAccessKeyTitle(); + String showAccessKeyTitle(); String editAccessKeyTitle(); @@ -2537,6 +2549,8 @@ SafeHtml representationInformationAssociatedWithDescription(String field, String String accessKeySuccessfullyRegenerated(); + String accessKeySuccessfullyDeleted(); + String accessKeySuccessfullyRevoked(); String accessKeyDeleteConfirmationMessage(); @@ -2545,6 +2559,8 @@ SafeHtml representationInformationAssociatedWithDescription(String field, String String accessKeyRegenerateConfirmationMessage(); + String accessKeyExpirationDateInThePast(); + /** Market **/ String marketPluginsActionsTabLabel(); @@ -2640,6 +2656,61 @@ SafeHtml representationInformationAssociatedWithDescription(String field, String String reasonCantActOnGroup(); + // RODA Members - toolbar actions + String deactivateUserTitle(); + + String deactivateUserConfirmationMessage(); + + String activateUserTitle(); + + String activateUserConfirmationMessage(); + + // RODA Members - Members + String membersTabTitle(); + + String addNewGroupModalTitle(); + + String addToGroupButton(); + + String addNewMemberToGroupButton(); + + String groupSuccessfullyAdded(); + + String memberSuccessfullyAdded(); + + String addNewMemberToGroupTitle(); + + String addNewMemberAction(); + + String removeGroupConfirmationTitle(); + + String removeGroupConfirmationMessage(String group); + + String groupSuccessfullyRemoved(); + + String removeMemberConfirmationMessage(String member); + + String removeMemberConfirmationTitle(); + + String memberSuccessfullyRemoved(); + + // RODA Members - Permissions + String catalogueAndSearchGroupLabel(); + + String ingestPreservationActionsInternalActionsGroupLabel(); + + String administrationGroupLabel(); + + String planningGroupLabel(); + + String disposalGroupLabel(); + + String editPermissionsReadOnlyPermissionsText(); + + String permissionsUpdateWithSuccess(); + + String editPermissionsModalTitle(); + // ── Email viewer ────────────────────────────────────────────────────────── String emailViewerSubject(); diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/controller/MembersController.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/controller/MembersController.java index 7b20c7f465..5c81bcfc11 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/controller/MembersController.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/controller/MembersController.java @@ -19,6 +19,7 @@ import javax.crypto.SecretKey; +import io.swagger.v3.oas.annotations.Parameter; import org.apache.commons.lang3.StringUtils; import org.apereo.cas.client.authentication.AttributePrincipal; import org.roda.core.RodaCoreFactory; @@ -78,6 +79,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.ldap.NamingException; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -272,7 +274,7 @@ private static Set mapCasGroupstoRODAGroups(Set casGroups) { } @Override - public User getUser(String name) { + public RODAMember getUser(String name) { RequestContext requestContext = RequestUtils.parseHTTPRequest(request); ControllerAssistant controllerAssistant = new ControllerAssistant() {}; LogEntryState state = LogEntryState.SUCCESS; @@ -281,7 +283,11 @@ public User getUser(String name) { // check user permissions controllerAssistant.checkRoles(requestContext.getUser()); - return membersService.retrieveUser(name); + if (name.startsWith("user-")) { + return membersService.retrieveUser(name.substring("user-".length())); + } else { + return membersService.retrieveGroup(name.substring("group-".length())); + } } catch (RODAException e) { state = LogEntryState.FAILURE; @@ -631,6 +637,136 @@ public Void updateGroup(@RequestBody Group modifiedGroup) { return null; } + @Override + public User addGroupsToUser(String id, @RequestBody SelectedItemsRequest groups) { + ControllerAssistant controllerAssistant = new ControllerAssistant() {}; + RequestContext requestContext = RequestUtils.parseHTTPRequest(request); + + LogEntryState state = LogEntryState.SUCCESS; + + try { + // check user permissions + controllerAssistant.checkRoles(requestContext.getUser()); + // delegate + return membersService.addGroupsToUser(id, groups); + } catch (RODAException e) { + state = LogEntryState.FAILURE; + throw new RESTException(e); + } finally { + // register action + controllerAssistant.registerAction(requestContext, state, RodaConstants.CONTROLLER_USER_PARAM, id, + RodaConstants.CONTROLLER_SELECTED_ITEMS_PARAM, groups); + } + } + + @Override + public Group addMembersToGroup(String id, @RequestBody SelectedItemsRequest members) { + ControllerAssistant controllerAssistant = new ControllerAssistant() {}; + RequestContext requestContext = RequestUtils.parseHTTPRequest(request); + + LogEntryState state = LogEntryState.SUCCESS; + + try { + // check user permissions + controllerAssistant.checkRoles(requestContext.getUser()); + // delegate + return membersService.addMembersToGroup(id, members); + } catch (RODAException e) { + state = LogEntryState.FAILURE; + throw new RESTException(e); + } finally { + // register action + controllerAssistant.registerAction(requestContext, state, RodaConstants.CONTROLLER_GROUP_PARAM, id, + RodaConstants.CONTROLLER_SELECTED_ITEMS_PARAM, members); + } + } + + @Override + public User removeGroupsFromUser(String id, String groupID) { + ControllerAssistant controllerAssistant = new ControllerAssistant() {}; + RequestContext requestContext = RequestUtils.parseHTTPRequest(request); + + LogEntryState state = LogEntryState.SUCCESS; + + try { + // check user permissions + controllerAssistant.checkRoles(requestContext.getUser()); + // delegate + return membersService.removeGroupFromUser(id, groupID); + } catch (RODAException e) { + state = LogEntryState.FAILURE; + throw new RESTException(e); + } finally { + // register action + controllerAssistant.registerAction(requestContext, state, RodaConstants.CONTROLLER_USER_PARAM, id, + RodaConstants.CONTROLLER_GROUP_PARAM, groupID); + } + } + + @Override + public Group removeMembersFromGroup(String id, String userId) { + ControllerAssistant controllerAssistant = new ControllerAssistant() {}; + RequestContext requestContext = RequestUtils.parseHTTPRequest(request); + + LogEntryState state = LogEntryState.SUCCESS; + + try { + // check user permissions + controllerAssistant.checkRoles(requestContext.getUser()); + // delegate + return membersService.removeMemberFromGroup(id, userId); + } catch (RODAException e) { + state = LogEntryState.FAILURE; + throw new RESTException(e); + } finally { + // register action + controllerAssistant.registerAction(requestContext, state, RodaConstants.CONTROLLER_GROUP_PARAM, id, + RodaConstants.CONTROLLER_USER_PARAM, userId); + } + } + + @Override + public Set getUserGroups(String id) { + ControllerAssistant controllerAssistant = new ControllerAssistant() {}; + RequestContext requestContext = RequestUtils.parseHTTPRequest(request); + + LogEntryState state = LogEntryState.SUCCESS; + + try { + // check user permissions + controllerAssistant.checkRoles(requestContext.getUser()); + // delegate + return membersService.getGroupFromUser(id); + } catch (RODAException e) { + state = LogEntryState.FAILURE; + throw new RESTException(e); + } finally { + // register action + controllerAssistant.registerAction(requestContext, state, RodaConstants.CONTROLLER_USER_PARAM, id); + } + } + + @Override + public Set getGroupMembers(String id) { + ControllerAssistant controllerAssistant = new ControllerAssistant() {}; + RequestContext requestContext = RequestUtils.parseHTTPRequest(request); + + LogEntryState state = LogEntryState.SUCCESS; + + try { + // check user permissions + controllerAssistant.checkRoles(requestContext.getUser()); + // delegate + return membersService.getGroupMembers(id); + } catch (RODAException e) { + state = LogEntryState.FAILURE; + throw new RESTException(e); + } finally { + // register action + controllerAssistant.registerAction(requestContext, state, RodaConstants.CONTROLLER_GROUP_PARAM, id); + } + } + @Override public User updateUser(@RequestBody UpdateUserRequest userRequest) { // TODO: Change request usage diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/services/IndexService.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/services/IndexService.java index 898514443e..cfef55b3b4 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/services/IndexService.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/services/IndexService.java @@ -23,7 +23,6 @@ import org.roda.core.data.v2.index.SuggestRequest; import org.roda.core.data.v2.index.sort.Sorter; import org.roda.core.data.v2.index.sublist.Sublist; -import org.roda.core.data.v2.user.User; import org.roda.wui.api.v2.controller.RequestHandler; import org.roda.wui.api.v2.exceptions.RESTException; import org.roda.wui.api.v2.stream.FacetsCSVOutputStream; diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/services/MembersService.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/services/MembersService.java index 437feef11d..b81eaf2149 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/services/MembersService.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/services/MembersService.java @@ -15,11 +15,14 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import org.apache.commons.io.IOUtils; +import org.apache.solr.client.solrj.util.ClientUtils; import org.joda.time.DateTime; import org.roda.core.RodaCoreFactory; import org.roda.core.common.Messages; @@ -41,6 +44,8 @@ import org.roda.core.data.exceptions.RequestNotValidException; import org.roda.core.data.exceptions.UserAlreadyExistsException; import org.roda.core.data.v2.generics.MetadataValue; +import org.roda.core.data.v2.generics.select.SelectedItemsListRequest; +import org.roda.core.data.v2.generics.select.SelectedItemsRequest; import org.roda.core.data.v2.index.filter.Filter; import org.roda.core.data.v2.index.select.SelectedItems; import org.roda.core.data.v2.index.select.SelectedItemsFilter; @@ -57,8 +62,11 @@ import org.roda.core.data.v2.validation.ValidationException; import org.roda.core.index.IndexService; import org.roda.core.model.ModelService; +import org.roda.core.model.utils.LdapGroup; +import org.roda.core.model.utils.LdapUtility; import org.roda.core.model.utils.UserUtility; import org.roda.core.plugins.base.maintenance.ChangeRodaMemberStatusPlugin; +import org.roda.core.repository.ldap.LdapGroupRepository; import org.roda.core.util.IdUtils; import org.roda.wui.api.v2.exceptions.RESTException; import org.roda.wui.api.v2.utils.CommonServicesUtils; @@ -69,6 +77,8 @@ import org.roda.wui.common.server.ServerTools; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import jakarta.servlet.http.HttpServletRequest; @@ -82,6 +92,9 @@ public class MembersService { private static final Logger LOGGER = LoggerFactory.getLogger(MembersService.class); private static final String RECAPTCHA_CODE_SECRET_PROPERTY = "ui.google.recaptcha.code.secret"; + @Autowired + LdapGroupRepository ldapGroupRepository; + private static List getMemberUuidFromSelectedItems(SelectedItems members) throws GenericException, RequestNotValidException { List uuids = new ArrayList<>(); @@ -202,8 +215,8 @@ public void deleteUser(String username) throws GenericException, AuthorizationDe RodaCoreFactory.getIndexService().commit(true, RODAMember.class); } - public Group retrieveGroup(String groupname) throws GenericException, NotFoundException { - return RodaCoreFactory.getModelService().retrieveGroup(groupname); + public Group retrieveGroup(String groupName) throws GenericException, NotFoundException { + return RodaCoreFactory.getModelService().retrieveGroup(groupName); } public Job changeActiveMembers(SelectedItems selectedItems, boolean active, User user) @@ -297,6 +310,91 @@ public User confirmUserEmail(String username, String email, String emailConfirma return RodaCoreFactory.getModelService().confirmUserEmail(username, email, emailConfirmationToken, true, true); } + public User addGroupsToUser(String id, SelectedItemsRequest groups) + throws GenericException, AuthorizationDeniedException, AlreadyExistsException, NotFoundException { + if (groups instanceof SelectedItemsListRequest listRequest) { + User user = RodaCoreFactory.getModelService().retrieveUser(id); + Set collect = listRequest.getIds().stream().map(m -> m.replace("group-", "")).collect(Collectors.toSet()); + user.getGroups().addAll(collect); + RodaCoreFactory.getModelService().updateUser(user, null, true); + RodaCoreFactory.getIndexService().commit(true, RODAMember.class); + return user; + } + + return new User(); + } + + public Group addMembersToGroup(String id, SelectedItemsRequest members) + throws GenericException, AuthorizationDeniedException, AlreadyExistsException, NotFoundException, IllegalOperationException { + if (members instanceof SelectedItemsListRequest listRequest) { + Set collect = listRequest.getIds().stream().map(m -> m.replace("user-", "")).collect(Collectors.toSet()); + + collect.removeIf(p -> UserUtility.getLdapUtility().isProtectedUser(p)); + + if (collect.isEmpty()) { + throw new IllegalOperationException("All selected users are protected and cannot be added to the group"); + } + + Group group = RodaCoreFactory.getModelService().updateGroupMembers(id, collect, true, false); + RodaCoreFactory.getIndexService().commit(true, RODAMember.class); + return group; + } + + return new Group(); + } + + public Set getGroupFromUser(String id) throws NotFoundException, GenericException { + Set groups = new HashSet<>(); + RodaCoreFactory.getModelService().retrieveUser(id).getGroups().forEach(groupName -> { + try { + Group group = RodaCoreFactory.getModelService().retrieveGroup(groupName); + group.getUsers().clear(); + groups.add(group); + } catch (GenericException | NotFoundException e) { + LOGGER.error("Error retrieving group {}", groupName, e); + } + }); + + return groups; + } + + public Set getGroupMembers(String id) throws NotFoundException, GenericException { + Set members = new HashSet<>(); + RodaCoreFactory.getModelService().retrieveGroup(id).getUsers().forEach(userId -> { + try { + User user = RodaCoreFactory.getModelService().retrieveUser(userId); + user.getGroups().clear(); + user.setResetPasswordToken(null); + user.setResetPasswordTokenExpirationDate(null); + user.setEmailConfirmationToken(null); + user.setEmailConfirmationTokenExpirationDate(null); + members.add(user); + } catch (GenericException e) { + LOGGER.error("Error retrieving user {}", userId, e); + } + }); + + return members; + } + + public User removeGroupFromUser(String id, String groupID) + throws GenericException, AuthorizationDeniedException, AlreadyExistsException, NotFoundException { + User user = RodaCoreFactory.getModelService().retrieveUser(id); + user.getGroups().remove(groupID); + RodaCoreFactory.getModelService().updateUser(user, null, true); + RodaCoreFactory.getIndexService().commit(true, RODAMember.class); + return user; + } + + public Group removeMemberFromGroup(String id, String userId) + throws GenericException, AuthorizationDeniedException, AlreadyExistsException, NotFoundException { + User user = RodaCoreFactory.getModelService().retrieveUser(userId); + user.getGroups().remove(id); + RodaCoreFactory.getModelService().updateUser(user, null, true); + RodaCoreFactory.getIndexService().commit(true, RODAMember.class); + return RodaCoreFactory.getModelService().retrieveGroup(id); + } + public User updateUser(UpdateUserRequest request) throws GenericException, AlreadyExistsException, NotFoundException, AuthorizationDeniedException, ValidationException, RequestNotValidException { ModelService model = RodaCoreFactory.getModelService(); diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/BrowseAIP.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/BrowseAIP.java index b23c196d24..08e8fc3ef1 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/BrowseAIP.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/BrowseAIP.java @@ -346,7 +346,6 @@ private static void refresh(String id, AsyncCallback callback) { }); } }); - } private void updateSectionIdentification(BrowseAIPResponse response) { diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/RODAMemberTabs.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/RODAMemberTabs.java new file mode 100644 index 0000000000..53a51775df --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/RODAMemberTabs.java @@ -0,0 +1,58 @@ +package org.roda.wui.client.browse.tabs; + +import com.google.gwt.safehtml.shared.SafeHtmlUtils; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Widget; +import org.roda.core.data.common.RodaConstants; +import org.roda.core.data.v2.user.RODAMember; +import org.roda.wui.client.common.actions.Actionable; +import org.roda.wui.client.common.utils.PermissionClientUtils; +import org.roda.wui.client.management.members.tabs.AccessKeysTab; +import org.roda.wui.client.management.members.tabs.RODAMemberDetailsPanel; +import org.roda.wui.client.management.members.tabs.RODAMemberGroupsTab; +import org.roda.wui.client.management.members.tabs.RODAMemberPermissionsTab; + +public class RODAMemberTabs extends Tabs { + + public void init(RODAMember member, AsyncCallback actionCallback) { + + int activeIndex = this.getSelectedTabIndex(); + + this.clear(); + + // 1. Clear any existing tabs before building new ones! + createAndAddTab(SafeHtmlUtils.fromSafeConstant(messages.detailsTab()), new TabContentBuilder() { + @Override + public Widget buildTabWidget() { + return new RODAMemberDetailsPanel(member, actionCallback); + } + }); + + createAndAddTab(SafeHtmlUtils.fromSafeConstant(member.isUser() ? messages.groups() : messages.membersTabTitle()), + new TabContentBuilder() { + @Override + public Widget buildTabWidget() { + return new RODAMemberGroupsTab(member, actionCallback); + } + }); + + createAndAddTab(SafeHtmlUtils.fromSafeConstant(messages.permissionsTab()), new TabContentBuilder() { + @Override + public Widget buildTabWidget() { + return new RODAMemberPermissionsTab(member, actionCallback); + } + }); + + if (member.isUser() && PermissionClientUtils.hasPermissions(RodaConstants.PERMISSION_METHOD_REVOKE_ACCESS_TOKEN, + RodaConstants.PERMISSION_METHOD_REGENERATE_ACCESS_TOKEN, RodaConstants.PERMISSION_METHOD_DELETE_ACCESS_TOKEN)) { + createAndAddTab(SafeHtmlUtils.fromSafeConstant(messages.showAccessKeyTitle()), new TabContentBuilder() { + @Override + public Widget buildTabWidget() { + return new AccessKeysTab(member, actionCallback); + } + }); + } + + this.selectTabByIndex(activeIndex); + } +} \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/Tabs.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/Tabs.java index 75f04f1dfe..7e3af0215c 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/Tabs.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/Tabs.java @@ -120,6 +120,27 @@ protected void selectTab(Widget tabButtonContainer) { } } + public void clear() { + tabButtons.clear(); + tabContentWrapper.clear(); + tabs.clear(); + selectedTab = null; + } + + public int getSelectedTabIndex() { + if (selectedTab == null) { + return 0; // Default to first tab + } + return tabButtons.getWidgetIndex(selectedTab); + } + + public void selectTabByIndex(int index) { + if (index >= 0 && index < tabButtons.getWidgetCount()) { + Widget tabToSelect = tabButtons.getWidget(index); + selectTab(tabToSelect); + } + } + public interface TabContentBuilder { public Widget buildTabWidget(); } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/RODAMemberActionsToolbar.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/RODAMemberActionsToolbar.java new file mode 100644 index 0000000000..ac42198e5f --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/RODAMemberActionsToolbar.java @@ -0,0 +1,40 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE file at the root of the source + * tree and available online at + * + * https://github.com/keeps/roda + */ +package org.roda.wui.client.common; + +import java.util.List; + +import org.roda.core.data.v2.user.RODAMember; +import org.roda.wui.client.common.actions.RODAMemberAction; +import org.roda.wui.client.common.actions.RODAMemberToolbarActions; +import org.roda.wui.client.common.actions.model.ActionableObject; +import org.roda.wui.client.common.actions.widgets.ActionableWidgetBuilder; + +/** + * + * @author Miguel Guimarães + */ +public class RODAMemberActionsToolbar extends BrowseObjectActionsToolbar { + public void buildIcon() { + // do nothing + } + + public void buildTags() { + // do nothing + } + + public void buildActions() { + this.actions.clear(); + + RODAMemberToolbarActions rodaMemberActions = RODAMemberToolbarActions.get(); + this.actions.add(new ActionableWidgetBuilder(rodaMemberActions).withActionCallback(actionCallback) + .buildGroupedListWithObjects(new ActionableObject<>(object), + List.of(RODAMemberAction.ACTIVATE, RODAMemberAction.DEACTIVATE, RODAMemberAction.REMOVE), + List.of())); + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberAction.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberAction.java new file mode 100644 index 0000000000..1ed5b7418b --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberAction.java @@ -0,0 +1,32 @@ +package org.roda.wui.client.common.actions; + +import org.roda.core.data.common.RodaConstants; +import org.roda.core.data.v2.user.RODAMember; + +import java.util.Arrays; +import java.util.List; + +/** + * @author Miguel Guimarães + */ + +public enum RODAMemberAction implements Actionable.Action { + NEW_USER(RodaConstants.PERMISSION_METHOD_CREATE_USER), NEW_GROUP(RodaConstants.PERMISSION_METHOD_CREATE_GROUP), + ACTIVATE(RodaConstants.PERMISSION_METHOD_UPDATE_USER), DEACTIVATE(RodaConstants.PERMISSION_METHOD_UPDATE_USER), + EDIT(RodaConstants.PERMISSION_METHOD_UPDATE_USER), REMOVE(RodaConstants.PERMISSION_METHOD_DELETE_USER), + ADD_NEW_GROUP(RodaConstants.PERMISSION_METHOD_UPDATE_USER), + ADD_NEW_MEMBER(RodaConstants.PERMISSION_METHOD_UPDATE_USER), + EDIT_PERMISSIONS(RodaConstants.PERMISSION_METHOD_UPDATE_USER), + NEW_ACCESS_KEY(RodaConstants.PERMISSION_METHOD_CREATE_ACCESS_KEY); + + private final List methods; + + RODAMemberAction(String... methods) { + this.methods = Arrays.asList(methods); + } + + @Override + public List getMethods() { + return this.methods; + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberActions.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberActions.java index 88c67146bf..f0f50550aa 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberActions.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberActions.java @@ -9,24 +9,22 @@ import java.util.Arrays; import java.util.HashSet; -import java.util.List; import java.util.Set; -import org.roda.core.data.common.RodaConstants; +import com.google.gwt.i18n.client.LocaleInfo; import org.roda.core.data.utils.SelectedItemsUtils; import org.roda.core.data.v2.index.select.SelectedItems; import org.roda.core.data.v2.user.RODAMember; import org.roda.core.data.v2.user.requests.ChangeUserStatusRequest; +import org.roda.core.data.v2.user.requests.CreateGroupRequest; +import org.roda.core.data.v2.user.requests.CreateUserRequest; +import org.roda.wui.client.common.actions.callbacks.ActionAsyncCallback; import org.roda.wui.client.common.actions.model.ActionableBundle; import org.roda.wui.client.common.actions.model.ActionableGroup; import org.roda.wui.client.common.dialogs.Dialogs; +import org.roda.wui.client.common.dialogs.RODAMembersDialogs; import org.roda.wui.client.ingest.process.ShowJob; -import org.roda.wui.client.management.CreateGroup; -import org.roda.wui.client.management.CreateUser; -import org.roda.wui.client.management.EditGroup; -import org.roda.wui.client.management.EditUser; -import org.roda.wui.client.management.MemberManagement; -import org.roda.wui.client.management.access.CreateAccessKey; +import org.roda.wui.client.management.members.MemberManagement; import org.roda.wui.client.services.Services; import org.roda.wui.common.client.tools.HistoryUtils; import org.roda.wui.common.client.widgets.Toast; @@ -50,7 +48,7 @@ public class RODAMemberActions extends AbstractActionable { Arrays.asList(RODAMemberAction.ACTIVATE, RODAMemberAction.DEACTIVATE, RODAMemberAction.REMOVE)); private static final Set POSSIBLE_ACTIONS_ON_GROUP = new HashSet<>( - Arrays.asList(RODAMemberAction.EDIT, RODAMemberAction.REMOVE)); + Arrays.asList(RODAMemberAction.REMOVE)); private static final Set POSSIBLE_ACTIONS_ON_MEMBERS = new HashSet<>( Arrays.asList(RODAMemberAction.ACTIVATE, RODAMemberAction.DEACTIVATE, RODAMemberAction.REMOVE)); @@ -92,11 +90,8 @@ public CanActResult userCanAct(Action action, RODAMember object) { @Override public CanActResult contextCanAct(Action action, RODAMember object) { if (object.isUser()) { - return new CanActResult( - (action.equals(RODAMemberAction.DEACTIVATE) && object.isActive()) - || (action.equals(RODAMemberAction.ACTIVATE) && !object.isActive()) - || (action.equals(RODAMemberAction.REMOVE) || (action.equals(RODAMemberAction.EDIT) - || (action.equals(RODAMemberAction.NEW_ACCESS_KEY) && object.isUser()))), + return new CanActResult((action.equals(RODAMemberAction.DEACTIVATE) && object.isActive()) + || (action.equals(RODAMemberAction.ACTIVATE) && !object.isActive()) || (action.equals(RODAMemberAction.REMOVE)), CanActResult.Reason.CONTEXT, messages.reasonCantActOnUser()); } else { return new CanActResult(POSSIBLE_ACTIONS_ON_GROUP.contains(action), CanActResult.Reason.CONTEXT, @@ -132,12 +127,8 @@ public void act(Action action, RODAMember object, AsyncCallback callback) { - callback.onSuccess(ActionImpact.NONE); - if (object.isUser()) { - HistoryUtils.newHistory(EditUser.RESOLVER, object.getId()); - } else { - HistoryUtils.newHistory(EditGroup.RESOLVER, object.getId()); - } - } - private void deactivate(SelectedItems objects, AsyncCallback callback) { Services services = new Services("Deactivate RODA member", "deactivate"); ChangeUserStatusRequest request = new ChangeUserStatusRequest(SelectedItemsUtils.convertToRESTRequest(objects), @@ -240,18 +222,51 @@ public void onFailure(Throwable caught) { } private void createUser(AsyncCallback callback) { - callback.onSuccess(ActionImpact.NONE); - HistoryUtils.newHistory(CreateUser.RESOLVER.getHistoryPath()); + RODAMembersDialogs.createUser(messages.addUserButton(), messages.cancelButton(), messages.saveButton(), + new ActionAsyncCallback(callback) { + @Override + public void onSuccess(CreateUserRequest result) { + Services services = new Services("Create User", "create"); + services.membersResource(s -> s.createUser(result, LocaleInfo.getCurrentLocale().getLocaleName())) + .whenComplete((res, error) -> { + if (error == null) { + Toast.showInfo(messages.users(), "User successfully created"); + doActionCallbackUpdated(); + } else { + doActionCallbackNone(); + } + }); + } + + @Override + public void onFailure(Throwable caught) { + doActionCallbackNone(); + } + }); } private void createGroup(AsyncCallback callback) { - callback.onSuccess(ActionImpact.NONE); - HistoryUtils.newHistory(CreateGroup.RESOLVER.getHistoryPath()); - } + RODAMembersDialogs.createGroup(messages.addGroupButton(), messages.cancelButton(), messages.saveButton(), + new ActionAsyncCallback(callback) { + + @Override + public void onSuccess(CreateGroupRequest createGroupRequest) { + Services services = new Services("Create Group", "create"); + services.membersResource(s -> s.createGroup(createGroupRequest)).whenComplete((res, error) -> { + if (error == null) { + Toast.showInfo(messages.groups(), "Group successfully created"); + doActionCallbackUpdated(); + } else { + doActionCallbackNone(); + } + }); + } - private void createNewAccessKey(AsyncCallback callback, RODAMember user) { - callback.onSuccess(ActionImpact.NONE); - HistoryUtils.newHistory(CreateAccessKey.RESOLVER, user.getName()); + @Override + public void onFailure(Throwable caught) { + doActionCallbackNone(); + } + }); } @Override @@ -264,35 +279,13 @@ public ActionableBundle createActionsBundle() { "btn-plus-circle"); actionableGroup.addButton(messages.addGroupButton(), RODAMemberAction.NEW_GROUP, ActionImpact.UPDATED, "btn-plus-circle"); - actionableGroup.addButton(messages.editUserAction(), RODAMemberAction.EDIT, ActionImpact.UPDATED, "btn-edit"); - actionableGroup.addButton(messages.editUserActivate(), RODAMemberAction.ACTIVATE, ActionImpact.UPDATED, "btn-enable-user"); actionableGroup.addButton(messages.editUserDeactivate(), RODAMemberAction.DEACTIVATE, ActionImpact.UPDATED, "btn-disable-user"); - actionableGroup.addButton(messages.addAccessKeyButton(), RODAMemberAction.NEW_ACCESS_KEY, ActionImpact.UPDATED, - "btn-key"); actionableGroup.addButton(messages.editUserRemove(), RODAMemberAction.REMOVE, ActionImpact.DESTROYED, "btn-ban"); transferredResourcesActionableBundle.addGroup(actionableGroup); return transferredResourcesActionableBundle; } - - public enum RODAMemberAction implements Action { - NEW_USER(RodaConstants.PERMISSION_METHOD_CREATE_USER), NEW_GROUP(RodaConstants.PERMISSION_METHOD_CREATE_GROUP), - ACTIVATE(RodaConstants.PERMISSION_METHOD_UPDATE_USER), DEACTIVATE(RodaConstants.PERMISSION_METHOD_UPDATE_USER), - EDIT(RodaConstants.PERMISSION_METHOD_UPDATE_USER), REMOVE(RodaConstants.PERMISSION_METHOD_DELETE_USER), - NEW_ACCESS_KEY(RodaConstants.PERMISSION_METHOD_CREATE_ACCESS_KEY); - - private List methods; - - RODAMemberAction(String... methods) { - this.methods = Arrays.asList(methods); - } - - @Override - public List getMethods() { - return this.methods; - } - } } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberToolbarActions.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberToolbarActions.java new file mode 100644 index 0000000000..baf4b8e973 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberToolbarActions.java @@ -0,0 +1,406 @@ +package org.roda.wui.client.common.actions; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.safehtml.shared.SafeHtmlUtils; +import com.google.gwt.user.client.rpc.AsyncCallback; +import config.i18n.client.ClientMessages; + +import org.roda.core.data.common.RodaConstants; +import org.roda.core.data.utils.SelectedItemsUtils; +import org.roda.core.data.v2.accessKey.CreateAccessKeyRequest; +import org.roda.core.data.v2.index.filter.BasicSearchFilterParameter; +import org.roda.core.data.v2.index.filter.Filter; +import org.roda.core.data.v2.index.filter.NotSimpleFilterParameter; +import org.roda.core.data.v2.index.select.SelectedItems; +import org.roda.core.data.v2.user.Group; +import org.roda.core.data.v2.user.RODAMember; +import org.roda.core.data.v2.user.User; +import org.roda.core.data.v2.user.requests.ChangeUserStatusRequest; +import org.roda.core.data.v2.user.requests.UpdateUserRequest; +import org.roda.wui.client.common.NoAsyncCallback; +import org.roda.wui.client.common.actions.callbacks.ActionAsyncCallback; +import org.roda.wui.client.common.actions.callbacks.ActionNoAsyncCallback; +import org.roda.wui.client.common.actions.model.ActionableBundle; +import org.roda.wui.client.common.actions.model.ActionableGroup; +import org.roda.wui.client.common.dialogs.AccessKeyDialogs; +import org.roda.wui.client.common.dialogs.Dialogs; +import org.roda.wui.client.common.dialogs.RODAMembersDialogs; +import org.roda.wui.client.ingest.process.ShowJob; +import org.roda.wui.client.management.members.MemberManagement; +import org.roda.wui.client.management.members.tabs.PermissionsPanel; +import org.roda.wui.client.services.Services; +import org.roda.wui.common.client.tools.HistoryUtils; +import org.roda.wui.common.client.widgets.Toast; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * @author Miguel Guimarães + */ + +public class RODAMemberToolbarActions extends AbstractActionable { + private static final ClientMessages messages = GWT.create(ClientMessages.class); + + private static final Set POSSIBLE_ACTIONS_ON_USER = new HashSet<>( + Arrays.asList(RODAMemberAction.ACTIVATE, RODAMemberAction.DEACTIVATE, RODAMemberAction.REMOVE)); + + private static final Set POSSIBLE_ACTIONS_ON_GROUP = new HashSet<>( + Arrays.asList(RODAMemberAction.EDIT, RODAMemberAction.EDIT_PERMISSIONS, RODAMemberAction.REMOVE, + RODAMemberAction.ADD_NEW_MEMBER)); + + private static final RODAMemberToolbarActions GENERAL_INSTANCE = new RODAMemberToolbarActions(); + + private RODAMemberToolbarActions() { + } + + public static RODAMemberToolbarActions get() { + return GENERAL_INSTANCE; + } + + @Override + public RODAMemberAction[] getActions() { + return RODAMemberAction.values(); + } + + @Override + public RODAMemberAction actionForName(String name) { + return RODAMemberAction.valueOf(name); + } + + @Override + public CanActResult userCanAct(Action action, RODAMember object) { + return new CanActResult(hasPermissions(action), CanActResult.Reason.USER, messages.reasonUserLacksPermission()); + } + + @Override + public CanActResult contextCanAct(Action action, RODAMember object) { + if (object.isUser()) { + return new CanActResult( + (action.equals(RODAMemberAction.DEACTIVATE) && object.isActive()) + || (action.equals(RODAMemberAction.ACTIVATE) && !object.isActive()) + || (action.equals(RODAMemberAction.REMOVE) + || (action.equals(RODAMemberAction.EDIT) || action.equals(RODAMemberAction.EDIT_PERMISSIONS) + || (action.equals(RODAMemberAction.ADD_NEW_GROUP) && object.isUser()) + || (action.equals(RODAMemberAction.NEW_ACCESS_KEY) && object.isUser()))), + CanActResult.Reason.CONTEXT, messages.reasonCantActOnUser()); + } else { + return new CanActResult(POSSIBLE_ACTIONS_ON_GROUP.contains(action), CanActResult.Reason.CONTEXT, + messages.reasonCantActOnGroup()); + } + } + + @Override + public void act(Action action, RODAMember object, AsyncCallback callback) { + if (action.equals(RODAMemberAction.ACTIVATE)) { + activate(object, callback); + } else if (action.equals(RODAMemberAction.DEACTIVATE)) { + deactivate(object, callback); + } else if (action.equals(RODAMemberAction.EDIT)) { + edit(object, callback); + } else if (action.equals(RODAMemberAction.REMOVE)) { + remove(object, callback); + } else if (action.equals(RODAMemberAction.NEW_ACCESS_KEY)) { + createNewAccessKey(callback, object); + } else if (action.equals(RODAMemberAction.ADD_NEW_GROUP)) { + addNewGroup(object, callback); + } else if (action.equals(RODAMemberAction.ADD_NEW_MEMBER)) { + addNewMember(object, callback); + } else if (action.equals(RODAMemberAction.EDIT_PERMISSIONS)) { + editPermissions(object, callback); + } else { + unsupportedAction(action, callback); + } + } + + private void editPermissions(RODAMember object, AsyncCallback callback) { + PermissionsPanel permissionsPanel = new PermissionsPanel(object, false, true); + + RODAMembersDialogs.showEditRODAMemberPermissionsPanel(messages.editPermissionsModalTitle(), messages.cancelButton(), + messages.updateButton(), permissionsPanel, new ActionAsyncCallback>(callback) { + @Override + public void onSuccess(List result) { + Services services = new Services("Edit RODA member permissions", "update"); + if (object.isUser()) { + UpdateUserRequest request = new UpdateUserRequest(); + User user = (User) object; + user.setDirectRoles(new HashSet<>(result)); + request.setPassword(null); + request.setUser(user); + services.membersResource(s -> s.updateUser(request)).whenComplete((res, error) -> { + if (error != null) { + doActionCallbackUpdated(); + Toast.showError(error.getMessage()); + } else { + doActionCallbackUpdated(); + Toast.showInfo(messages.userPermissions(), messages.permissionsUpdateWithSuccess()); + } + }); + } else { + Group group = (Group) object; + group.setDirectRoles(new HashSet<>(result)); + services.membersResource(s -> s.updateGroup(group)).whenComplete((res, error) -> { + if (error != null) { + doActionCallbackUpdated(); + Toast.showError(error.getMessage()); + } else { + doActionCallbackUpdated(); + Toast.showInfo(messages.groupPermissions(), messages.permissionsUpdateWithSuccess()); + } + }); + } + } + }); + } + + private void addNewMember(RODAMember object, AsyncCallback callback) { + Filter filter = new Filter(new BasicSearchFilterParameter(RodaConstants.MEMBERS_IS_USER, "true")); + ((Group) object).getUsers() + .forEach(user -> filter.add(new NotSimpleFilterParameter(RodaConstants.MEMBERS_NAME, user))); + + RODAMembersDialogs.showAddGroupsToRODAMember(SafeHtmlUtils.fromSafeConstant(messages.addNewMemberToGroupTitle()), + messages.cancelButton(), messages.confirmButton(), filter, + new ActionAsyncCallback>(callback) { + @Override + public void onSuccess(SelectedItems result) { + Services services = new Services("Add member to RODA group", "update"); + services + .membersResource(s -> s.addMembersToGroup(object.getId(), SelectedItemsUtils.convertToRESTRequest(result))) + .whenComplete((group, error) -> { + if (error != null) { + doActionCallbackUpdated(); + Toast.showError(error.getMessage()); + } else { + doActionCallbackUpdated(); + Toast.showInfo(messages.membersTabTitle(), messages.memberSuccessfullyAdded()); + } + }); + } + }); + } + + private void addNewGroup(RODAMember object, AsyncCallback callback) { + Filter filter = new Filter(new BasicSearchFilterParameter(RodaConstants.MEMBERS_IS_USER, "false")); + ((User) object).getGroups() + .forEach(group -> filter.add(new NotSimpleFilterParameter(RodaConstants.MEMBERS_NAME, group))); + + RODAMembersDialogs.showAddGroupsToRODAMember(SafeHtmlUtils.fromSafeConstant(messages.addNewGroupModalTitle()), + messages.cancelButton(), messages.confirmButton(), filter, + new ActionAsyncCallback>(callback) { + @Override + public void onSuccess(SelectedItems result) { + Services services = new Services("Add groups to RODA member", "update"); + services + .membersResource(s -> s.addGroupsToUser(object.getId(), SelectedItemsUtils.convertToRESTRequest(result))) + .whenComplete((res, error) -> { + if (error != null) { + doActionCallbackUpdated(); + Toast.showError(error.getMessage()); + } else { + doActionCallbackUpdated(); + Toast.showInfo(messages.groups(), messages.groupSuccessfullyAdded()); + } + }); + } + }); + } + + private void activate(RODAMember object, AsyncCallback callback) { + Dialogs.showConfirmDialog(messages.activateUserTitle(), messages.activateUserConfirmationMessage(), + messages.dialogNo(), messages.dialogYes(), new ActionAsyncCallback(callback) { + @Override + public void onSuccess(Boolean confirmed) { + if (confirmed) { + Services services = new Services("Activate RODA member", "activate"); + ChangeUserStatusRequest request = new ChangeUserStatusRequest( + SelectedItemsUtils.convertToRESTRequest(objectToSelectedItems(object, RODAMember.class)), true); + services.membersResource(s -> s.changeActive(request)).whenComplete((res, error) -> { + if (error == null) { + Toast.showInfo(messages.runningInBackgroundTitle(), messages.runningInBackgroundDescription()); + Dialogs.showJobRedirectDialog(messages.jobCreatedMessage(), new AsyncCallback() { + + @Override + public void onFailure(Throwable caught) { + doActionCallbackUpdated(); + } + + @Override + public void onSuccess(final Void nothing) { + callback.onSuccess(ActionImpact.NONE); + HistoryUtils.newHistory(ShowJob.RESOLVER, res.getId()); + } + }); + } + }); + } else { + doActionCallbackNone(); + } + } + + @Override + public void onFailure(Throwable caught) { + callback.onFailure(caught); + } + }); + } + + private void edit(RODAMember object, AsyncCallback callback) { + if (object.isUser()) { + RODAMembersDialogs.editUserInformation(messages.editUserTitle(), messages.cancelButton(), messages.updateButton(), + (User) object, new ActionAsyncCallback(callback) { + @Override + public void onSuccess(UpdateUserRequest result) { + Services services = new Services("Update User", "update"); + services.membersResource(s -> s.updateUser(result)).whenComplete((res, error) -> { + if (error == null) { + Toast.showInfo(messages.users(), "User successfully updated"); + doActionCallbackUpdated(); + } else { + doActionCallbackNone(); + } + }); + } + }); + } else { + RODAMembersDialogs.editGroupInformation(messages.editGroupTitle(), messages.cancelButton(), + messages.updateButton(), (Group) object, new ActionAsyncCallback(callback) { + @Override + public void onSuccess(Group result) { + Services services = new Services("Update group", "update"); + services.membersResource(s -> s.updateGroup(result)).whenComplete((res, error) -> { + if (error == null) { + Toast.showInfo(messages.groups(), "Group successfully updated"); + doActionCallbackUpdated(); + } else { + doActionCallbackNone(); + } + }); + } + }); + } + } + + private void deactivate(RODAMember object, AsyncCallback callback) { + Dialogs.showConfirmDialog(messages.deactivateUserTitle(), messages.deactivateUserConfirmationMessage(), + messages.dialogNo(), messages.dialogYes(), new ActionAsyncCallback(callback) { + @Override + public void onSuccess(Boolean confirmed) { + if (confirmed) { + Services services = new Services("Deactivate RODA member", "deactivate"); + ChangeUserStatusRequest request = new ChangeUserStatusRequest( + SelectedItemsUtils.convertToRESTRequest(objectToSelectedItems(object, RODAMember.class)), false); + services.membersResource(s -> s.changeActive(request)).whenComplete((res, error) -> { + if (error == null) { + Toast.showInfo(messages.runningInBackgroundTitle(), messages.runningInBackgroundDescription()); + Dialogs.showJobRedirectDialog(messages.jobCreatedMessage(), new AsyncCallback() { + + @Override + public void onFailure(Throwable caught) { + doActionCallbackUpdated(); + } + + @Override + public void onSuccess(final Void nothing) { + callback.onSuccess(ActionImpact.NONE); + HistoryUtils.newHistory(ShowJob.RESOLVER, res.getId()); + } + }); + } + }); + } else { + doActionCallbackNone(); + } + } + + @Override + public void onFailure(Throwable caught) { + doActionCallbackNone(); + callback.onFailure(caught); + } + }); + } + + private void remove(RODAMember object, AsyncCallback callback) { + Dialogs.showConfirmDialog(object.isUser() ? messages.singleUserRemoveConfirmDialogTitle() : messages.singleGroupRemoveConfirmDialogTitle(), + object.isUser() ? messages.singleUserRemoveConfirmDialogMessage(object.getId()) : messages.singleGroupRemoveConfirmDialogMessage(object.getId()), messages.dialogNo(), messages.dialogYes(), + new ActionAsyncCallback(callback) { + @Override + public void onSuccess(Boolean confirmed) { + if (confirmed) { + Services services = new Services("Remove RODA members", "remove"); + services + .membersResource(s -> s.deleteMultipleMembers( + SelectedItemsUtils.convertToRESTRequest(objectToSelectedItems(object, RODAMember.class)))) + .whenComplete((res, error) -> { + if (error == null) { + callback.onSuccess(Actionable.ActionImpact.DESTROYED); + } else { + Toast.showError(error.getMessage()); + callback.onSuccess(Actionable.ActionImpact.NONE); + } + }); + } else { + doActionCallbackNone(); + } + } + + @Override + public void onFailure(Throwable caught) { + callback.onFailure(caught); + } + }); + } + + private void createNewAccessKey(AsyncCallback callback, RODAMember user) { + callback.onSuccess(ActionImpact.NONE); + AccessKeyDialogs.createAccessKeyDialog(messages.createAccessKeyTitle(), null, true, + new ActionNoAsyncCallback(callback) { + @Override + public void onSuccess(CreateAccessKeyRequest keyRequest) { + Services services = new Services("Create access key", "create"); + CreateAccessKeyRequest createAccessKeyRequest = new CreateAccessKeyRequest(keyRequest.getName(), + keyRequest.getExpirationDate()); + services.membersResource(s -> s.createAccessKey(user.getId(), createAccessKeyRequest)) + .whenComplete((response, error) -> { + if (response != null) { + AccessKeyDialogs.showAccessKeyDialog(messages.accessKeyLabel(), response, + new NoAsyncCallback() { + @Override + public void onSuccess(Boolean result) { + callback.onSuccess(ActionImpact.UPDATED); + } + }); + } + }); + } + }); + } + + @Override + public ActionableBundle createActionsBundle() { + ActionableBundle actionableBundle = new ActionableBundle<>(); + + // ACTIONS + ActionableGroup actionableGroup = new ActionableGroup<>(messages.manage(), "btn-edit"); + actionableGroup.addButton(messages.editUserAction(), RODAMemberAction.EDIT, ActionImpact.UPDATED, "btn-edit"); + + actionableGroup.addButton(messages.editUserActivate(), RODAMemberAction.ACTIVATE, ActionImpact.UPDATED, + "btn-enable-user"); + actionableGroup.addButton(messages.editUserDeactivate(), RODAMemberAction.DEACTIVATE, ActionImpact.UPDATED, + "btn-disable-user"); + actionableGroup.addButton(messages.addAccessKeyButton(), RODAMemberAction.NEW_ACCESS_KEY, ActionImpact.UPDATED, + "btn-key"); + actionableGroup.addButton(messages.addToGroupButton(), RODAMemberAction.ADD_NEW_GROUP, ActionImpact.UPDATED, + "btn-group"); + actionableGroup.addButton(messages.addNewMemberToGroupButton(), RODAMemberAction.ADD_NEW_MEMBER, + ActionImpact.UPDATED, "btn-user"); + actionableGroup.addButton(messages.editButton(), RODAMemberAction.EDIT_PERMISSIONS, ActionImpact.UPDATED, + "btn-edit"); + actionableGroup.addButton(messages.editUserRemove(), RODAMemberAction.REMOVE, ActionImpact.DESTROYED, "btn-ban"); + + actionableBundle.addGroup(actionableGroup); + return actionableBundle; + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/callbacks/ActionNoAsyncCallback.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/callbacks/ActionNoAsyncCallback.java index 803cd149a5..20e9a62bae 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/callbacks/ActionNoAsyncCallback.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/callbacks/ActionNoAsyncCallback.java @@ -11,7 +11,6 @@ import org.roda.wui.client.common.actions.Actionable; import com.google.gwt.user.client.rpc.AsyncCallback; -import org.roda.wui.client.services.Services; /** * @author Bruno Ferreira diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/dialogs/AccessKeyDialogs.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/dialogs/AccessKeyDialogs.java index f3f40d37ef..1fafe165fe 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/dialogs/AccessKeyDialogs.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/dialogs/AccessKeyDialogs.java @@ -8,10 +8,12 @@ package org.roda.wui.client.common.dialogs; import com.google.gwt.i18n.client.DateTimeFormat; -import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.datepicker.client.DateBox; import org.roda.core.data.v2.accessKey.AccessKey; +import org.roda.core.data.v2.accessKey.CreateAccessKeyRequest; import org.roda.wui.client.common.utils.JavascriptUtils; +import org.roda.wui.common.client.tools.StringUtils; import org.roda.wui.common.client.widgets.Toast; import com.google.gwt.core.client.GWT; @@ -100,17 +102,30 @@ public void onClick(ClickEvent clickEvent) { dialogBox.show(); } - public static void showRegenerateAccessKeyDialog(String title, final AsyncCallback callback) { + public static void createAccessKeyDialog(String title, String tokenName, boolean create, + final AsyncCallback callback) { final DialogBox dialogBox = new DialogBox(false, true); final Button cancelButton = new Button(messages.cancelButton()); final Button confirmButton = new Button(messages.confirmButton()); final FlowPanel layout = new FlowPanel(); final FlowPanel header = new FlowPanel(); final FlowPanel footer = new FlowPanel(); - final Label expirationDateLabel = new Label("Expiration date"); + + final Label tokenNameLabel = new Label(messages.accessKeyNameLabel()); + final TextBox tokenNameTextBox = new TextBox(); + final Label tokenNameTextBoxErrorLabel = new Label(messages.mandatoryField()); + + if (!create) { + tokenNameTextBox.setText(tokenName); + tokenNameTextBox.setEnabled(false); + } + + final Label expirationDateLabel = new Label(messages.accessKeyExpirationDateLabel()); final DateBox expirationDateBox = new DateBox(); - final Label expirationDateErrorLabel = new Label("Invalid Date"); + final Label expirationDateErrorLabel = new Label(); + tokenNameTextBoxErrorLabel.addStyleName("form-label-error"); + tokenNameTextBoxErrorLabel.setVisible(false); expirationDateErrorLabel.addStyleName("form-label-error"); expirationDateErrorLabel.setVisible(false); @@ -124,21 +139,51 @@ public static void showRegenerateAccessKeyDialog(String title, final AsyncCallba dialogBox.setText(title); layout.add(header); layout.add(footer); + + header.add(tokenNameLabel); + header.add(tokenNameTextBox); + header.add(tokenNameTextBoxErrorLabel); + header.add(expirationDateLabel); header.add(expirationDateBox); header.add(expirationDateErrorLabel); + footer.add(cancelButton); footer.add(confirmButton); confirmButton.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent clickEvent) { + boolean errors = false; + String name = tokenNameTextBox.getText(); + if (create && (name == null || StringUtils.isBlank(name))) { + tokenNameTextBoxErrorLabel.setVisible(true); + errors = true; + } else { + tokenNameTextBoxErrorLabel.setVisible(false); + } + Date selectedDate = expirationDateBox.getValue(); - if (selectedDate == null || selectedDate.before(new Date())) { + if (selectedDate == null) { expirationDateErrorLabel.setVisible(true); + expirationDateErrorLabel.setText(messages.mandatoryField()); + errors = true; + } else if (selectedDate.before(new Date())) { + expirationDateErrorLabel.setVisible(true); + expirationDateErrorLabel.setText(messages.accessKeyExpirationDateInThePast()); + errors = true; } else { + expirationDateErrorLabel.setVisible(false); + } + + if (!errors) { dialogBox.hide(); - callback.onSuccess(selectedDate); + CreateAccessKeyRequest request = new CreateAccessKeyRequest(); + if (create) { + request.setName(tokenNameTextBox.getText()); + } + request.setExpirationDate(selectedDate); + callback.onSuccess(request); } } }); @@ -160,6 +205,8 @@ public void onClick(ClickEvent clickEvent) { header.addStyleName("wui-dialog-message"); footer.addStyleName("wui-dialog-layout-footer"); + tokenNameLabel.addStyleName("form-label"); + tokenNameTextBox.addStyleName("form-textbox form-textbox-small"); expirationDateLabel.addStyleName("form-label"); expirationDateBox.addStyleName("form-textbox form-textbox-small"); confirmButton.addStyleName("btn btn-play"); diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/dialogs/Dialogs.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/dialogs/Dialogs.java index 0f9fd7525f..56f803cb60 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/dialogs/Dialogs.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/dialogs/Dialogs.java @@ -7,11 +7,9 @@ */ package org.roda.wui.client.common.dialogs; -import org.roda.core.data.v2.ip.IndexedFile; import org.roda.wui.client.common.NoAsyncCallback; import org.roda.wui.client.common.search.SearchSuggestBox; import org.roda.wui.client.common.utils.JavascriptUtils; -import org.roda.wui.common.client.tools.RestUtils; import org.roda.wui.common.client.tools.StringUtils; import org.roda.wui.common.client.widgets.Toast; @@ -22,8 +20,6 @@ import com.google.gwt.regexp.shared.RegExp; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlUtils; -import com.google.gwt.safehtml.shared.SafeUri; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.DialogBox; @@ -140,56 +136,6 @@ public static void showInformationDialog(String title, final String message, Str showInformationDialog(title, message, continueButtonText, canCopyMessage, new NoAsyncCallback<>()); } - public static void showTechnicalMetadataInformation(String title, String downloadText, String closeText, - IndexedFile file, String html) { - final DialogBox dialogBox = new ClosableDialog(true, true); - FlowPanel main = new FlowPanel(); - ScrollPanel layout = new ScrollPanel(); - FlowPanel footer = new FlowPanel(); - final Button downloadButton = new Button(downloadText); - final Button closeButton = new Button(closeText); - layout.setSize("70vw", "60vh"); - - VerticalPanel verticalPanel = new VerticalPanel(); - verticalPanel.setWidth("100%"); - if (html == null) { - html = "

No technical metadata found. Please contact system administrator

"; - } - HTML keyHtml = new HTML(html); - keyHtml.setStyleName("value-overflow"); - verticalPanel.add(keyHtml); - - layout.add(verticalPanel); - layout.addStyleName("wui-dialog-message"); - - footer.add(closeButton); - footer.add(downloadButton); - footer.addStyleName("wui-dialog-layout-footer"); - - downloadButton.addStyleName("btn btn-download"); - closeButton.addStyleName("btn btn-link"); - main.addStyleName("wui-dialog-layout"); - main.add(layout); - main.add(footer); - - closeButton.addClickHandler(event -> { - dialogBox.hide(); - }); - - downloadButton.addClickHandler(event -> { - SafeUri downloadUri = RestUtils.createTechnicalMetadataDownloadUri(file.getUUID()); - Window.Location.assign(downloadUri.asString()); - }); - - dialogBox.setWidget(main); - dialogBox.setText(title); - dialogBox.addStyleName("wui-dialog-information"); - dialogBox.setGlassEnabled(true); - dialogBox.setAnimationEnabled(false); - dialogBox.center(); - dialogBox.show(); - } - public static void showInformationDialog(String title, final String message, String continueButtonText, boolean canCopyMessage, final AsyncCallback callback) { final DialogBox dialogBox = new DialogBox(false, true); diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/dialogs/MemberSelectDialog.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/dialogs/MemberSelectDialog.java index 8287ff6df0..afe832809e 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/dialogs/MemberSelectDialog.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/dialogs/MemberSelectDialog.java @@ -18,6 +18,6 @@ public MemberSelectDialog(String title, Filter filter) { super(title, new ListBuilder<>(() -> new RodaMemberList(), new AsyncTableCellOptions<>(RODAMember.class, "MemberSelectDialog_rodaMembers").withFilter(filter) - .withSummary(title))); + .withCsvDownloadButtonVisibility(false).withSummary(title))); } } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/dialogs/RODAMembersDialogs.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/dialogs/RODAMembersDialogs.java new file mode 100644 index 0000000000..470f484abc --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/dialogs/RODAMembersDialogs.java @@ -0,0 +1,445 @@ +package org.roda.wui.client.common.dialogs; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.safehtml.shared.SafeHtml; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.DialogBox; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.Widget; +import config.i18n.client.ClientMessages; +import org.roda.core.data.common.SecureString; +import org.roda.core.data.v2.index.filter.Filter; +import org.roda.core.data.v2.index.select.SelectedItems; +import org.roda.core.data.v2.index.select.SelectedItemsList; +import org.roda.core.data.v2.index.select.SelectedItemsNone; +import org.roda.core.data.v2.user.Group; +import org.roda.core.data.v2.user.RODAMember; +import org.roda.core.data.v2.user.User; +import org.roda.core.data.v2.user.requests.CreateGroupRequest; +import org.roda.core.data.v2.user.requests.CreateUserRequest; +import org.roda.core.data.v2.user.requests.UpdateUserRequest; +import org.roda.wui.client.common.lists.RodaMemberList; +import org.roda.wui.client.common.lists.utils.AsyncTableCell; +import org.roda.wui.client.common.lists.utils.AsyncTableCellOptions; +import org.roda.wui.client.common.lists.utils.ListBuilder; +import org.roda.wui.client.common.search.SearchWrapper; +import org.roda.wui.client.management.members.CreateUserPanel; +import org.roda.wui.client.management.members.GroupDataPanel; +import org.roda.wui.client.management.members.UserDataPanel; +import org.roda.wui.client.management.members.tabs.PermissionsPanel; + +import java.util.HashSet; +import java.util.List; + +/** + * @author Miguel Guimarães + */ + +public class RODAMembersDialogs { + private static final ClientMessages messages = GWT.create(ClientMessages.class); + + private RODAMembersDialogs() { + // private method + } + + public static void createGroup(final String title, final String cancelButtonText, final String saveButtonText, + final AsyncCallback callback) { + final DialogBox dialogBox = new DialogBox(false, true); + dialogBox.setHTML(title); + dialogBox.addStyleName("create-group-dialog"); + + final FlowPanel layout = new FlowPanel(); + + dialogBox.addStyleName("wui-dialog-prompt"); + layout.addStyleName("wui-dialog-layout"); + + final FlowPanel buttonPanel = new FlowPanel(); + final Button cancelButton = new Button(cancelButtonText); + final Button saveButton = new Button(saveButtonText); + buttonPanel.add(cancelButton); + buttonPanel.add(saveButton); + + final FlowPanel content = new FlowPanel(); + GroupDataPanel groupDataPanel = new GroupDataPanel(false); + + content.add(groupDataPanel); + layout.add(content); + layout.add(buttonPanel); + dialogBox.setWidget(layout); + + dialogBox.setGlassEnabled(true); + dialogBox.setAnimationEnabled(false); + + cancelButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + dialogBox.hide(); + callback.onFailure(null); + } + }); + + saveButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + if (!groupDataPanel.isChanged()) { + dialogBox.hide(); + callback.onFailure(null); + } else { + if (groupDataPanel.isValid()) { + dialogBox.hide(); + Group group = groupDataPanel.getGroup(); + CreateGroupRequest createGroupRequest = new CreateGroupRequest(group.getName(), group.getFullName(), + new HashSet<>()); + callback.onSuccess(createGroupRequest); + } + } + } + }); + + cancelButton.addStyleName("btn btn-link"); + saveButton.addStyleName("pull-right btn btn-play"); + + dialogBox.center(); + dialogBox.show(); + } + + public static void createUser(final String title, final String cancelButtonText, final String saveButtonText, + final AsyncCallback callback) { + final DialogBox dialogBox = new DialogBox(false, true); + dialogBox.setHTML(title); + dialogBox.addStyleName("create-user-dialog"); + + final FlowPanel layout = new FlowPanel(); + + dialogBox.addStyleName("wui-dialog-prompt"); + layout.addStyleName("wui-dialog-layout"); + + final FlowPanel buttonPanel = new FlowPanel(); + final Button cancelButton = new Button(cancelButtonText); + final Button saveButton = new Button(saveButtonText); + buttonPanel.add(cancelButton); + buttonPanel.add(saveButton); + + final FlowPanel content = new FlowPanel(); + CreateUserPanel userPanel = new CreateUserPanel(); + + userPanel.setOnFormReadyCallback(new Runnable() { + @Override + public void run() { + dialogBox.center(); + } + }); + + content.add(userPanel); + layout.add(content); + layout.add(buttonPanel); + dialogBox.setWidget(layout); + + dialogBox.setGlassEnabled(true); + dialogBox.setAnimationEnabled(false); + + cancelButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + dialogBox.hide(); + callback.onFailure(null); + } + }); + + saveButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + if (!userPanel.isChanged()) { + dialogBox.hide(); + callback.onFailure(null); + } else { + if (userPanel.isValid()) { + dialogBox.hide(); + User user = userPanel.getUser(); + CreateUserRequest request = new CreateUserRequest(user.getEmail(), user.getName(), user.getFullName(), + new HashSet<>(), new HashSet<>(), user.isGuest(), null, userPanel.getUserExtra()); + callback.onSuccess(request); + } + } + } + }); + + cancelButton.addStyleName("btn btn-link"); + saveButton.addStyleName("pull-right btn btn-play"); + + dialogBox.center(); + dialogBox.show(); + } + + public static void editGroupInformation(final String title, final String cancelButtonText, final String saveButtonText, + Group group, final AsyncCallback callback) { + final DialogBox dialogBox = new DialogBox(false, true); + dialogBox.setHTML(title); + dialogBox.addStyleName("edit-group-information-dialog"); + + final FlowPanel layout = new FlowPanel(); + + dialogBox.addStyleName("wui-dialog-prompt"); + layout.addStyleName("wui-dialog-layout"); + + final FlowPanel buttonPanel = new FlowPanel(); + final Button cancelButton = new Button(cancelButtonText); + final Button saveButton = new Button(saveButtonText); + buttonPanel.add(cancelButton); + buttonPanel.add(saveButton); + + final FlowPanel content = new FlowPanel(); + GroupDataPanel groupPanel = new GroupDataPanel(true); + groupPanel.setGroupNameReadOnly(true); + groupPanel.setGroup(group); + + content.add(groupPanel); + layout.add(content); + layout.add(buttonPanel); + dialogBox.setWidget(layout); + + dialogBox.setGlassEnabled(true); + dialogBox.setAnimationEnabled(false); + + cancelButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + dialogBox.hide(); + callback.onFailure(null); + } + }); + + saveButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + if (!groupPanel.isChanged()) { + dialogBox.hide(); + callback.onFailure(null); + } else { + if (groupPanel.isValid()) { + dialogBox.hide(); + callback.onSuccess(groupPanel.getGroup()); + } + } + } + }); + + cancelButton.addStyleName("btn btn-link"); + saveButton.addStyleName("pull-right btn btn-play"); + + dialogBox.center(); + dialogBox.show(); + } + + public static void editUserInformation(final String title, final String cancelButtonText, final String saveButtonText, + User user, final AsyncCallback callback) { + final DialogBox dialogBox = new DialogBox(false, true); + dialogBox.setHTML(title); + dialogBox.addStyleName("edit-user-information-dialog"); + + final FlowPanel layout = new FlowPanel(); + + dialogBox.addStyleName("wui-dialog-prompt"); + layout.addStyleName("wui-dialog-layout"); + + final FlowPanel buttonPanel = new FlowPanel(); + final Button cancelButton = new Button(cancelButtonText); + final Button saveButton = new Button(saveButtonText); + buttonPanel.add(cancelButton); + buttonPanel.add(saveButton); + + final FlowPanel content = new FlowPanel(); + UserDataPanel userPanel = new UserDataPanel(true, true); + userPanel.setUsernameReadOnly(true); + userPanel.setUser(user); + userPanel.setUserExtra(user.getExtra()); + + content.add(userPanel); + layout.add(content); + layout.add(buttonPanel); + dialogBox.setWidget(layout); + + dialogBox.setGlassEnabled(true); + dialogBox.setAnimationEnabled(false); + + cancelButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + dialogBox.hide(); + callback.onFailure(null); + } + }); + + saveButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + if (!userPanel.isChanged()) { + dialogBox.hide(); + callback.onFailure(null); + } else { + if (userPanel.isValid()) { + dialogBox.hide(); + UpdateUserRequest request = new UpdateUserRequest(); + request.setUser(userPanel.getUser()); + request.getUser().setDirectRoles(user.getDirectRoles()); + request.getUser().setGroups(user.getGroups()); + if (userPanel.isPasswordChanged()) { + SecureString securePassword = new SecureString(userPanel.getPassword().toCharArray()); + request.setPassword(securePassword); + } else { + request.setPassword(null); + } + request.setValues(userPanel.getUserExtra()); + callback.onSuccess(request); + } + } + } + }); + + cancelButton.addStyleName("btn btn-link"); + saveButton.addStyleName("pull-right btn btn-play"); + + dialogBox.center(); + dialogBox.show(); + } + + public static void showEditRODAMemberPermissionsPanel(final String title, final String cancelButtonText, + final String saveButtonText, Widget panel, final AsyncCallback> callback) { + final DialogBox dialogBox = new DialogBox(false, true); + dialogBox.setHTML(title); + dialogBox.addStyleName("edit-permissions-dialog"); + + if (panel instanceof PermissionsPanel) { + ((PermissionsPanel) panel).setOnDataLoadedCallback(new Runnable() { + @Override + public void run() { + dialogBox.center(); + } + }); + } + + final FlowPanel layout = new FlowPanel(); + + dialogBox.addStyleName("wui-dialog-prompt"); + layout.addStyleName("wui-dialog-layout"); + + final FlowPanel buttonPanel = new FlowPanel(); + buttonPanel.addStyleName("dialog-button-panel"); + final Button cancelButton = new Button(cancelButtonText); + final Button saveButton = new Button(saveButtonText); + buttonPanel.add(cancelButton); + buttonPanel.add(saveButton); + + final FlowPanel content = new FlowPanel(); + content.addStyleName("content"); + content.add(panel); + layout.add(content); + layout.add(buttonPanel); + dialogBox.setWidget(layout); + + dialogBox.setGlassEnabled(true); + dialogBox.setAnimationEnabled(false); + + cancelButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + dialogBox.hide(); + callback.onFailure(null); + } + }); + + saveButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + dialogBox.hide(); + callback.onSuccess(((PermissionsPanel) panel).getUserSelections()); + } + }); + + cancelButton.addStyleName("btn btn-link"); + saveButton.addStyleName("pull-right btn btn-play"); + + dialogBox.center(); + dialogBox.show(); + } + + public static void showAddGroupsToRODAMember(SafeHtml title, final String cancelButtonText, + final String confirmButtonText, Filter filter, final AsyncCallback> callback) { + + final DialogBox dialogBox = new DialogBox(false, true); + + dialogBox.addStyleName("ri-dialog add-groups-to-user-dialog"); + dialogBox.setHTML(title); + final FlowPanel layout = new FlowPanel(); + + dialogBox.addStyleName("wui-dialog-prompt"); + layout.addStyleName("wui-dialog-layout"); + + final FlowPanel buttonPanel = new FlowPanel(); + final Button cancelButton = new Button(cancelButtonText); + final Button confirmButton = new Button(confirmButtonText); + confirmButton.setEnabled(false); + buttonPanel.add(cancelButton); + buttonPanel.add(confirmButton); + + final FlowPanel content = new FlowPanel(); + content.addStyleName("row skip_padding full_width content"); + content.add(createInnerAddGroupList(dialogBox, confirmButton, filter, callback)); + layout.add(content); + layout.add(buttonPanel); + dialogBox.setWidget(layout); + + dialogBox.setGlassEnabled(true); + dialogBox.setAnimationEnabled(false); + + cancelButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + dialogBox.hide(); + callback.onFailure(null); + } + }); + + cancelButton.addStyleName("btn btn-link"); + confirmButton.addStyleName("pull-right btn btn-play"); + + dialogBox.center(); + dialogBox.show(); + } + + public static FlowPanel createInnerAddGroupList(final DialogBox dialogBox, final Button addGroupButton, + final Filter filter, final AsyncCallback> callback) { + FlowPanel container = new FlowPanel(); + container.addStyleName("wui-dialog-message"); + + // create search box and results list + + ListBuilder rodaMemberListBuilder = new ListBuilder<>(() -> new RodaMemberList(), + new AsyncTableCellOptions<>(RODAMember.class, "RepresentationInformationDialogs_RI") + .withSummary(messages.representationInformationTitle()).withInitialPageSize(10).withPageSizeIncrement(10) + .withCsvDownloadButtonVisibility(false).withRecenteringOfParentDialog(dialogBox).withForceSelectable(true) + .withFilter(filter).addCheckboxSelectionListener(new AsyncTableCell.CheckboxSelectionListener() { + @Override + public void onSelectionChange(SelectedItems selected) { + addGroupButton.setEnabled(!(selected instanceof SelectedItemsNone) + && (!(selected instanceof SelectedItemsList) || !((SelectedItemsList) selected).getIds().isEmpty())); + } + })); + + SearchWrapper searchWrapper = new SearchWrapper(false).withListsInsideScrollPanel("ri-dialog-list-scroll") + .createListAndSearchPanel(rodaMemberListBuilder); + + container.add(searchWrapper); + + addGroupButton.addClickHandler(event -> { + dialogBox.hide(); + callback.onSuccess(searchWrapper.getSelectedItems(RODAMember.class)); + }); + + return container; + } + +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/RodaMemberList.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/RodaMemberList.java index cff9cdec48..c97eac9bbb 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/RodaMemberList.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/RodaMemberList.java @@ -34,20 +34,18 @@ public class RodaMemberList extends AsyncTableCell { + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private static final List fieldsToReturn = Arrays.asList(RodaConstants.INDEX_UUID, RodaConstants.MEMBERS_ID, + RodaConstants.MEMBERS_IS_USER, RodaConstants.MEMBERS_NAME, RodaConstants.MEMBERS_FULLNAME, + RodaConstants.MEMBERS_GROUPS, RodaConstants.MEMBERS_IS_ACTIVE); @SuppressWarnings("unused") private final ClientLogger logger = new ClientLogger(getClass().getName()); - private static final ClientMessages messages = GWT.create(ClientMessages.class); - private Column activeColumn; private Column typeColumn; private TextColumn nameColumn; private TextColumn fullNameColumn; private TextColumn groupsColumn; - private static final List fieldsToReturn = Arrays.asList(RodaConstants.INDEX_UUID, RodaConstants.MEMBERS_ID, - RodaConstants.MEMBERS_IS_USER, RodaConstants.MEMBERS_NAME, RodaConstants.MEMBERS_FULLNAME, - RodaConstants.MEMBERS_GROUPS, RodaConstants.MEMBERS_IS_ACTIVE); - @Override protected void adjustOptions(AsyncTableCellOptions options) { options.withFieldsToReturn(fieldsToReturn); diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/ActionMenuCell.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/ActionMenuCell.java new file mode 100644 index 0000000000..2bd5d8361d --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/ActionMenuCell.java @@ -0,0 +1,54 @@ +package org.roda.wui.client.common.lists.utils; + +import com.google.gwt.cell.client.AbstractCell; +import com.google.gwt.cell.client.ValueUpdater; +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.safehtml.shared.SafeHtmlBuilder; +import config.i18n.client.ClientMessages; + +/** + * + * @author Miguel Guimarães + */ +public class ActionMenuCell extends AbstractCell { + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private final Delegate delegate; + + public ActionMenuCell(Delegate delegate) { + super("click"); + this.delegate = delegate; + } + + @Override + public void render(Context context, T value, SafeHtmlBuilder sb) { + // Render a button that looks like a dropdown trigger + sb.appendHtmlConstant("
"); + sb.appendHtmlConstant("
"); + sb.appendHtmlConstant( + ""); + sb.appendHtmlConstant("
"); + sb.appendHtmlConstant("
"); + } + + @Override + public void onBrowserEvent(Context context, Element parent, T value, NativeEvent event, + ValueUpdater valueUpdater) { + if ("click".equals(event.getType())) { + // Get position of the clicked element + int left = parent.getAbsoluteLeft(); + int top = parent.getAbsoluteTop() + parent.getOffsetHeight(); + + if (delegate != null) { + delegate.onShowMenu(value, left, top); + } + } + } + + public interface Delegate { + void onShowMenu(T value, int left, int top); + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/BasicTablePanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/BasicTablePanel.java index b5b667cbd8..bd183413ab 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/BasicTablePanel.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/BasicTablePanel.java @@ -148,6 +148,35 @@ public void setVisible(boolean visible) { handleScrollChanges(); } + public void removeSelectionModel() { + if (selectionModel != null) { + display.setSelectionModel(null); + } + } + + /** + * Updates the table with new rows, clearing the previous data. + * * @param newRowItems An Iterator containing the new data to display. + */ + public void updateData(Iterator newRowItems) { + if (dataProvider != null) { + // Get the list managed by the provider + List list = dataProvider.getList(); + + // Clear existing data + list.clear(); + + // Add new data + while (newRowItems.hasNext()) { + list.add(newRowItems.next()); + } + + // Note: You do not need to call refresh(). + // Modifying the list returned by dataProvider.getList() + // automatically pushes the changes to the CellTable. + } + } + public void handleScrollChanges() { if (displayScroll.getMaximumHorizontalScrollPosition() > 0) { double percent = displayScroll.getHorizontalScrollPosition() * 100F diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/SimpleActionCell.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/SimpleActionCell.java new file mode 100644 index 0000000000..4b1d114416 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/SimpleActionCell.java @@ -0,0 +1,58 @@ +package org.roda.wui.client.common.lists.utils; + +import com.google.gwt.cell.client.AbstractCell; +import com.google.gwt.cell.client.ValueUpdater; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.safehtml.shared.SafeHtmlBuilder; + +/** + * @author Miguel Guimarães + */ +public class SimpleActionCell extends AbstractCell { + + private final Delegate delegate; + private final String buttonText; + private final String cssClass; + + // Constructor allows you to pass custom text and CSS classes for the button + public SimpleActionCell(String buttonText, String cssClass, Delegate delegate) { + super("click", "keydown"); // Listen for both click and Enter key + this.buttonText = buttonText; + this.cssClass = cssClass; + this.delegate = delegate; + } + + @Override + public void render(Context context, T value, SafeHtmlBuilder sb) { + // Render the button with your custom classes + + sb.appendHtmlConstant(""); + } + + @Override + public void onBrowserEvent(Context context, Element parent, T value, NativeEvent event, + ValueUpdater valueUpdater) { + + super.onBrowserEvent(context, parent, value, event, valueUpdater); + + String eventType = event.getType(); + if ("click".equals(eventType) || ("keydown".equals(eventType) && event.getKeyCode() == 13)) { + + // Stop the event from propagating (useful if the cell is in a selectable + // DataGrid/CellTable) + event.preventDefault(); + event.stopPropagation(); + + if (delegate != null) { + delegate.execute(value); + } + } + } + + public interface Delegate { + void execute(T value); + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/panels/PermissionPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/panels/PermissionPanel.java new file mode 100644 index 0000000000..8a730df406 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/panels/PermissionPanel.java @@ -0,0 +1,80 @@ +package org.roda.wui.client.common.panels; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.safehtml.shared.SafeHtmlUtils; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.Label; +import config.i18n.client.ClientMessages; +import org.roda.core.data.v2.user.Group; +import org.roda.core.data.v2.user.RODAMember; +import org.roda.core.data.v2.user.User; + +import java.util.Set; + +/** + * @author Miguel Guimarães + */ +public class PermissionPanel extends Composite { + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private final Set list; + private final boolean isUser; + + // Private constructor forces the use of the Builder + private PermissionPanel(Builder builder) { + this.list = builder.list; + this.isUser = builder.isUser; + + FlowPanel superPanel = new FlowPanel(); + initWidget(superPanel); + + for (String str : list) { + FlowPanel panel = new FlowPanel(); + + FlowPanel panelBody = new FlowPanel(); + HTML type = new HTML( + SafeHtmlUtils.fromSafeConstant(isUser ? "" : "")); + panelBody.add(type); + type.addStyleName("permission-type"); + Label label = new Label(str); + panelBody.add(label); + panel.add(panelBody); + panelBody.addStyleName("panel-body"); + label.addStyleName("permission-name"); + + panel.addStyleName("panel permission"); + panel.addStyleName(isUser ? "permission-group" : "permission-user"); + + superPanel.add(panel); + } + } + + public Set getList() { + return list; + } + + public boolean isUser() { + return isUser; + } + + // --- BUILDER IMPLEMENTATION --- + + public static class Builder { + private final Set list; + private final boolean isUser; + + public Builder(RODAMember member) { + this.isUser = member.isUser(); + if (this.isUser) { + list = ((User) member).getGroups(); + } else { + list = ((Group) member).getUsers(); + } + } + + public PermissionPanel build() { + return new PermissionPanel(this); + } + } +} \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/panels/PermissionsPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/panels/PermissionsPanel.java new file mode 100644 index 0000000000..1348089fba --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/panels/PermissionsPanel.java @@ -0,0 +1,39 @@ +package org.roda.wui.client.common.panels; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.Widget; +import config.i18n.client.ClientMessages; +import org.roda.wui.client.common.labels.Header; + +import java.util.concurrent.Flow; + +/** + * @author Miguel Guimarães + */ + +public class PermissionsPanel extends Composite { + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); + + @UiField + FlowPanel permissionsDescription; + + @UiField + Header permissionsTitle; + + @UiField + Label permissionsEmpty; + + @UiField + FlowPanel permissionsPanel; + + + + interface MyUiBinder extends UiBinder { + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/panels/PermissionsPanel.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/panels/PermissionsPanel.ui.xml new file mode 100644 index 0000000000..43a294cca1 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/panels/PermissionsPanel.ui.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/resources/main.gss b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/resources/main.gss index 28269b2c62..2b9bf9f549 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/resources/main.gss +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/resources/main.gss @@ -234,11 +234,12 @@ table .label-success, table .label-danger, table .label-warning, table .label-in /* font-size: 75%; */ /* font-weight: 700; */ line-height: 1; - color: #fff; + color: #047857; text-align: center; white-space: nowrap; vertical-align: baseline; border-radius: .25em; + font-weight: bold; } .label-danger { @@ -248,11 +249,12 @@ table .label-success, table .label-danger, table .label-warning, table .label-in /* font-size: 75%; */ /* font-weight: 700; */ line-height: 1; - color: #fff; + color: #B91C3B; text-align: center; white-space: nowrap; vertical-align: baseline; border-radius: .25em; + font-weight: bold; } .label-warning { @@ -262,11 +264,12 @@ table .label-success, table .label-danger, table .label-warning, table .label-in /*font-size: 75%; font-weight: 700;*/ line-height: 1; - color: #fff; + color: #9b6100; text-align: center; white-space: nowrap; vertical-align: baseline; border-radius: .25em; + font-weight: bold; } .label-info { @@ -276,11 +279,12 @@ table .label-success, table .label-danger, table .label-warning, table .label-in /* font-size: 75%; */ /* font-weight: 700; */ line-height: 1; - color: #fff; + color: #036d97; text-align: center; white-space: nowrap; vertical-align: baseline; border-radius: .25em; + font-weight: bold; } .label-default { @@ -290,11 +294,12 @@ table .label-success, table .label-danger, table .label-warning, table .label-in /* font-size: 75%; */ /* font-weight: 700; */ line-height: 1; - color: #fff; + color: #666; text-align: center; white-space: nowrap; vertical-align: baseline; border-radius: .25em; + font-weight: bold; } .label-priority-urgent { @@ -1191,6 +1196,14 @@ button:after { content: "\e14b"; } +.btn-group:after { + content: "\f0c0"; +} + +.btn-user:after { + content: "\e7fe"; +} + .btn-preview:after { content: "\e8f4"; } @@ -7215,7 +7228,8 @@ td.datePickerMonth, td.datePickerYear { .ri-dialog .ri-dialog-list-scroll { width: 800px; height: 400px; - overflow-x: scroll; + overflow-x: hidden !important; /* Hides the horizontal scrollbar entirely */ + overflow-y: auto !important; /* Adds a vertical scrollbar ONLY when content exceeds 400px */ margin-top: 20px; margin-bottom: 20px; } @@ -9183,11 +9197,11 @@ wui-disposal-hold-data .form-textbox { ******************************/ .badge-panel { position: relative; - background-color: #e0e0e0; + background-color: #f5f5f5; display: inline-flex; padding: 0.7rem; line-height: 1; - color: #7f7f7f; + color: #666; text-align: center; white-space: nowrap; vertical-align: baseline; @@ -9201,6 +9215,7 @@ wui-disposal-hold-data .form-textbox { text-align: left; text-transform: uppercase; text-decoration: none; + font-weight: bold; } .badge-panel-dark { @@ -9713,3 +9728,146 @@ wui-disposal-hold-data .form-textbox { font-size: 13px; } + + +/* Container and Search */ +.permissions-dashboard { + width: 100%; +} +.permissions-search-box { + width: 100%; + padding: 8px 12px; + margin-bottom: 15px; + border: 1px solid #ccc; + border-radius: 4px; + box-sizing: border-box; +} + +/* Category Accordions */ +.permission-category { + border: 1px solid #e0e0e0; + border-radius: 4px; + margin-bottom: 10px; + background: #fff; +} + +.permission-category-header { + padding: 12px 15px; + background: #f5f5f5; + cursor: pointer; + border-bottom: 1px solid #e0e0e0; + display: flex; + justify-content: space-between; /* Pushes title left, icon right */ + align-items: center; /* Vertically centers them */ + transition: background 0.2s ease; +} + +.permission-category-header.collapsed { + border-bottom: none; +} +.permission-category-header:hover { + background: #eaeaea; +} + +/* Items container */ +.permission-category-items { + padding: 15px; + + /* Creates responsive columns. + It will fit as many 350px columns as possible. + If the screen is small, it gracefully collapses to 1 column. */ + column-width: 400px; + column-gap: 40px; /* The horizontal space between columns */ +} + +/* Select All styling */ +.select-all-wrapper { + font-weight: bold; + padding-bottom: 8px; + margin-bottom: 8px; + border-bottom: 1px dashed #e0e0e0; +} + +/* Individual permissions (Flexbox replaces HorizontalPanel) */ +.permission-category-item { + display: flex; + align-items: center; + margin-bottom: 12px; /* Adds vertical space since we removed flex gap */ + + /* CRITICAL: Prevents a checkbox and its label from + being visually ripped apart across two different columns */ + break-inside: avoid; + page-break-inside: avoid; +} + +.permission-checkbox { + margin-right: 8px; +} +.permission-description { + cursor: pointer; + user-select: none; +} +.permission-description-off { + color: #999; +} + +/* Right side wrapper containing Select All and Arrow */ +.permission-category-header-right { + display: flex; + align-items: center; + gap: 16px; /* Controls the small gap between the Select All checkbox and the arrow */ +} + +/* Header Checkbox */ +.permission-category-checkbox { + margin: 0; + cursor: pointer; + font-weight: normal; + color: #555; +} + +.permission-category-title { + font-weight: bold; + color: #333; + margin: 0; + user-select: none; +} + +/* Arrow Icon */ +.permission-category-icon { + font-size: 12px; + color: #888; + margin: 0; + user-select: none; +} + +/* Add max-height so the dialog knows when to stop growing and start scrolling */ +.edit-permissions-dialog .wui-dialog-layout, +.edit-user-information-dialog .wui-dialog-layout, +.create-user-dialog .wui-dialog-layout { + width: 80vw; + max-width: 80vw !important; + max-height: 85vh; /* ADD THIS: Keeps the dialog within the monitor height */ + + /* Use flexbox to ensure internal scrolling works correctly */ + display: flex; + flex-direction: column; +} + +/* This will now work because we added content.addStyleName("content") in Java */ +.edit-permissions-dialog .wui-dialog-layout > .content, +.edit-user-information-dialog .wui-dialog-layout > .content, +.create-user-dialog .wui-dialog-layout > .content { + flex: 1; + overflow-y: auto; + min-height: 0; + padding-bottom: 20px; +} + +/* Update this selector to target the class we added, instead of GWT's FlowPanel object name */ +.edit-permissions-dialog .wui-dialog-layout > .dialog-button-panel, +.edit-user-information-dialog .wui-dialog-layout > .dialog-button-panel, +.create-user-dialog .wui-dialog-layout > .dialog-button-panel { + flex-shrink: 0; + margin-top: 15px; +} \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/slider/DisseminationsSliderHelper.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/slider/DisseminationsSliderHelper.java deleted file mode 100644 index 4513a050c5..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/slider/DisseminationsSliderHelper.java +++ /dev/null @@ -1,251 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE file at the root of the source - * tree and available online at - * - * https://github.com/keeps/roda - */ -package org.roda.wui.client.common.slider; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.roda.core.data.common.RodaConstants; -import org.roda.core.data.v2.index.FindRequest; -import org.roda.core.data.v2.index.IsIndexed; -import org.roda.core.data.v2.index.facet.Facets; -import org.roda.core.data.v2.index.filter.Filter; -import org.roda.core.data.v2.index.filter.SimpleFilterParameter; -import org.roda.core.data.v2.index.sort.SortParameter; -import org.roda.core.data.v2.index.sort.Sorter; -import org.roda.core.data.v2.index.sublist.Sublist; -import org.roda.core.data.v2.ip.IndexedAIP; -import org.roda.core.data.v2.ip.IndexedDIP; -import org.roda.core.data.v2.ip.IndexedFile; -import org.roda.core.data.v2.ip.IndexedRepresentation; -import org.roda.wui.client.common.actions.Actionable; -import org.roda.wui.client.common.actions.DisseminationActions; -import org.roda.wui.client.common.actions.model.ActionableObject; -import org.roda.wui.client.common.actions.widgets.ActionableWidgetBuilder; -import org.roda.wui.client.common.popup.CalloutPopup; -import org.roda.wui.client.common.popup.CalloutPopup.CalloutPosition; -import org.roda.wui.client.common.utils.AsyncCallbackUtils; -import org.roda.wui.client.services.Services; -import org.roda.wui.common.client.tools.HistoryUtils; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.i18n.client.LocaleInfo; -import com.google.gwt.safehtml.shared.SafeHtmlUtils; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.FocusPanel; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.UIObject; - -import config.i18n.client.ClientMessages; - -public class DisseminationsSliderHelper { - - private static final ClientMessages messages = GWT.create(ClientMessages.class); - - private DisseminationsSliderHelper() { - // do nothing - } - - private static void updateDisseminationsSliderPanel(final IndexedAIP aip, - final SliderPanel disseminationsSliderPanel) { - Filter filter = new Filter(new SimpleFilterParameter(RodaConstants.DIP_AIP_UUIDS, aip.getUUID())); - updateDisseminations(filter, disseminationsSliderPanel); - } - - private static void updateDisseminationsSliderPanel(IndexedRepresentation representation, - final SliderPanel disseminationsSliderPanel) { - Filter filter = new Filter( - new SimpleFilterParameter(RodaConstants.DIP_REPRESENTATION_UUIDS, representation.getUUID())); - updateDisseminations(filter, disseminationsSliderPanel); - } - - private static void updateDisseminationsSliderPanel(IndexedFile file, final SliderPanel disseminationsSliderPanel) { - Filter filter = new Filter(new SimpleFilterParameter(RodaConstants.DIP_FILE_UUIDS, file.getUUID())); - updateDisseminations(filter, disseminationsSliderPanel); - } - - private static void updateDisseminationsSliderPanel(IndexedFile file, final SliderPanel disseminationsSliderPanel, - Services services) { - Filter filter = new Filter(new SimpleFilterParameter(RodaConstants.DIP_FILE_UUIDS, file.getUUID())); - updateDisseminations(filter, disseminationsSliderPanel, services); - } - - private static void updateDisseminations(Filter filter, final SliderPanel disseminationsSliderPanel, - Services services) { - Sorter sorter = new Sorter(new SortParameter(RodaConstants.DIP_DATE_CREATED, true)); - Sublist sublist = new Sublist(0, 100); - Facets facets = Facets.NONE; - String localeString = LocaleInfo.getCurrentLocale().getLocaleName(); - - List dipFields = new ArrayList<>(RodaConstants.DIP_PERMISSIONS_FIELDS_TO_RETURN); - dipFields.addAll(Arrays.asList(RodaConstants.INDEX_UUID, RodaConstants.DIP_ID, RodaConstants.DIP_TITLE, - RodaConstants.DIP_DESCRIPTION, RodaConstants.DIP_DELETE_EXTERNAL_URL, RodaConstants.DIP_OPEN_EXTERNAL_URL)); - - FindRequest findRequest = FindRequest.getBuilder(filter, true).withSorter(sorter) - .withSublist(sublist).withFacets(facets).build(); - - services.rodaEntityRestService(s -> s.find(findRequest, localeString), IndexedDIP.class) - .whenComplete((indexedDIPIndexResult, throwable) -> { - if (throwable != null) { - AsyncCallbackUtils.defaultFailureTreatment(throwable); - } else { - updateDisseminationsSliderPanel(indexedDIPIndexResult.getResults(), disseminationsSliderPanel); - } - }); - } - - private static void updateDisseminations(Filter filter, final SliderPanel disseminationsSliderPanel) { - Sorter sorter = new Sorter(new SortParameter(RodaConstants.DIP_DATE_CREATED, true)); - Sublist sublist = new Sublist(0, 100); - Facets facets = Facets.NONE; - String localeString = LocaleInfo.getCurrentLocale().getLocaleName(); - - List dipFields = new ArrayList<>(RodaConstants.DIP_PERMISSIONS_FIELDS_TO_RETURN); - dipFields.addAll(Arrays.asList(RodaConstants.INDEX_UUID, RodaConstants.DIP_ID, RodaConstants.DIP_TITLE, - RodaConstants.DIP_DESCRIPTION, RodaConstants.DIP_DELETE_EXTERNAL_URL, RodaConstants.DIP_OPEN_EXTERNAL_URL)); - - FindRequest request = FindRequest.getBuilder(filter, true).withFieldsToReturn(dipFields).withSublist(sublist).withSorter(sorter).withFacets(facets).build(); - - Services services = new Services("Find Indexed DIP", "get"); - services.dipResource(s -> s.find(request, localeString)).whenComplete((indexedDIPIndexResult, throwable) -> { - if (throwable != null) { - AsyncCallbackUtils.defaultFailureTreatment(throwable.getCause()); - } else { - updateDisseminationsSliderPanel(indexedDIPIndexResult.getResults(), disseminationsSliderPanel); - } - }); - } - - private static void updateDisseminationsSliderPanel(List dips, SliderPanel disseminationsSliderPanel) { - disseminationsSliderPanel.clear(); - disseminationsSliderPanel.addTitle(new Label(messages.viewRepresentationFileDisseminationTitle())); - - if (dips.isEmpty()) { - Label dipEmpty = new Label(messages.browseFileDipEmpty()); - disseminationsSliderPanel.addContent(dipEmpty); - dipEmpty.addStyleName("dip-empty"); - } else { - for (final IndexedDIP dip : dips) { - disseminationsSliderPanel.addContent(createDisseminationPanel(dip)); - } - } - } - - private static FlowPanel createDisseminationPanel(final IndexedDIP dip) { - FlowPanel layout = new FlowPanel(); - - // open layout - FlowPanel leftLayout = new FlowPanel(); - Label titleLabel = new Label(dip.getTitle()); - Label descriptionLabel = new Label(dip.getDescription()); - - leftLayout.add(titleLabel); - leftLayout.add(descriptionLabel); - - FocusPanel openFocus = new FocusPanel(leftLayout); - layout.add(openFocus); - - // options - HTML optionsIcon = new HTML(SafeHtmlUtils.fromSafeConstant("")); - final FocusPanel optionsButton = new FocusPanel(optionsIcon); - - optionsButton.addStyleName("lightbtn"); - optionsIcon.addStyleName("lightbtn-icon"); - optionsButton.setTitle(messages.browseFileDipDelete()); - - optionsButton.addClickHandler(new ClickHandler() { - - @Override - public void onClick(ClickEvent event) { - showActions(dip, optionsButton); - } - }); - - layout.add(optionsButton); - - titleLabel.addStyleName("dipTitle"); - descriptionLabel.addStyleName("dipDescription"); - layout.addStyleName("dip"); - leftLayout.addStyleName("dip-left"); - openFocus.addStyleName("dip-focus"); - optionsButton.addStyleName("dip-options"); - - openFocus.addClickHandler(new ClickHandler() { - - @Override - public void onClick(ClickEvent event) { - HistoryUtils.openBrowse(dip); - } - - }); - - return layout; - } - - protected static void updateDisseminationsObjectSliderPanel(final T object, - final SliderPanel disseminationsSliderPanel) { - if (object instanceof IndexedAIP) { - updateDisseminationsSliderPanel((IndexedAIP) object, disseminationsSliderPanel); - } else if (object instanceof IndexedRepresentation) { - updateDisseminationsSliderPanel((IndexedRepresentation) object, disseminationsSliderPanel); - } else if (object instanceof IndexedFile) { - updateDisseminationsSliderPanel((IndexedFile) object, disseminationsSliderPanel); - } else { - // do nothing - } - } - - protected static void updateDisseminationsObjectSliderPanel(final T object, - final SliderPanel disseminationsSliderPanel, Services services) { - if (object instanceof IndexedAIP) { - updateDisseminationsSliderPanel((IndexedAIP) object, disseminationsSliderPanel); - } else if (object instanceof IndexedRepresentation) { - updateDisseminationsSliderPanel((IndexedRepresentation) object, disseminationsSliderPanel); - } else if (object instanceof IndexedFile) { - updateDisseminationsSliderPanel((IndexedFile) object, disseminationsSliderPanel, services); - } else { - // do nothing - } - } - - protected static void showActions(final IndexedDIP dip, final UIObject actionsButton) { - final CalloutPopup actionsPopup = new CalloutPopup(); - actionsPopup.addStyleName("ActionableStyleMenu"); - - if (actionsPopup.isShowing()) { - actionsPopup.hide(); - } else { - AsyncCallback callback = new AsyncCallback() { - - @Override - public void onFailure(Throwable caught) { - AsyncCallbackUtils.defaultFailureTreatment(caught); - } - - @Override - public void onSuccess(Actionable.ActionImpact impact) { - if (!Actionable.ActionImpact.NONE.equals(impact)) { - // update - } - actionsPopup.hide(); - } - }; - - actionsPopup.setWidget(new ActionableWidgetBuilder<>(DisseminationActions.get(dip.getPermissions())) - .withActionCallback(callback).buildListWithObjects(new ActionableObject<>(dip))); - actionsPopup.showRelativeTo(actionsButton, CalloutPosition.TOP_RIGHT); - } - - } - -} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/slider/InfoSliderHelper.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/slider/InfoSliderHelper.java index 00b15d19e2..7eff192b3c 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/slider/InfoSliderHelper.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/slider/InfoSliderHelper.java @@ -7,67 +7,39 @@ */ package org.roda.wui.client.common.slider; -import java.util.AbstractMap; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.TreeSet; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import org.roda.core.data.common.RodaConstants; import org.roda.core.data.utils.RepresentationInformationUtils; -import org.roda.core.data.v2.index.IsIndexed; import org.roda.core.data.v2.ip.IndexedAIP; import org.roda.core.data.v2.ip.IndexedFile; import org.roda.core.data.v2.ip.IndexedRepresentation; -import org.roda.core.data.v2.ip.Permissions; import org.roda.core.data.v2.ip.metadata.FileFormat; import org.roda.core.data.v2.jobs.Job; -import org.roda.wui.client.browse.PreservationEvents; import org.roda.wui.client.browse.RepresentationInformationHelper; -import org.roda.wui.client.common.actions.AipActions; -import org.roda.wui.client.common.dialogs.Dialogs; import org.roda.wui.client.common.model.BrowseAIPResponse; -import org.roda.wui.client.common.model.BrowseFileResponse; import org.roda.wui.client.common.model.BrowseRepresentationResponse; import org.roda.wui.client.ingest.process.ShowJob; -import org.roda.wui.client.management.distributed.ShowDistributedInstance; -import org.roda.wui.client.planning.RiskIncidenceRegister; import org.roda.wui.client.services.Services; -import org.roda.wui.common.client.tools.ConfigurationManager; import org.roda.wui.common.client.tools.DescriptionLevelUtils; import org.roda.wui.common.client.tools.HistoryUtils; import org.roda.wui.common.client.tools.Humanize; -import org.roda.wui.common.client.tools.RestUtils; import org.roda.wui.common.client.tools.StringUtils; import org.roda.wui.common.client.widgets.Toast; -import com.google.gwt.cell.client.SafeHtmlCell; import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.Style; -import com.google.gwt.http.client.Request; -import com.google.gwt.http.client.RequestBuilder; -import com.google.gwt.http.client.RequestCallback; -import com.google.gwt.http.client.RequestException; -import com.google.gwt.http.client.Response; -import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.safehtml.shared.SafeHtmlUtils; -import com.google.gwt.safehtml.shared.SafeUri; -import com.google.gwt.user.cellview.client.CellTable; -import com.google.gwt.user.cellview.client.Column; import com.google.gwt.user.client.ui.Anchor; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.InlineHTML; -import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Widget; -import com.google.gwt.view.client.ListDataProvider; -import com.google.gwt.view.client.SingleSelectionModel; import config.i18n.client.ClientMessages; @@ -78,72 +50,8 @@ private InfoSliderHelper() { // do nothing } - protected static void updateInfoObjectSliderPanel(T object, SliderPanel slider) { - if (object instanceof IndexedAIP) { - updateInfoSliderPanel((IndexedAIP) object, slider); - } else if (object instanceof IndexedRepresentation) { - updateInfoSliderPanel((IndexedRepresentation) object, slider); - } else { - // do nothing - } - } - - private static void updateInfoSliderPanel(IndexedAIP aip, SliderPanel infoSliderPanel) { - HashMap values = new HashMap<>(); - - infoSliderPanel.clear(); - infoSliderPanel.addTitle(new Label(messages.oneOfAObject(IndexedAIP.class.getName()))); - - if (aip != null) { - if (StringUtils.isNotBlank(aip.getLevel())) { - values.put(messages.aipLevel(), - new InlineHTML(DescriptionLevelUtils.getElementLevelIconSafeHtml(aip.getLevel(), true))); - } - - if (StringUtils.isNotBlank(aip.getTitle())) { - values.put(messages.aipGenericTitle(), new InlineHTML(SafeHtmlUtils.fromString(aip.getTitle()))); - } - - if (aip.getDateInitial() != null || aip.getDateFinal() != null) { - values.put(messages.aipDates(), new InlineHTML( - SafeHtmlUtils.fromString(Humanize.getDatesText(aip.getDateInitial(), aip.getDateFinal(), true)))); - } - } - - populate(infoSliderPanel, values); - } - - private static void updateInfoSliderPanel(IndexedRepresentation representation, SliderPanel infoSliderPanel) { - HashMap values = new HashMap<>(); - - infoSliderPanel.clear(); - infoSliderPanel.addTitle(new Label(messages.oneOfAObject(IndexedRepresentation.class.getName()))); - - if (representation != null) { - if (StringUtils.isNotBlank(messages.representationType())) { - values.put(messages.representationType(), - new InlineHTML(DescriptionLevelUtils.getRepresentationTypeIcon(representation.getType(), true))); - } - - if (StringUtils.isNotBlank(messages.representationFiles())) { - values.put(messages.representationFiles(), new InlineHTML(SafeHtmlUtils.fromString( - messages.numberOfFiles(representation.getNumberOfDataFiles(), representation.getNumberOfDataFolders())))); - } - - if (representation.getNumberOfDataFiles() + representation.getNumberOfDataFolders() > 0) { - values.put(messages.representationFiles(), new InlineHTML(SafeHtmlUtils.fromString( - messages.numberOfFiles(representation.getNumberOfDataFiles(), representation.getNumberOfDataFolders())))); - } - - values.put(messages.representationOriginal(), new InlineHTML(SafeHtmlUtils.fromString( - representation.isOriginal() ? messages.originalRepresentation() : messages.alternativeRepresentation()))); - } - - populate(infoSliderPanel, values); - } - - public static HashMap getRepresentationInfoDetailsMap(BrowseRepresentationResponse response) { - HashMap values = new HashMap<>(); + public static Map getRepresentationInfoDetailsMap(BrowseRepresentationResponse response) { + Map values = new HashMap<>(); IndexedRepresentation representation = response.getIndexedRepresentation(); values.put(messages.representationId(), createIdHTML(response)); @@ -165,21 +73,8 @@ public static HashMap getRepresentationInfoDetailsMap(BrowseRepr return values; } - public static void updateInfoSliderPanel(BrowseRepresentationResponse response, SliderPanel infoSliderPanel) { - IndexedRepresentation representation = response.getIndexedRepresentation(); - - HashMap values = getRepresentationInfoDetailsMap(response); - infoSliderPanel.clear(); - infoSliderPanel.addTitle(new Label(messages.oneOfAObject(IndexedRepresentation.class.getName()))); - - addLinkIfCentralInstance(values, representation.getInstanceName(), representation.isLocalInstance(), - representation.getInstanceId()); - - populate(infoSliderPanel, values); - } - - public static HashMap getAipInfoDetailsMap(BrowseAIPResponse response) { - HashMap values = new HashMap<>(); + public static Map getAipInfoDetailsMap(BrowseAIPResponse response) { + Map values = new HashMap<>(); IndexedAIP aip = response.getIndexedAIP(); values.put(messages.itemId(), createIdHTML(response)); @@ -210,7 +105,6 @@ public static HashMap getAipInfoDetailsMap(BrowseAIPResponse res values.put(messages.sipId(), sipIds); } - if (response.getIndexedAIP().getIngestJobId() != null && !response.getIndexedAIP().getIngestJobId().isEmpty()) { FlowPanel jobIdsList = new FlowPanel(); jobIdsList.addStyleName("slider-info-entry-value-aip-ingest-jobs"); @@ -257,8 +151,7 @@ public static HashMap getAipInfoDetailsMap(BrowseAIPResponse res if (throwable != null) { Toast.showError("Error fetching AIP jobs information"); - } - else { + } else { jobIdsList.clear(); jobIdsList.add(value); } @@ -269,156 +162,8 @@ public static HashMap getAipInfoDetailsMap(BrowseAIPResponse res return values; } - public static void updateInfoSliderPanel(BrowseAIPResponse response, SliderPanel infoSliderPanel) { - IndexedAIP aip = response.getIndexedAIP(); - - HashMap values = getAipInfoDetailsMap(response); - infoSliderPanel.clear(); - infoSliderPanel.addTitle(new Label(messages.oneOfAObject(IndexedAIP.class.getName()))); - - addLinkIfCentralInstance(values, response.getIndexedAIP().getInstanceName(), - response.getIndexedAIP().isLocalInstance(), aip.getInstanceId()); - - if (!response.getIndexedAIP().getPermissions().getUsers().equals(new Permissions().getUsers()) - || !response.getIndexedAIP().getPermissions().getGroups().equals(new Permissions().getGroups())) { - values.put(messages.aipPermissionDetails(), createAipPermissionDetailsHTML(response.getIndexedAIP())); - } - populate(infoSliderPanel, values); - } - - private static Widget createAipPermissionDetailsHTML(IndexedAIP aip) { - Permissions permissions = aip.getPermissions(); - - final String CSS_HAS_PERMISSION = ""; - final String CSS_NO_PERMISSION = " slider-aip-permissions-table-icon-fade"; - - List>> entryList = new ArrayList<>(); - for (String username : new TreeSet<>(permissions.getUsernames())) { - entryList.add(new AbstractMap.SimpleEntry<>("u-" + username, permissions.getUserPermissions(username))); - } - for (String groupname : new TreeSet<>(permissions.getGroupnames())) { - entryList.add(new AbstractMap.SimpleEntry<>("g-" + groupname, permissions.getGroupPermissions(groupname))); - } - - CellTable>> table = new CellTable<>(); - table.addStyleName("slider-aip-permissions-table"); - - Column>, SafeHtml> userGroupIconColumn = new Column>, SafeHtml>( - new SafeHtmlCell()) { - @Override - public SafeHtml getValue(Entry> object) { - if (object.getKey().startsWith("u-")) { - return SafeHtmlUtils.fromSafeConstant(""); - } else { - return SafeHtmlUtils.fromSafeConstant(""); - } - } - }; - - Column>, SafeHtml> nameColumn = new Column>, SafeHtml>( - new SafeHtmlCell()) { - @Override - public SafeHtml getValue(Entry> object) { - String name = object.getKey().substring(2); - return SafeHtmlUtils.fromSafeConstant( - "" + SafeHtmlUtils.htmlEscape(name) + ""); - } - }; - - Column>, SafeHtml> iconReadColumn = new Column>, SafeHtml>( - new SafeHtmlCell()) { - @Override - public SafeHtml getValue(Entry> object) { - String extraIconCss = object.getValue().contains(Permissions.PermissionType.READ) ? CSS_HAS_PERMISSION - : CSS_NO_PERMISSION; - return SafeHtmlUtils - .fromSafeConstant(""); - } - }; - - Column>, SafeHtml> iconCreateColumn = new Column>, SafeHtml>( - new SafeHtmlCell()) { - @Override - public SafeHtml getValue(Entry> object) { - String extraIconCss = object.getValue().contains(Permissions.PermissionType.CREATE) ? CSS_HAS_PERMISSION - : CSS_NO_PERMISSION; - return SafeHtmlUtils - .fromSafeConstant(""); - } - }; - - Column>, SafeHtml> iconEditColumn = new Column>, SafeHtml>( - new SafeHtmlCell()) { - @Override - public SafeHtml getValue(Entry> object) { - String extraIconCss = object.getValue().contains(Permissions.PermissionType.UPDATE) ? CSS_HAS_PERMISSION - : CSS_NO_PERMISSION; - return SafeHtmlUtils - .fromSafeConstant(""); - } - }; - - Column>, SafeHtml> iconDeleteColumn = new Column>, SafeHtml>( - new SafeHtmlCell()) { - @Override - public SafeHtml getValue(Entry> object) { - String extraIconCss = object.getValue().contains(Permissions.PermissionType.DELETE) ? CSS_HAS_PERMISSION - : CSS_NO_PERMISSION; - return SafeHtmlUtils - .fromSafeConstant(""); - } - }; - - Column>, SafeHtml> iconGrantColumn = new Column>, SafeHtml>( - new SafeHtmlCell()) { - @Override - public SafeHtml getValue(Entry> object) { - String extraIconCss = object.getValue().contains(Permissions.PermissionType.GRANT) ? CSS_HAS_PERMISSION - : CSS_NO_PERMISSION; - return SafeHtmlUtils - .fromSafeConstant(""); - } - }; - - table.addColumn(userGroupIconColumn); - table.addColumn(nameColumn); - table.addColumn(iconReadColumn); - table.addColumn(iconCreateColumn); - table.addColumn(iconEditColumn); - table.addColumn(iconDeleteColumn); - table.addColumn(iconGrantColumn); - - table.setColumnWidth(userGroupIconColumn, 23, Style.Unit.PX); - table.setColumnWidth(iconReadColumn, 23, Style.Unit.PX); - table.setColumnWidth(iconCreateColumn, 23, Style.Unit.PX); - table.setColumnWidth(iconEditColumn, 23, Style.Unit.PX); - table.setColumnWidth(iconDeleteColumn, 23, Style.Unit.PX); - table.setColumnWidth(iconGrantColumn, 23, Style.Unit.PX); - - nameColumn.setCellStyleNames("nowrap slider-aip-permissions-table-name"); - - AipActions aipActions = AipActions.get(); - if (aipActions.canAct(AipActions.AipAction.UPDATE_PERMISSIONS, aip).canAct()) { - table.addStyleName("slider-aip-permissions-table-with-grant"); - SingleSelectionModel>> selectionModel = new SingleSelectionModel<>( - item -> item.getKey().substring(2)); - selectionModel.addSelectionChangeHandler(event -> aipActions.act(AipActions.AipAction.UPDATE_PERMISSIONS, aip)); - table.setSelectionModel(selectionModel); - } - - ListDataProvider>> dataProvider = new ListDataProvider<>(entryList); - dataProvider.addDataDisplay(table); - - return table; - } - - public static HashMap getFileInfoDetailsMap(IndexedFile file, List riRules) { - HashMap values = new HashMap<>(); + public static Map getFileInfoDetailsMap(IndexedFile file, List riRules) { + Map values = new HashMap<>(); if (file != null) { String fileName = file.getOriginalName() != null ? file.getOriginalName() : file.getId(); @@ -501,93 +246,6 @@ public static HashMap getFileInfoDetailsMap(IndexedFile file, Li return values; } - public static void createFileInfoSliderPanel(IndexedFile file, BrowseFileResponse response, - SliderPanel infoSliderPanel) { - HashMap values = getFileInfoDetailsMap(file, response.getRepresentationInformationFields()); - infoSliderPanel.clear(); - infoSliderPanel.addTitle(new Label(messages.oneOfAObject(IndexedFile.class.getName()))); - - Long risksCounter = response.getRiskCounterResponse().getResult(); - Long preservationEventsCounter = response.getPreservationCounterResponse().getResult(); - - if (file != null) { - addLinkIfCentralInstance(values, file.getInstanceName(), file.isLocalInstance(), file.getInstanceId()); - - List history = new ArrayList<>(); - history.add(file.getAipId()); - history.add(file.getRepresentationId()); - history.addAll(file.getPath()); - history.add(file.getId()); - - if (risksCounter >= 0) { - Anchor risksLink = new Anchor(messages.aipRiskIncidences(risksCounter), - HistoryUtils.createHistoryHashLink(RiskIncidenceRegister.RESOLVER, history)); - values.put(messages.preservationRisks(), risksLink); - } - - if (preservationEventsCounter >= 0) { - Anchor eventsLink = new Anchor(messages.aipEvents(preservationEventsCounter), - HistoryUtils.createHistoryHashLink(PreservationEvents.BROWSE_RESOLVER, file.getAipId(), - file.getRepresentationUUID(), file.getUUID())); - values.put(messages.preservationEvents(), eventsLink); - } - - SafeUri uri = RestUtils.createTechnicalMetadataHTMLUri(file.getUUID()); - RequestBuilder requestBuilder = new RequestBuilder(RequestBuilder.GET, uri.asString()); - - Anchor technicalInformationAnchor = new Anchor(); - technicalInformationAnchor.setStyleName("clickable"); - technicalInformationAnchor.setText(messages.showTechnicalMetadata()); - - // technicalInformation - try { - requestBuilder.sendRequest(null, new RequestCallback() { - @Override - public void onResponseReceived(Request request, Response response) { - if (response.getStatusCode() == 200) { - if (!response.getText().isEmpty()) { - values.put(messages.viewTechnicalInformation(), technicalInformationAnchor); - technicalInformationAnchor - .addClickHandler(e -> Dialogs.showTechnicalMetadataInformation(messages.viewTechnicalMetadata(), - messages.downloadButton(), messages.closeButton(), file, response.getText())); - } - } else { - values.put(messages.viewTechnicalInformation(), technicalInformationAnchor); - technicalInformationAnchor - .addClickHandler(e -> Dialogs.showTechnicalMetadataInformation(messages.viewTechnicalMetadata(), - messages.downloadButton(), messages.closeButton(), file, null)); - } - populate(infoSliderPanel, values); - } - - @Override - public void onError(Request request, Throwable throwable) { - populate(infoSliderPanel, values); - } - }); - } catch (RequestException e) { - throw new RuntimeException(e); - } - } - } - - private static void populate(SliderPanel infoSliderPanel, HashMap values) { - for (Entry entry : values.entrySet()) { - FlowPanel entryPanel = new FlowPanel(); - - Label keyLabel = new Label(entry.getKey()); - Widget valueLabel = entry.getValue(); - - entryPanel.add(keyLabel); - entryPanel.add(valueLabel); - infoSliderPanel.addContent(entryPanel); - - keyLabel.addStyleName("slider-info-entry-key"); - valueLabel.addStyleName("slider-info-entry-value"); - entryPanel.addStyleName("slider-info-entry"); - } - } - private static FlowPanel createExtensionHTML(List representationInformationFields, String extension) { FlowPanel panel = new FlowPanel(); final String riFilter = RepresentationInformationUtils @@ -722,28 +380,4 @@ private static FlowPanel createRepresentationTypeHTML(BrowseRepresentationRespon return panel; } - - public static void addLinkIfCentralInstance(Map values, String instanceName, boolean localToInstance, - String instanceId) { - if (StringUtils.isNotBlank(instanceId)) { - String distributedMode = ConfigurationManager.getStringWithDefault( - RodaConstants.DEFAULT_DISTRIBUTED_MODE_TYPE.name(), RodaConstants.DISTRIBUTED_MODE_TYPE_PROPERTY); - if (RodaConstants.DistributedModeType.CENTRAL.name().equals(distributedMode)) { - if (localToInstance) { - values.put(messages.distributedInstanceLabel(), new Label(instanceName)); - } else { - Anchor anchor = new Anchor(); - if (StringUtils.isNotBlank(instanceName)) { - anchor.setText(instanceName); - } else { - anchor.setText(instanceId); - } - anchor.setHref(HistoryUtils.createHistoryHashLink(ShowDistributedInstance.RESOLVER, instanceId)); - values.put(messages.distributedInstanceLabel(), anchor); - } - } else { - values.put(messages.itemInstanceId(), new Label(instanceId)); - } - } - } } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/slider/OptionsSliderHelper.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/slider/OptionsSliderHelper.java deleted file mode 100644 index a49ef14887..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/slider/OptionsSliderHelper.java +++ /dev/null @@ -1,76 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE file at the root of the source - * tree and available online at - * - * https://github.com/keeps/roda - */ -package org.roda.wui.client.common.slider; - -import org.roda.core.data.v2.index.IsIndexed; -import org.roda.core.data.v2.ip.DIPFile; -import org.roda.core.data.v2.ip.IndexedAIP; -import org.roda.core.data.v2.ip.IndexedDIP; -import org.roda.core.data.v2.ip.IndexedFile; -import org.roda.core.data.v2.ip.IndexedRepresentation; -import org.roda.wui.client.common.actions.AipActions; -import org.roda.wui.client.common.actions.DisseminationActions; -import org.roda.wui.client.common.actions.DisseminationFileActions; -import org.roda.wui.client.common.actions.FileActions; -import org.roda.wui.client.common.actions.RepresentationActions; -import org.roda.wui.client.common.actions.model.ActionableObject; -import org.roda.wui.client.common.actions.widgets.ActionableWidgetBuilder; - -public class OptionsSliderHelper { - - private OptionsSliderHelper() { - // do nothing - } - - static void updateOptionsObjectSliderPanel(T object, SliderPanel slider) { - if (object instanceof IndexedFile) { - updateOptionsSliderPanel((IndexedFile) object, slider); - } else if (object instanceof IndexedRepresentation) { - updateOptionsSliderPanel((IndexedRepresentation) object, slider); - } else if (object instanceof IndexedAIP) { - updateOptionsSliderPanel((IndexedAIP) object, slider); - } else if (object instanceof IndexedDIP) { - updateOptionsSliderPanel((IndexedDIP) object, slider); - } else if (object instanceof DIPFile) { - updateOptionsSliderPanel((DIPFile) object, slider); - } else { - // do nothing - } - } - - private static void updateOptionsSliderPanel(IndexedAIP aip, SliderPanel slider) { - slider.clear(); - slider - .addContent(new ActionableWidgetBuilder<>(AipActions.get()).buildListWithObjects(new ActionableObject<>(aip))); - } - - private static void updateOptionsSliderPanel(IndexedRepresentation representation, SliderPanel slider) { - slider.clear(); - slider.addContent(new ActionableWidgetBuilder<>(RepresentationActions.get()) - .buildListWithObjects(new ActionableObject<>(representation))); - } - - private static void updateOptionsSliderPanel(final IndexedFile file, final SliderPanel slider) { - slider.clear(); - slider.addContent( - new ActionableWidgetBuilder<>(FileActions.get()).buildListWithObjects(new ActionableObject<>((file)))); - } - - private static void updateOptionsSliderPanel(final IndexedDIP dip, final SliderPanel slider) { - slider.clear(); - slider.addContent(new ActionableWidgetBuilder<>(DisseminationActions.get(dip.getPermissions())) - .buildListWithObjects(new ActionableObject<>((dip)))); - } - - private static void updateOptionsSliderPanel(final DIPFile file, final SliderPanel slider) { - slider.clear(); - slider.addContent(new ActionableWidgetBuilder<>(DisseminationFileActions.get()) - .buildListWithObjects(new ActionableObject<>((file)))); - } - -} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/slider/SliderPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/slider/SliderPanel.java deleted file mode 100644 index f959285583..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/slider/SliderPanel.java +++ /dev/null @@ -1,161 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE file at the root of the source - * tree and available online at - * - * https://github.com/keeps/roda - */ -package org.roda.wui.client.common.slider; - -import org.roda.wui.client.common.utils.JavascriptUtils; - -import com.google.gwt.dom.client.Document; -import com.google.gwt.event.dom.client.ChangeEvent; -import com.google.gwt.event.dom.client.ChangeHandler; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.event.dom.client.HasChangeHandlers; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.uibinder.client.UiChild; -import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.FocusPanel; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.ScrollPanel; -import com.google.gwt.user.client.ui.SimplePanel; -import com.google.gwt.user.client.ui.Widget; - -public class SliderPanel extends Composite implements HasChangeHandlers { - - private static final String CSS_CLASS_ACTIVE = "active"; - private static SliderPanel OPEN_SLIDER = null; - - private final SimplePanel wrapper; - private final ScrollPanel layout; - private FlowPanel contentLayout; - - private FocusPanel toggleButton = null; - private HandlerRegistration toggleButtonHandlerRegistration = null; - private boolean disposed = false; - - public SliderPanel() { - super(); - this.contentLayout = new FlowPanel(); - this.layout = new ScrollPanel(); - this.layout.add(contentLayout); - - this.wrapper = new SimplePanel(this.layout); - initWidget(wrapper); - - layout.addStyleName("slider-layout"); - this.addStyleName("slider"); - } - - /** - * Destroys this slider, making it unusable. Trying to use the slider after this - * will cause errors. - */ - public void dispose() { - if (!disposed) { - closeAll(); - if (toggleButtonHandlerRegistration != null) { - toggleButtonHandlerRegistration.removeHandler(); - } - clear(); - getElement().removeFromParent(); - disposed = true; - } - } - - public void setToggleButton(FocusPanel toggleButton) { - this.toggleButton = toggleButton; - - // bind toggle button events - toggleButtonHandlerRegistration = toggleButton.addClickHandler(new ClickHandler() { - - @Override - public void onClick(ClickEvent event) { - if (isOpen(SliderPanel.this)) { - closeAll(); - } else { - open(); - } - } - }); - } - - public static void closeAll() { - if (OPEN_SLIDER != null) { - // remove CSS classes - OPEN_SLIDER.setActive(false); - - // hide animation - JavascriptUtils.toggle(OPEN_SLIDER.getElement()); - - // update state - OPEN_SLIDER = null; - } - } - - private static boolean isOpen(SliderPanel slider) { - return slider == OPEN_SLIDER; - } - - public static void open(SliderPanel slider) { - closeAll(); - - // update state - OPEN_SLIDER = slider; - - // Show animation - JavascriptUtils.toggle(slider.getElement()); - - // Add CSS classes - OPEN_SLIDER.setActive(true); - } - - @UiChild(tagname = "title") - public void addTitle(Label title) { - contentLayout.add(title); - title.addStyleName("slider-title"); - } - - @UiChild(tagname = "content") - public void addContent(Widget widget) { - contentLayout.add(widget); - } - - public void clear() { - contentLayout.clear(); - } - - private void setActive(boolean active) { - if (active) { - if (toggleButton != null) { - toggleButton.addStyleName(CSS_CLASS_ACTIVE); - } - this.addStyleDependentName(CSS_CLASS_ACTIVE); - } else { - if (toggleButton != null) { - toggleButton.removeStyleName(CSS_CLASS_ACTIVE); - } - this.removeStyleDependentName(CSS_CLASS_ACTIVE); - } - - ChangeEvent.fireNativeEvent(Document.get().createChangeEvent(), this); - } - - public void open() { - open(this); - } - - public boolean isOpen() { - return isOpen(this); - } - - @Override - public HandlerRegistration addChangeHandler(ChangeHandler handler) { - return addHandler(handler, ChangeEvent.getType()); - } - -} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/slider/Sliders.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/slider/Sliders.java deleted file mode 100644 index 6b62ba92ad..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/slider/Sliders.java +++ /dev/null @@ -1,62 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE file at the root of the source - * tree and available online at - * - * https://github.com/keeps/roda - */ -package org.roda.wui.client.common.slider; - -import org.roda.core.data.v2.index.IsIndexed; -import org.roda.core.data.v2.ip.IndexedFile; -import org.roda.wui.client.common.model.BrowseAIPResponse; -import org.roda.wui.client.common.model.BrowseFileResponse; -import org.roda.wui.client.common.model.BrowseRepresentationResponse; -import org.roda.wui.client.services.Services; - -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.FocusPanel; - -public class Sliders { - - private Sliders() { - - } - - public static SliderPanel createSlider(FlowPanel container, FocusPanel toggleButton) { - SliderPanel slider = new SliderPanel(); - container.add(slider); - container.addStyleName("slider-container"); - slider.setToggleButton(toggleButton); - return slider; - } - - public static SliderPanel createDisseminationSlider(FlowPanel container, - FocusPanel toggleButton, T object, Services services) { - SliderPanel slider = createSlider(container, toggleButton); - DisseminationsSliderHelper.updateDisseminationsObjectSliderPanel(object, slider, services); - return slider; - } - - public static SliderPanel createAipInfoSlider(FlowPanel container, FocusPanel toggleButton, - BrowseAIPResponse response) { - SliderPanel slider = createSlider(container, toggleButton); - InfoSliderHelper.updateInfoSliderPanel(response, slider); - return slider; - } - - public static SliderPanel createFileInfoSlider(FlowPanel container, FocusPanel toggleButton, IndexedFile file, - BrowseFileResponse response) { - SliderPanel slider = createSlider(container, toggleButton); - InfoSliderHelper.createFileInfoSliderPanel(file, response, slider); - return slider; - } - - public static SliderPanel createRepresentationInfoSlider(FlowPanel container, FocusPanel toggleButton, - BrowseRepresentationResponse response) { - SliderPanel slider = createSlider(container, toggleButton); - InfoSliderHelper.updateInfoSliderPanel(response, slider); - return slider; - } - -} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/utils/FormUtilities.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/utils/FormUtilities.java index faa9d9fe02..5266fa556f 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/utils/FormUtilities.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/utils/FormUtilities.java @@ -13,6 +13,7 @@ import java.util.Set; import java.util.concurrent.Callable; +import com.google.gwt.user.client.ui.InlineHTML; import org.roda.core.data.v2.generics.MetadataValue; import org.roda.wui.client.common.RichTextToolbar; @@ -41,6 +42,7 @@ import com.google.gwt.user.datepicker.client.DateBox; import config.i18n.client.ClientMessages; +import org.roda.wui.common.client.tools.StringUtils; /** * Created by adrapereira on 13-06-2016. @@ -569,4 +571,40 @@ public static void callOnChange(final Callable onChange) { // do nothing } } + + public static FlowPanel buildField(String label, InlineHTML html) { + FlowPanel topPanel = new FlowPanel(); + topPanel.addStyleName("descriptiveMetadata"); + FlowPanel fieldPanel = new FlowPanel(); + fieldPanel.setStyleName("field"); + + Label fieldLabel = new Label(label); + fieldLabel.setStyleName("label"); + + FlowPanel fieldValuePanel = new FlowPanel(); + fieldValuePanel.setStyleName("value"); + fieldValuePanel.add(html); + + fieldPanel.add(fieldLabel); + fieldPanel.add(fieldValuePanel); + + topPanel.add(fieldPanel); + return topPanel; + } + + public static void addIfNotBlank(FlowPanel panel, String label, String value) { + if (StringUtils.isNotBlank(value)) { + panel.add(buildField(label, new InlineHTML(SafeHtmlUtils.htmlEscape(value)))); + } + } + + public static FlowPanel buildSeparator(String text) { + FlowPanel separator = new FlowPanel(); + separator.addStyleName("form-separator"); + Label separatorLabel = new Label(messages.groups()); + separatorLabel.addStyleName("separator-label"); + separator.add(separatorLabel); + + return separator; + } } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java index 37d160281f..d62435b148 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java @@ -19,11 +19,14 @@ import org.roda.core.data.v2.ip.IndexedFile; import org.roda.core.data.v2.ip.IndexedRepresentation; import org.roda.core.data.v2.ip.TransferredResource; +import org.roda.core.data.v2.user.RODAMember; import org.roda.wui.client.browse.BrowseTop; import org.roda.wui.client.browse.PreservationEvents; import org.roda.wui.client.disposal.DisposalDestroyedRecords; import org.roda.wui.client.ingest.appraisal.IngestAppraisal; import org.roda.wui.client.ingest.transfer.IngestTransfer; +import org.roda.wui.client.management.members.MemberManagement; +import org.roda.wui.client.management.members.ShowMember; import org.roda.wui.common.client.tools.DescriptionLevelUtils; import org.roda.wui.common.client.tools.HistoryUtils; import org.roda.wui.common.client.tools.ListUtils; @@ -436,6 +439,21 @@ private static SafeHtml getBreadcrumbLabel(IndexedAIP aip) { return breadcrumbLabel; } + public static List getRODAMemberBreadcrumbs(RODAMember user) { + List ret = new ArrayList<>(); + ret.add(new BreadcrumbItem(SafeHtmlUtils.fromSafeConstant(messages.usersAndGroupsTitle()), messages.usersAndGroupsTitle(), + MemberManagement.RESOLVER.getHistoryPath())); + + if (user != null) { + List path = new ArrayList<>(ShowMember.RESOLVER.getHistoryPath()); + path.add(user.getUUID()); + String label = user.getId(); + ret.add(new BreadcrumbItem(SafeHtmlUtils.fromString(label), label, path)); + } + + return ret; + } + private static String getBreadcrumbTitle(IndexedAIP aip) { String title; if (aip.getGhost()) { diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/ContentPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/ContentPanel.java index ec213cfc30..8276d9a553 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/ContentPanel.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/ContentPanel.java @@ -26,11 +26,11 @@ import org.roda.wui.client.ingest.Ingest; import org.roda.wui.client.management.AcknowledgeNotification; import org.roda.wui.client.management.Management; -import org.roda.wui.client.management.Profile; -import org.roda.wui.client.management.RecoverLogin; -import org.roda.wui.client.management.Register; -import org.roda.wui.client.management.ResetPassword; -import org.roda.wui.client.management.SetPassword; +import org.roda.wui.client.management.members.Profile; +import org.roda.wui.client.management.members.RecoverLogin; +import org.roda.wui.client.management.members.Register; +import org.roda.wui.client.management.members.ResetPassword; +import org.roda.wui.client.management.members.SetPassword; import org.roda.wui.client.management.VerifyEmail; import org.roda.wui.client.planning.Planning; import org.roda.wui.client.process.Process; @@ -54,13 +54,10 @@ */ public class ContentPanel extends SimplePanel { - private static ContentPanel instance = null; - - private static ClientLogger logger = new ClientLogger(ContentPanel.class.getName()); - private static final Set resolvers = new HashSet<>(); private static final ClientMessages messages = GWT.create(ClientMessages.class); - + private static ContentPanel instance = null; + private static ClientLogger logger = new ClientLogger(ContentPanel.class.getName()); private Widget currWidget; private List lastHistoryTokens = null; private HistoryResolver lastResolver = null; diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/ExpiredSessionDetector.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/ExpiredSessionDetector.java index 3559fac751..2d8712b1cb 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/ExpiredSessionDetector.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/ExpiredSessionDetector.java @@ -11,8 +11,8 @@ import org.roda.core.data.v2.user.User; import org.roda.wui.client.common.UserLogin; import com.google.gwt.user.client.Timer; -import org.roda.wui.client.management.RecoverLogin; -import org.roda.wui.client.management.Register; +import org.roda.wui.client.management.members.RecoverLogin; +import org.roda.wui.client.management.members.Register; import org.roda.wui.client.welcome.Welcome; import org.roda.wui.common.client.tools.HistoryUtils; diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/Header.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/Header.java index eaafd0602b..f00c72c604 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/Header.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/Header.java @@ -29,7 +29,7 @@ import org.roda.wui.client.ingest.preingest.PreIngest; import org.roda.wui.client.ingest.transfer.IngestTransfer; import org.roda.wui.client.management.Management; -import org.roda.wui.client.management.MemberManagement; +import org.roda.wui.client.management.members.MemberManagement; import org.roda.wui.client.management.NotificationRegister; import org.roda.wui.client.management.Statistics; import org.roda.wui.client.management.UserLog; diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/Login.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/Login.java index 309028ea53..12002c7cf0 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/Login.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/Login.java @@ -24,8 +24,8 @@ import org.roda.wui.client.common.UserLogin; import org.roda.wui.client.common.dialogs.Dialogs; import org.roda.wui.client.common.utils.JavascriptUtils; -import org.roda.wui.client.management.RecoverLogin; -import org.roda.wui.client.management.Register; +import org.roda.wui.client.management.members.RecoverLogin; +import org.roda.wui.client.management.members.Register; import org.roda.wui.client.services.Services; import org.roda.wui.client.welcome.Welcome; import org.roda.wui.common.client.ClientLogger; diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/UserMenu.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/UserMenu.java index 1a15fd8a12..b9f7111be2 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/UserMenu.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/UserMenu.java @@ -19,8 +19,8 @@ import org.roda.core.data.v2.user.User; import org.roda.wui.client.common.UserLogin; import org.roda.wui.client.common.utils.JavascriptUtils; -import org.roda.wui.client.management.Profile; -import org.roda.wui.client.management.Register; +import org.roda.wui.client.management.members.Profile; +import org.roda.wui.client.management.members.Register; import org.roda.wui.common.client.ClientLogger; import org.roda.wui.common.client.tools.HistoryUtils; import org.roda.wui.common.client.widgets.wcag.AcessibleMenuBar; diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/CreateGroup.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/CreateGroup.java deleted file mode 100644 index ec1a5052fc..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/CreateGroup.java +++ /dev/null @@ -1,133 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE file at the root of the source - * tree and available online at - * - * https://github.com/keeps/roda - */ -/** - * - */ -package org.roda.wui.client.management; - -import java.util.List; - -import org.roda.core.data.exceptions.AlreadyExistsException; -import org.roda.core.data.v2.user.Group; -import org.roda.core.data.v2.user.requests.CreateGroupRequest; -import org.roda.wui.client.common.UserLogin; -import org.roda.wui.client.services.Services; -import org.roda.wui.common.client.HistoryResolver; -import org.roda.wui.common.client.tools.HistoryUtils; -import org.roda.wui.common.client.tools.ListUtils; -import org.roda.wui.common.client.widgets.Toast; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.uibinder.client.UiBinder; -import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.uibinder.client.UiHandler; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.Button; -import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.Widget; - -import config.i18n.client.ClientMessages; - -/** - * @author Luis Faria - * - */ -public class CreateGroup extends Composite { - - public static final HistoryResolver RESOLVER = new HistoryResolver() { - - @Override - public void resolve(List historyTokens, final AsyncCallback callback) { - CreateGroup createGroup = new CreateGroup(new Group()); - callback.onSuccess(createGroup); - } - - @Override - public void isCurrentUserPermitted(AsyncCallback callback) { - UserLogin.getInstance().checkRoles(new HistoryResolver[] {MemberManagement.RESOLVER}, false, callback); - } - - @Override - public List getHistoryPath() { - return ListUtils.concat(MemberManagement.RESOLVER.getHistoryPath(), getHistoryToken()); - } - - @Override - public String getHistoryToken() { - return "create_group"; - } - }; - - interface MyUiBinder extends UiBinder { - } - - private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); - - private Group group; - - private static final ClientMessages messages = GWT.create(ClientMessages.class); - - @UiField - Button buttonApply; - - @UiField - Button buttonCancel; - - @UiField(provided = true) - GroupDataPanel groupDataPanel; - - /** - * Create a new panel to create a group - * - * @param group - * the group to create - */ - public CreateGroup(Group group) { - this.group = group; - - this.groupDataPanel = new GroupDataPanel(true, false); - this.groupDataPanel.setGroup(group); - - initWidget(uiBinder.createAndBindUi(this)); - } - - @UiHandler("buttonApply") - void buttonApplyHandler(ClickEvent e) { - if (groupDataPanel.isValid()) { - group = groupDataPanel.getGroup(); - Services services = new Services("Create group", "create"); - CreateGroupRequest createGroupRequest = new CreateGroupRequest(group.getName(), group.getFullName(), - group.getDirectRoles()); - services.membersResource(s -> s.createGroup(createGroupRequest)).whenComplete((newGroup, error) -> { - if (newGroup != null) { - HistoryUtils.newHistory(MemberManagement.RESOLVER); - } else if (error != null) { - errorMessage(error); - } - }); - } - } - - @UiHandler("buttonCancel") - void buttonCancelHandler(ClickEvent e) { - cancel(); - } - - private void cancel() { - HistoryUtils.newHistory(MemberManagement.RESOLVER); - } - - private void errorMessage(Throwable caught) { - if (caught instanceof AlreadyExistsException) { - Toast.showError(messages.createGroupAlreadyExists(group.getName())); - } else { - Toast.showError(messages.createGroupFailure(caught.getMessage())); - } - } -} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/CreateGroup.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/CreateGroup.ui.xml deleted file mode 100644 index 0c21d1d09f..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/CreateGroup.ui.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/CreateUser.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/CreateUser.java deleted file mode 100644 index 4062f2232b..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/CreateUser.java +++ /dev/null @@ -1,138 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE file at the root of the source - * tree and available online at - * - * https://github.com/keeps/roda - */ -/** - * - */ -package org.roda.wui.client.management; - -import java.util.List; - -import org.roda.core.data.exceptions.EmailAlreadyExistsException; -import org.roda.core.data.exceptions.UserAlreadyExistsException; -import org.roda.core.data.v2.user.User; -import org.roda.core.data.v2.user.requests.CreateUserRequest; -import org.roda.wui.client.common.UserLogin; -import org.roda.wui.client.services.Services; -import org.roda.wui.common.client.HistoryResolver; -import org.roda.wui.common.client.tools.HistoryUtils; -import org.roda.wui.common.client.tools.ListUtils; -import org.roda.wui.common.client.widgets.Toast; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.i18n.client.LocaleInfo; -import com.google.gwt.uibinder.client.UiBinder; -import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.uibinder.client.UiHandler; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.Button; -import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.Widget; - -import config.i18n.client.ClientMessages; - -/** - * @author Luis Faria - * - */ -public class CreateUser extends Composite { - - public static final HistoryResolver RESOLVER = new HistoryResolver() { - - @Override - public void resolve(List historyTokens, final AsyncCallback callback) { - CreateUser createUser = new CreateUser(new User()); - callback.onSuccess(createUser); - } - - @Override - public void isCurrentUserPermitted(AsyncCallback callback) { - UserLogin.getInstance().checkRoles(new HistoryResolver[] {MemberManagement.RESOLVER}, false, callback); - } - - @Override - public List getHistoryPath() { - return ListUtils.concat(MemberManagement.RESOLVER.getHistoryPath(), getHistoryToken()); - } - - @Override - public String getHistoryToken() { - return "create_user"; - } - }; - - interface MyUiBinder extends UiBinder { - } - - private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); - - private User user; - - private static final ClientMessages messages = GWT.create(ClientMessages.class); - - @UiField - Button buttonApply; - - @UiField - Button buttonCancel; - - @UiField(provided = true) - CreateUserPanel userDataPanel; - - /** - * Create a new panel to create a user - * - * @param user - * the user to create - */ - public CreateUser(User user) { - this.user = user; - - this.userDataPanel = new CreateUserPanel(true, false, true); - this.userDataPanel.setUser(user); - initWidget(uiBinder.createAndBindUi(this)); - } - - @UiHandler("buttonApply") - void buttonApplyHandler(ClickEvent e) { - if (userDataPanel.isValid()) { - user = userDataPanel.getUser(); - Services services = new Services("Create RODA user", "create"); - CreateUserRequest userOperations = new CreateUserRequest(user.getEmail(), user.getName(), user.getFullName(), - user.getDirectRoles(), user.getGroups(), user.isGuest(), null, userDataPanel.getUserExtra()); - services.membersResource(s -> s.createUser(userOperations, LocaleInfo.getCurrentLocale().getLocaleName())) - .whenComplete((createdUser, error) -> { - if (createdUser != null) { - HistoryUtils.newHistory(MemberManagement.RESOLVER); - } else if (error != null) { - errorMessage(error); - } - }); - } - } - - @UiHandler("buttonCancel") - void buttonCancelHandler(ClickEvent e) { - cancel(); - } - - private void cancel() { - HistoryUtils.newHistory(MemberManagement.RESOLVER); - } - - private void errorMessage(Throwable caught) { - if (caught instanceof EmailAlreadyExistsException) { - Toast.showError(messages.createUserEmailAlreadyExists(user.getEmail())); - } else if (caught instanceof UserAlreadyExistsException) { - Toast.showError(messages.createUserAlreadyExists(user.getId())); - } else { - Toast.showError(messages.createUserFailure(caught.getMessage())); - } - } - -} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/CreateUser.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/CreateUser.ui.xml deleted file mode 100644 index 7d1073363e..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/CreateUser.ui.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/CreateUserPanel.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/CreateUserPanel.ui.xml deleted file mode 100644 index 5afd8d520c..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/CreateUserPanel.ui.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - .section { - margin-top: 20px; - } - - - - - - - - - - - * - - - - - - * - - - - - - * - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/EditGroup.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/EditGroup.java deleted file mode 100644 index 0ab478ebb7..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/EditGroup.java +++ /dev/null @@ -1,161 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE file at the root of the source - * tree and available online at - * - * https://github.com/keeps/roda - */ -/** - * - */ -package org.roda.wui.client.management; - -import java.util.List; - -import org.roda.core.data.exceptions.NotFoundException; -import org.roda.core.data.v2.user.Group; -import org.roda.wui.client.common.UserLogin; -import org.roda.wui.client.services.Services; -import org.roda.wui.common.client.HistoryResolver; -import org.roda.wui.common.client.tools.HistoryUtils; -import org.roda.wui.common.client.tools.ListUtils; -import org.roda.wui.common.client.widgets.Toast; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.uibinder.client.UiBinder; -import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.uibinder.client.UiHandler; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.Button; -import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.Widget; - -import config.i18n.client.ClientMessages; - -/** - * @author Luis Faria - * - */ -public class EditGroup extends Composite { - - public static final HistoryResolver RESOLVER = new HistoryResolver() { - - @Override - public void resolve(List historyTokens, final AsyncCallback callback) { - if (historyTokens.size() == 1) { - String groupname = historyTokens.get(0); - Services services = new Services("Get Group", "get"); - services.membersResource(s -> s.getGroup(groupname)).whenComplete((group, error) -> { - if (group != null) { - EditGroup editGroup = new EditGroup(group); - callback.onSuccess(editGroup); - } else if (error != null) { - callback.onFailure(error); - } - }); - } else { - HistoryUtils.newHistory(MemberManagement.RESOLVER); - callback.onSuccess(null); - } - - } - - @Override - public void isCurrentUserPermitted(AsyncCallback callback) { - UserLogin.getInstance().checkRoles(new HistoryResolver[] {MemberManagement.RESOLVER}, false, callback); - } - - @Override - public List getHistoryPath() { - return ListUtils.concat(MemberManagement.RESOLVER.getHistoryPath(), getHistoryToken()); - } - - @Override - public String getHistoryToken() { - return "edit_group"; - } - }; - - interface MyUiBinder extends UiBinder { - } - - private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); - - private Group group; - - private static final ClientMessages messages = GWT.create(ClientMessages.class); - - @UiField - Button buttonApply; - - @UiField - Button buttonCancel; - - @UiField(provided = true) - GroupDataPanel groupDataPanel; - - /** - * Create a new panel to edit a group - * - * @param group - * the group to edit - */ - public EditGroup(Group group) { - this.group = group; - - this.groupDataPanel = new GroupDataPanel(true, true); - this.groupDataPanel.setGroup(group); - - initWidget(uiBinder.createAndBindUi(this)); - } - - @UiHandler("buttonApply") - void buttonApplyHandler(ClickEvent e) { - if (groupDataPanel.isChanged()) { - if (groupDataPanel.isValid()) { - group = groupDataPanel.getGroup(); - Services services = new Services("Update group", "update"); - services.membersResource(s -> s.updateGroup(group)).whenComplete((res, error) -> { - if (error == null) { - HistoryUtils.newHistory(MemberManagement.RESOLVER); - } else { - errorMessage(error); - } - }); - } - } else { - HistoryUtils.newHistory(MemberManagement.RESOLVER); - } - } - - @UiHandler("buttonRemove") - void buttonRemoveHandler(ClickEvent e) { - Services services = new Services("Delete group", "delete"); - services.membersResource(s -> s.deleteGroup(group.getId())).whenComplete((res, error) -> { - if (error == null) { - HistoryUtils.newHistory(MemberManagement.RESOLVER); - } else { - errorMessage(error); - } - }); - } - - @UiHandler("buttonCancel") - void buttonCancelHandler(ClickEvent e) { - cancel(); - } - - private void cancel() { - HistoryUtils.newHistory(ShowGroup.RESOLVER, group.getId()); - } - - private void errorMessage(Throwable caught) { - if (caught instanceof NotFoundException) { - Toast.showError(messages.editGroupNotFound(group.getName())); - cancel(); - } else { - Toast.showError(messages.editGroupFailure(EditGroup.this.group.getName(), caught.getMessage())); - } - } -} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/EditGroup.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/EditGroup.ui.xml deleted file mode 100644 index e51a43cbfe..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/EditGroup.ui.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/EditUser.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/EditUser.java deleted file mode 100644 index 550e663c4e..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/EditUser.java +++ /dev/null @@ -1,241 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE file at the root of the source - * tree and available online at - * - * https://github.com/keeps/roda - */ -/** - * - */ -package org.roda.wui.client.management; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; - -import org.roda.core.data.common.SecureString; -import org.roda.core.data.exceptions.AlreadyExistsException; -import org.roda.core.data.exceptions.NotFoundException; -import org.roda.core.data.v2.generics.select.SelectedItemsListRequest; -import org.roda.core.data.v2.user.User; -import org.roda.core.data.v2.user.requests.ChangeUserStatusRequest; -import org.roda.core.data.v2.user.requests.UpdateUserRequest; -import org.roda.wui.client.common.UserLogin; -import org.roda.wui.client.common.dialogs.Dialogs; -import org.roda.wui.client.ingest.process.ShowJob; -import org.roda.wui.client.services.Services; -import org.roda.wui.common.client.HistoryResolver; -import org.roda.wui.common.client.tools.HistoryUtils; -import org.roda.wui.common.client.tools.ListUtils; -import org.roda.wui.common.client.widgets.Toast; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.uibinder.client.UiBinder; -import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.uibinder.client.UiHandler; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.Button; -import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.Widget; - -import config.i18n.client.ClientMessages; - -/** - * @author Luis Faria - * - */ -public class EditUser extends Composite { - - public static final HistoryResolver RESOLVER = new HistoryResolver() { - - @Override - public void resolve(List historyTokens, final AsyncCallback callback) { - if (historyTokens.size() == 1) { - String username = historyTokens.get(0); - Services services = new Services("Get User", "get"); - services.membersResource(s -> s.getUser(username)).whenComplete((user, error) -> { - if (user != null) { - EditUser editUser = new EditUser(user); - callback.onSuccess(editUser); - } else if (error != null) { - callback.onFailure(error); - } - }); - } else { - HistoryUtils.newHistory(MemberManagement.RESOLVER); - callback.onSuccess(null); - } - } - - @Override - public void isCurrentUserPermitted(AsyncCallback callback) { - UserLogin.getInstance().checkRoles(new HistoryResolver[] {MemberManagement.RESOLVER}, false, callback); - } - - @Override - public List getHistoryPath() { - return ListUtils.concat(MemberManagement.RESOLVER.getHistoryPath(), getHistoryToken()); - } - - @Override - public String getHistoryToken() { - return "edit_user"; - } - }; - - interface MyUiBinder extends UiBinder { - } - - private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); - - private final User user; - - private static final ClientMessages messages = GWT.create(ClientMessages.class); - - @UiField - Button buttonApply; - - @UiField - Button buttonDeActivate; - - @UiField - Button buttonRemove; - - @UiField - Button buttonCancel; - - @UiField(provided = true) - UserDataPanel userDataPanel; - - /** - * Create a new panel to edit a user - * - * @param user - * the user to edit - */ - public EditUser(User user) { - this.user = user; - this.userDataPanel = new UserDataPanel(true, true, true); - this.userDataPanel.setUser(user); - if (user.getExtra() != null) { - this.userDataPanel.setUserExtra(user.getExtra()); - } else { - this.userDataPanel.setUserExtra(new HashSet<>()); - } - - initWidget(uiBinder.createAndBindUi(this)); - - userDataPanel.setUsernameReadOnly(true); - - buttonDeActivate.setEnabled(true); - if (user.isActive()) { - buttonDeActivate.setText(messages.editUserDeactivate()); - } - } - - private SecureString getPassword() { - if (userDataPanel.getPassword() != null) { - return new SecureString(userDataPanel.getPassword().toCharArray()); - } else { - return null; - } - } - - @UiHandler("buttonApply") - void buttonApplyHandler(ClickEvent e) { - if (userDataPanel.isChanged()) { - if (userDataPanel.isValid()) { - final User updatedUser = userDataPanel.getUser(); - try (SecureString password = getPassword()) { - Services services = new Services("Update User", "update"); - UpdateUserRequest userOperations = new UpdateUserRequest(updatedUser, password, userDataPanel.getUserExtra()); - services.membersResource(s -> s.updateUser(userOperations)).whenComplete((res, error) -> { - if (error == null) { - HistoryUtils.newHistory(ShowUser.RESOLVER, res.getId()); - } else { - errorMessage(error, updatedUser); - } - }); - } - } else { - HistoryUtils.newHistory(MemberManagement.RESOLVER); - } - } - } - - @UiHandler("buttonDeActivate") - void buttonDeActivateHandler(ClickEvent e) { - user.setActive(!user.isActive()); - Services services = new Services("Update User", "update"); - - ChangeUserStatusRequest request = new ChangeUserStatusRequest( - new SelectedItemsListRequest(Arrays.asList(user.getUUID())), user.isActive()); - services.membersResource(s -> s.changeActive(request)).whenComplete((res, error) -> { - if (error == null) { - Toast.showInfo(messages.runningInBackgroundTitle(), messages.runningInBackgroundDescription()); - Dialogs.showJobRedirectDialog(messages.jobCreatedMessage(), new AsyncCallback() { - - @Override - public void onFailure(Throwable caught) { - HistoryUtils.newHistory(MemberManagement.RESOLVER); - } - - @Override - public void onSuccess(final Void nothing) { - HistoryUtils.newHistory(ShowJob.RESOLVER, res.getId()); - } - }); - - HistoryUtils.newHistory(MemberManagement.RESOLVER); - } - }); - } - - @UiHandler("buttonRemove") - void buttonRemoveHandler(ClickEvent e) { - Dialogs.showConfirmDialog(messages.userRemoveConfirmDialogTitle(), messages.userRemoveConfirmDialogMessage(), - messages.dialogNo(), messages.dialogYes(), new AsyncCallback() { - @Override - public void onSuccess(Boolean confirmed) { - if (confirmed) { - Services services = new Services("Delete user", "delete"); - services.membersResource(s -> s.deleteUser(user.getId())).whenComplete((res, error) -> { - if (error == null) { - HistoryUtils.newHistory(MemberManagement.RESOLVER); - } else { - errorMessage(error, null); - } - }); - } - } - - @Override - public void onFailure(Throwable caught) { - errorMessage(caught, null); - } - }); - } - - @UiHandler("buttonCancel") - void buttonCancelHandler(ClickEvent e) { - cancel(); - } - - private void cancel() { - HistoryUtils.newHistory(ShowUser.RESOLVER, user.getId()); - } - - private void errorMessage(Throwable caught, User modifiedUser) { - if (caught instanceof NotFoundException) { - Toast.showError(messages.editUserNotFound(user.getName())); - cancel(); - } else if (caught instanceof AlreadyExistsException) { - String email = (modifiedUser != null) ? modifiedUser.getEmail() : user.getEmail(); - Toast.showError(messages.editUserEmailAlreadyExists(email)); - } else { - Toast.showError(messages.editUserFailure(EditUser.this.user.getName(), caught.getMessage())); - } - } -} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/EditUser.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/EditUser.ui.xml deleted file mode 100644 index 86a9a8cb13..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/EditUser.ui.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/GroupDataPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/GroupDataPanel.java deleted file mode 100644 index 9a1aa7fe3e..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/GroupDataPanel.java +++ /dev/null @@ -1,268 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE file at the root of the source - * tree and available online at - * - * https://github.com/keeps/roda - */ -/** - * - */ -package org.roda.wui.client.management; - -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import org.roda.core.data.common.RodaConstants; -import org.roda.core.data.v2.user.Group; -import org.roda.wui.client.common.utils.AsyncCallbackUtils; -import org.roda.wui.common.client.ClientLogger; -import org.roda.wui.common.client.tools.HistoryUtils; -import org.roda.wui.common.client.tools.StringUtils; -import org.roda.wui.common.client.widgets.wcag.WCAGUtilities; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.ChangeEvent; -import com.google.gwt.event.dom.client.ChangeHandler; -import com.google.gwt.event.dom.client.KeyUpEvent; -import com.google.gwt.event.dom.client.KeyUpHandler; -import com.google.gwt.event.logical.shared.HasValueChangeHandlers; -import com.google.gwt.event.logical.shared.ValueChangeEvent; -import com.google.gwt.event.logical.shared.ValueChangeHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.uibinder.client.UiBinder; -import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.TextBox; -import com.google.gwt.user.client.ui.Widget; - -/** - * @author Luis Faria - * - */ -public class GroupDataPanel extends Composite implements HasValueChangeHandlers { - - interface MyUiBinder extends UiBinder { - } - - private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); - - private Group group = new Group(); - - @UiField - TextBox groupname; - - @UiField - TextBox fullname; - - @UiField - Label usersLabel, usersValue; - - @UiField - FlowPanel permissionsSelectPanel; - - @UiField - PermissionsPanel permissionsPanel; - - @SuppressWarnings("unused") - private ClientLogger logger = new ClientLogger(getClass().getName()); - - private boolean editmode; - - private boolean changed = false; - private boolean checked = false; - - /** - * Create a new group data panel - * - * @param editmode - * if group name should be editable - */ - public GroupDataPanel(boolean editmode) { - this(true, editmode); - } - - /** - * - * @param visible - * @param editmode - */ - public GroupDataPanel(boolean visible, boolean editmode) { - - initWidget(uiBinder.createAndBindUi(this)); - - this.editmode = editmode; - super.setVisible(visible); - - ChangeHandler changeHandler = new ChangeHandler() { - - @Override - public void onChange(ChangeEvent event) { - GroupDataPanel.this.onChange(); - } - }; - - KeyUpHandler keyUpHandler = new KeyUpHandler() { - - @Override - public void onKeyUp(KeyUpEvent event) { - onChange(); - } - }; - - groupname.setEnabled(!isEditmode()); - groupname.addKeyDownHandler(new UserAndGroupKeyDownHandler()); - groupname.addChangeHandler(changeHandler); - groupname.addKeyUpHandler(keyUpHandler); - fullname.addChangeHandler(changeHandler); - fullname.addKeyUpHandler(keyUpHandler); - - permissionsPanel.addValueChangeHandler(new ValueChangeHandler>() { - - @Override - public void onValueChange(ValueChangeEvent> event) { - onChange(); - } - }); - } - - /** - * Set group information of group - * - * @param group - */ - public void setGroup(Group group) { - this.group = group; - this.groupname.setText(group.getName()); - this.fullname.setText(group.getFullName()); - this.usersValue.setText(StringUtils.prettyPrint(group.getUsers())); - - this.setPermissions(group.getName(), group.getDirectRoles(), group.getAllRoles()); - - // update visibility - this.usersLabel.setVisible(!group.getUsers().isEmpty()); - this.usersValue.setVisible(!group.getUsers().isEmpty()); - - } - - private void setPermissions(final String name, final Set directRoles, final Set allRoles) { - permissionsPanel.init(new AsyncCallback() { - - @Override - public void onSuccess(Boolean result) { - Set indirectRoles = new HashSet<>(allRoles); - indirectRoles.removeAll(directRoles); - permissionsPanel.checkPermissions(directRoles, RodaConstants.ADMINISTRATORS.equals(name)); - permissionsPanel.checkPermissions(indirectRoles, true); - WCAGUtilities.getInstance().makeAccessible(permissionsSelectPanel.getElement()); - } - - @Override - public void onFailure(Throwable caught) { - AsyncCallbackUtils.defaultFailureTreatment(caught); - HistoryUtils.newHistory(MemberManagement.RESOLVER); - } - }); - } - - /** - * Get group defined by this panel. This panel defines: name, fullname - * - * @return the group modified by this panel - */ - public Group getGroup() { - group.setId(groupname.getText()); - group.setName(groupname.getText()); - group.setFullName(fullname.getText()); - group.setDirectRoles(permissionsPanel.getDirectRoles()); - return group; - } - - /** - * Is group data panel valid - * - * @return true if valid - */ - public boolean isValid() { - boolean valid = true; - - if (groupname.getText().length() == 0) { - valid = false; - groupname.addStyleName("isWrong"); - } else { - groupname.removeStyleName("isWrong"); - } - - if (fullname.getText().length() == 0) { - valid = false; - fullname.addStyleName("isWrong"); - } else { - fullname.removeStyleName("isWrong"); - } - - checked = true; - return valid; - } - - /** - * Is group name read only - * - * @return true if read only - */ - public boolean isGroupnameReadOnly() { - return groupname.isReadOnly(); - } - - /** - * Set group name read only - * - * @param readonly - */ - public void setGroupnameReadOnly(boolean readonly) { - groupname.setReadOnly(readonly); - } - - public void clear() { - groupname.setText(""); - fullname.setText(""); - } - - /** - * Is group data panel editable, i.e. on create group mode - * - * @return true if editable - */ - public boolean isEditmode() { - return editmode; - } - - /** - * Is group data panel has been changed - * - * @return changed - */ - public boolean isChanged() { - return changed; - } - - @Override - public HandlerRegistration addValueChangeHandler(ValueChangeHandler handler) { - return addHandler(handler, ValueChangeEvent.getType()); - } - - protected void onChange() { - changed = true; - if (checked) { - isValid(); - } - ValueChangeEvent.fire(this, getValue()); - } - - public Group getValue() { - return getGroup(); - } -} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/Management.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/Management.java index 3d93c3ff7d..94814d218d 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/Management.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/Management.java @@ -16,6 +16,7 @@ import org.roda.wui.client.common.UserLogin; import org.roda.wui.client.management.distributed.DistributedInstancesManagement; import org.roda.wui.client.management.distributed.LocalInstanceManagement; +import org.roda.wui.client.management.members.MemberManagement; import org.roda.wui.client.process.ActionProcess; import org.roda.wui.client.process.CreateSelectedJob; import org.roda.wui.client.process.InternalProcess; diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowGroup.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowGroup.java deleted file mode 100644 index decb3e1dc1..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowGroup.java +++ /dev/null @@ -1,178 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE file at the root of the source - * tree and available online at - * - * https://github.com/keeps/roda - */ -package org.roda.wui.client.management; - -import java.util.List; -import java.util.MissingResourceException; -import java.util.Set; - -import org.roda.core.data.v2.user.Group; -import org.roda.core.data.v2.user.RODAMember; -import org.roda.wui.client.common.NoAsyncCallback; -import org.roda.wui.client.common.UserLogin; -import org.roda.wui.client.common.actions.Actionable; -import org.roda.wui.client.common.actions.RODAMemberActions; -import org.roda.wui.client.common.actions.model.ActionableObject; -import org.roda.wui.client.common.actions.widgets.ActionableWidgetBuilder; -import org.roda.wui.client.common.utils.SidebarUtils; -import org.roda.wui.client.services.Services; -import org.roda.wui.common.client.HistoryResolver; -import org.roda.wui.common.client.tools.ConfigurationManager; -import org.roda.wui.common.client.tools.HistoryUtils; -import org.roda.wui.common.client.tools.ListUtils; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.uibinder.client.UiBinder; -import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.InlineHTML; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.SimplePanel; -import com.google.gwt.user.client.ui.Widget; - -import config.i18n.client.ClientMessages; - -/** - * @author Gabriel Barros - */ -public class ShowGroup extends Composite { - public static final HistoryResolver RESOLVER = new HistoryResolver() { - - @Override - public void resolve(List historyTokens, final AsyncCallback callback) { - if (historyTokens.size() == 1) { - String groupname = historyTokens.get(0); - Services services = new Services("Get Group", "get"); - services.membersResource(s -> s.getGroup(groupname)).whenComplete((group, error) -> { - if (group != null) { - ShowGroup showGroup = new ShowGroup(group); - callback.onSuccess(showGroup); - } else if (error != null) { - callback.onFailure(error); - } - }); - } else { - HistoryUtils.newHistory(MemberManagement.RESOLVER); - callback.onSuccess(null); - } - - } - - @Override - public void isCurrentUserPermitted(AsyncCallback callback) { - UserLogin.getInstance().checkRoles(new HistoryResolver[] {MemberManagement.RESOLVER}, false, callback); - } - - @Override - public List getHistoryPath() { - return ListUtils.concat(MemberManagement.RESOLVER.getHistoryPath(), getHistoryToken()); - } - - @Override - public String getHistoryToken() { - return "show_group"; - } - }; - - interface MyUiBinder extends UiBinder { - } - - private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); - - private ActionableWidgetBuilder actionableWidgetBuilder; - private Group group; - - private static final ClientMessages messages = GWT.create(ClientMessages.class); - - @UiField - Label fullnameValue, groupNameValue; - - @UiField - FlowPanel userList, permissionList; - @UiField - SimplePanel actionsSidebar; - - @UiField - FlowPanel contentFlowPanel; - - @UiField - FlowPanel sidebarFlowPanel; - - public ShowGroup(Group group) { - this.group = group; - initWidget(uiBinder.createAndBindUi(this)); - initElements(); - } - - private void initElements() { - groupNameValue.setText(group.getName()); - fullnameValue.setText(group.getFullName()); - - if (group.getUsers().isEmpty()) { - userList.add(new Label(messages.showGroupEmptyUserList())); - } else { - for (String user : group.getUsers()) { - userList.add(createListItem(user)); - } - } - - // Permissions - buildPermissionList(); - - // Sidebar - RODAMemberActions rodaMemberActions = RODAMemberActions.get(); - actionableWidgetBuilder = new ActionableWidgetBuilder<>(rodaMemberActions).withBackButton() - .withActionCallback(new NoAsyncCallback() { - @Override - public void onSuccess(Actionable.ActionImpact result) { - if (result.equals(Actionable.ActionImpact.DESTROYED)) { - HistoryUtils.newHistory(MemberManagement.RESOLVER); - } - } - }); - - SidebarUtils.toggleSidebar(contentFlowPanel, sidebarFlowPanel, rodaMemberActions.hasAnyRoles()); - actionsSidebar.setWidget(actionableWidgetBuilder.buildListWithObjects(new ActionableObject<>(this.group))); - } - - private void buildPermissionList() { - Set allGroupRoles = group.getAllRoles(); - - if (allGroupRoles.isEmpty()) { - permissionList.add(new Label(messages.showGroupEmptyPermissions())); - } else { - List roles = ConfigurationManager.getStringList("ui.role"); - for (String role : roles) { - String description; - try { - description = messages.role(role); - } catch (MissingResourceException e) { - description = role + " (needs translation)"; - } - if (allGroupRoles.contains(role)) { - permissionList.add(createListItem(description)); - } - } - } - } - - private FlowPanel createListItem(String item) { - FlowPanel panel = new FlowPanel(); - InlineHTML bullet = new InlineHTML("•"); - InlineHTML value = new InlineHTML(item); - - bullet.addStyleName("bullet"); - panel.add(bullet); - panel.add(value); - - return panel; - } - -} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowGroup.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowGroup.ui.xml deleted file mode 100644 index ac1e565ea5..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowGroup.ui.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowLogEntry.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowLogEntry.java index cf86182336..bd2ac3a59b 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowLogEntry.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowLogEntry.java @@ -74,7 +74,7 @@ public void resolve(List historyTokens, final AsyncCallback call @Override public void isCurrentUserPermitted(AsyncCallback callback) { - UserLogin.getInstance().checkRoles(new HistoryResolver[] {MemberManagement.RESOLVER}, false, callback); + UserLogin.getInstance().checkRoles(new HistoryResolver[] {Management.RESOLVER}, false, callback); } @Override @@ -87,13 +87,8 @@ public String getHistoryToken() { return "logentry"; } }; - - interface MyUiBinder extends UiBinder { - } - private static final MyUiBinder uiBinder = GWT.create(MyUiBinder.class); private static final ClientMessages messages = GWT.create(ClientMessages.class); - @UiField Label logIdLabel; @UiField @@ -144,7 +139,6 @@ interface MyUiBinder extends UiBinder { SimplePanel expandedAuditLogs; @UiField SimplePanel expandedAuditLogsList; - /** * Create a new panel to view a log entry * @@ -230,4 +224,7 @@ public ShowLogEntry(LogEntry logEntry) { } } + interface MyUiBinder extends UiBinder { + } + } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowNotification.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowNotification.java index e134bc00ff..12a9679b3d 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowNotification.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowNotification.java @@ -69,7 +69,7 @@ public void resolve(List historyTokens, final AsyncCallback call @Override public void isCurrentUserPermitted(AsyncCallback callback) { - UserLogin.getInstance().checkRoles(new HistoryResolver[] {MemberManagement.RESOLVER}, false, callback); + UserLogin.getInstance().checkRole(this, callback); } @Override diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowUser.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowUser.java deleted file mode 100644 index b242ed820d..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowUser.java +++ /dev/null @@ -1,192 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE file at the root of the source - * tree and available online at - * - * https://github.com/keeps/roda - */ -package org.roda.wui.client.management; - -import java.util.List; -import java.util.MissingResourceException; -import java.util.Set; - -import org.roda.core.data.v2.user.RODAMember; -import org.roda.core.data.v2.user.User; -import org.roda.wui.client.common.NoAsyncCallback; -import org.roda.wui.client.common.UserLogin; -import org.roda.wui.client.common.actions.Actionable; -import org.roda.wui.client.common.actions.RODAMemberActions; -import org.roda.wui.client.common.actions.model.ActionableObject; -import org.roda.wui.client.common.actions.widgets.ActionableWidgetBuilder; -import org.roda.wui.client.common.utils.HtmlSnippetUtils; -import org.roda.wui.client.common.utils.SidebarUtils; -import org.roda.wui.client.management.access.AccessKeyTablePanel; -import org.roda.wui.client.services.Services; -import org.roda.wui.common.client.HistoryResolver; -import org.roda.wui.common.client.tools.ConfigurationManager; -import org.roda.wui.common.client.tools.HistoryUtils; -import org.roda.wui.common.client.tools.ListUtils; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.uibinder.client.UiBinder; -import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.InlineHTML; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.SimplePanel; -import com.google.gwt.user.client.ui.Widget; - -import config.i18n.client.ClientMessages; - -/** - * @author Gabriel Barros - */ -public class ShowUser extends Composite { - public static final HistoryResolver RESOLVER = new HistoryResolver() { - - @Override - public void resolve(List historyTokens, final AsyncCallback callback) { - if (historyTokens.size() == 1) { - String username = historyTokens.get(0); - Services services = new Services("Get User", "get"); - services.membersResource(s -> s.getUser(username)).whenComplete((user, error) -> { - if (user != null) { - ShowUser showUser = new ShowUser(user); - callback.onSuccess(showUser); - } else if (error != null) { - callback.onFailure(error); - } - }); - } else { - HistoryUtils.newHistory(MemberManagement.RESOLVER); - callback.onSuccess(null); - } - } - - @Override - public void isCurrentUserPermitted(AsyncCallback callback) { - UserLogin.getInstance().checkRoles(new HistoryResolver[] {MemberManagement.RESOLVER}, false, callback); - } - - @Override - public List getHistoryPath() { - return ListUtils.concat(MemberManagement.RESOLVER.getHistoryPath(), getHistoryToken()); - } - - @Override - public String getHistoryToken() { - return "show_user"; - } - }; - private static final ClientMessages messages = GWT.create(ClientMessages.class); - private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); - private final User user; - @UiField - Label userNameValue; - @UiField - Label fullnameValue; - @UiField - Label emailValue; - @UiField - HTML stateValue; - @UiField - FlowPanel extraValue; - @UiField - FlowPanel permissionList; - @UiField - FlowPanel groupList; - @UiField - FlowPanel accessKeyTablePanel; - @UiField - SimplePanel actionsSidebar; - @UiField - FlowPanel contentFlowPanel; - @UiField - FlowPanel sidebarFlowPanel; - - public ShowUser(User user) { - this.user = user; - initWidget(uiBinder.createAndBindUi(this)); - initElements(); - } - - private void initElements() { - userNameValue.setText(user.getName()); - fullnameValue.setText(user.getFullName()); - emailValue.setText(user.getEmail()); - stateValue.setHTML(HtmlSnippetUtils.getUserStateHtml(user)); - - if (!user.getExtra().isEmpty()) { - HtmlSnippetUtils.createExtraShow(extraValue, user.getExtra(), false); - } - - // Groups - if (user.getGroups().isEmpty()) { - groupList.add(new Label(messages.showUserEmptyGroupList())); - } else { - for (String group : user.getGroups()) { - groupList.add(createListItem(group)); - } - } - - // Permissions - buildPermissionList(); - - accessKeyTablePanel.add(new AccessKeyTablePanel(user.getId())); - - // Sidebar - RODAMemberActions rodaMemberActions = RODAMemberActions.get(); - ActionableWidgetBuilder actionableWidgetBuilder = new ActionableWidgetBuilder<>(rodaMemberActions) - .withBackButton().withActionCallback(new NoAsyncCallback() { - @Override - public void onSuccess(Actionable.ActionImpact result) { - if (result.equals(Actionable.ActionImpact.DESTROYED)) { - HistoryUtils.newHistory(MemberManagement.RESOLVER); - } - } - }); - - SidebarUtils.toggleSidebar(contentFlowPanel, sidebarFlowPanel, rodaMemberActions.hasAnyRoles()); - actionsSidebar.setWidget(actionableWidgetBuilder.buildListWithObjects(new ActionableObject<>(this.user))); - } - - private void buildPermissionList() { - Set allUserRoles = user.getAllRoles(); - - if (allUserRoles.isEmpty()) { - permissionList.add(new Label(messages.showUserEmptyPermissions())); - } else { - List roles = ConfigurationManager.getStringList("ui.role"); - for (String role : roles) { - String description; - try { - description = messages.role(role); - } catch (MissingResourceException e) { - description = role + " (needs translation)"; - } - if (allUserRoles.contains(role)) { - permissionList.add(createListItem(description)); - } - } - } - } - - private FlowPanel createListItem(String item) { - FlowPanel panel = new FlowPanel(); - InlineHTML bullet = new InlineHTML("•"); - InlineHTML value = new InlineHTML(item); - - bullet.addStyleName("bullet"); - panel.add(bullet); - panel.add(value); - - return panel; - } - - interface MyUiBinder extends UiBinder { - } -} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowUser.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowUser.ui.xml deleted file mode 100644 index 91b25185e6..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowUser.ui.xml +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/AccessKeyDataPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/AccessKeyDataPanel.java deleted file mode 100644 index b8276c495b..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/AccessKeyDataPanel.java +++ /dev/null @@ -1,192 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE file at the root of the source - * tree and available online at - * - * https://github.com/keeps/roda - */ -package org.roda.wui.client.management.access; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -import org.roda.core.data.v2.accessKey.AccessKey; -import org.roda.wui.common.client.tools.StringUtils; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.ChangeEvent; -import com.google.gwt.event.dom.client.ChangeHandler; -import com.google.gwt.event.dom.client.KeyUpEvent; -import com.google.gwt.event.dom.client.KeyUpHandler; -import com.google.gwt.event.logical.shared.HasValueChangeHandlers; -import com.google.gwt.event.logical.shared.ValueChangeEvent; -import com.google.gwt.event.logical.shared.ValueChangeHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.i18n.client.DateTimeFormat; -import com.google.gwt.uibinder.client.UiBinder; -import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.TextBox; -import com.google.gwt.user.client.ui.Widget; -import com.google.gwt.user.datepicker.client.DateBox; - -import config.i18n.client.ClientMessages; - -/** - * @author Gabriel Barros - */ -public class AccessKeyDataPanel extends Composite implements HasValueChangeHandlers { - public static final String IS_WRONG = "isWrong"; - - interface MyUiBinder extends UiBinder { - } - - private static AccessKeyDataPanel.MyUiBinder uiBinder = GWT.create(AccessKeyDataPanel.MyUiBinder.class); - - private static final ClientMessages messages = GWT.create(ClientMessages.class); - - @UiField - TextBox name; - - @UiField - Label nameError; - - @UiField - DateBox expirationDate; - - @UiField - Label expirationDateError; - - @UiField - HTML errors; - - private final boolean editMode; - - private boolean changed = false; - private boolean checked = false; - - public AccessKeyDataPanel(AccessKey accessKey, boolean editMode) { - initWidget(uiBinder.createAndBindUi(this)); - - this.editMode = editMode; - - setInitialState(accessKey); - initHandlers(); - - if (editMode) { - setAccessKey(accessKey); - } - } - - private void initHandlers() { - ChangeHandler changeHandler = new ChangeHandler() { - @Override - public void onChange(ChangeEvent changeEvent) { - AccessKeyDataPanel.this.onChange(); - } - }; - - KeyUpHandler keyUpHandler = new KeyUpHandler() { - @Override - public void onKeyUp(KeyUpEvent keyUpEvent) { - AccessKeyDataPanel.this.onChange(); - } - }; - } - - private void setInitialState(AccessKey accessKey) { - errors.setVisible(false); - - DateBox.DefaultFormat dateFormat = new DateBox.DefaultFormat(DateTimeFormat.getFormat("yyyy-MM-dd")); - expirationDate.setFormat(dateFormat); - expirationDate.getDatePicker().setYearArrowsVisible(true); - expirationDate.setFireNullValues(true); - expirationDate.setValue(new Date()); - - expirationDate.addValueChangeHandler(new ValueChangeHandler() { - @Override - public void onValueChange(ValueChangeEvent valueChangeEvent) { - onChange(); - } - }); - } - - public void setAccessKey(AccessKey accessKey) { - this.name.setText(accessKey.getName()); - this.expirationDate.setValue(accessKey.getExpirationDate()); - } - - public AccessKey getAccessKey() { - AccessKey accessKey = new AccessKey(); - accessKey.setName(name.getText()); - accessKey.setExpirationDate(expirationDate.getValue()); - return accessKey; - } - - public boolean isValid() { - List errorList = new ArrayList<>(); - if (StringUtils.isBlank(name.getText())) { - name.addStyleName(IS_WRONG); - nameError.setText(messages.mandatoryField()); - nameError.setVisible(true); - Window.scrollTo(name.getAbsoluteLeft(), name.getAbsoluteTop()); - errorList.add(messages.isAMandatoryField(messages.distributedInstanceNameLabel())); - } else { - name.removeStyleName(IS_WRONG); - nameError.setVisible(false); - } - - if (expirationDate.getValue() == null || expirationDate.getValue().before(new Date())) { - expirationDate.addStyleName("isWrong"); - expirationDateError.setVisible(true); - expirationDateError.setText(messages.mandatoryField()); - errorList.add(messages.isAMandatoryField(messages.accessKeyExpirationDateLabel())); - } else { - expirationDate.removeStyleName("isWrong"); - expirationDateError.setVisible(false); - } - - return errorList.isEmpty(); - } - - @Override - public HandlerRegistration addValueChangeHandler(ValueChangeHandler handler) { - return addHandler(handler, ValueChangeEvent.getType()); - } - - // @UiHandler("buttonAddMember") - // void buttonAddMemberHandler(ClickEvent e) { - // Filter filter = new Filter(); - // filter.add(new SimpleFilterParameter(RodaConstants.MEMBERS_IS_USER, "true")); - // - // MemberSelectDialog selectDialog = new - // MemberSelectDialog(messages.selectUserOrGroupToAdd(), filter); - // selectDialog.showAndCenter(); - // selectDialog.addValueChangeHandler(new ValueChangeHandler() { - // - // @Override - // public void onValueChange(ValueChangeEvent event) { - // RODAMember selected = event.getValue(); - // if (selected != null) { - // user.setText(selected.getName()); - // } - // } - // }); - // } - - protected void onChange() { - changed = true; - if (checked) { - isValid(); - } - ValueChangeEvent.fire(this, getValue()); - } - - public AccessKey getValue() { - return getAccessKey(); - } -} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/AccessKeyDataPanel.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/AccessKeyDataPanel.ui.xml deleted file mode 100644 index c72a34989e..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/AccessKeyDataPanel.ui.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - .section { - margin-top: 20px; - } - - .group { - display: flex; - } - - - - - - - - - - - - * - - - - - - - * - - - - - - - - - - - diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/AccessKeyTablePanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/AccessKeyTablePanel.java index 33038e997f..a57838a1c2 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/AccessKeyTablePanel.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/AccessKeyTablePanel.java @@ -7,14 +7,20 @@ */ package org.roda.wui.client.management.access; -import java.util.List; - +import com.google.gwt.user.client.rpc.AsyncCallback; +import org.roda.core.data.common.RodaConstants; import org.roda.core.data.v2.accessKey.AccessKey; +import org.roda.core.data.v2.accessKey.AccessKeyStatus; import org.roda.core.data.v2.accessKey.AccessKeys; +import org.roda.core.data.v2.accessKey.CreateAccessKeyRequest; +import org.roda.wui.client.common.NoAsyncCallback; +import org.roda.wui.client.common.dialogs.AccessKeyDialogs; +import org.roda.wui.client.common.dialogs.Dialogs; +import org.roda.wui.client.common.lists.utils.ActionMenuCell; import org.roda.wui.client.common.lists.utils.BasicTablePanel; import org.roda.wui.client.common.utils.HtmlSnippetUtils; +import org.roda.wui.client.common.utils.PermissionClientUtils; import org.roda.wui.client.services.Services; -import org.roda.wui.common.client.tools.HistoryUtils; import org.roda.wui.common.client.tools.Humanize; import com.google.gwt.cell.client.SafeHtmlCell; @@ -25,44 +31,53 @@ import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.cellview.client.Column; import com.google.gwt.user.cellview.client.TextColumn; +import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.PopupPanel; import com.google.gwt.user.client.ui.ScrollPanel; import com.google.gwt.user.client.ui.Widget; import config.i18n.client.ClientMessages; +import org.roda.wui.common.client.widgets.Toast; /** * @author Gabriel Barros */ public class AccessKeyTablePanel extends Composite { private static final ClientMessages messages = GWT.create(ClientMessages.class); - - interface MyUiBinder extends UiBinder { - } - private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); - @UiField FlowPanel contentFlowPanel; + private BasicTablePanel table; + private String username; + public AccessKeyTablePanel(String username) { initWidget(uiBinder.createAndBindUi(this)); + this.username = username; + + // Call the new unified refresh method + refresh(); + } + + public void refresh() { Services services = new Services("Get user access keys", "get"); services.membersResource(s -> s.getAccessKeysByUser(username)).whenComplete((accessKeys, error) -> { if (accessKeys != null) { + // Clear the panel and rebuild the table/empty state from scratch contentFlowPanel.clear(); contentFlowPanel.add(createTable(accessKeys)); + } else if (error != null) { + Toast.showError(error.getMessage()); } }); } public ScrollPanel createTable(AccessKeys accessKeys) { ScrollPanel scrollPanel = new ScrollPanel(); - scrollPanel.addStyleName("basicTable-border"); - scrollPanel.addStyleName("basicTable"); if (accessKeys.getObjects().isEmpty()) { String someOfAObject = messages.someOfAObject(accessKeys.getClass().getName()); @@ -71,15 +86,8 @@ public ScrollPanel createTable(AccessKeys accessKeys) { scrollPanel.add(label); } else { FlowPanel accessKeyPanel = new FlowPanel(); - BasicTablePanel table = getBasicTableForAccessKey(accessKeys); - table.getSelectionModel().addSelectionChangeHandler(event -> { - AccessKey selectedObject = table.getSelectionModel().getSelectedObject(); - if (selectedObject != null) { - table.getSelectionModel().clear(); - List path = HistoryUtils.getHistory(ShowAccessKey.RESOLVER.getHistoryPath(), selectedObject.getId()); - HistoryUtils.newHistory(path); - } - }); + table = getBasicTableForAccessKey(accessKeys); + table.removeSelectionModel(); accessKeyPanel.add(table); scrollPanel.add(accessKeyPanel); @@ -88,39 +96,215 @@ public ScrollPanel createTable(AccessKeys accessKeys) { return scrollPanel; } + private void showActionsMenu(AccessKey key, int left, int top) { + // 1. Create the Popup + PopupPanel popup = new PopupPanel(true); // true = auto-hide when clicking away + + // 2. Create your FlowPanel and add your action items + FlowPanel menuPanel = new FlowPanel(); + menuPanel.addStyleName("groupedActionableDropdown"); + + Button revokeBtn = new Button(messages.accessKeyRevokeButton()); + revokeBtn.addStyleName("actionable-button actionable-button-updated actionable-button-label btn-edit"); + revokeBtn.addClickHandler(e -> { + popup.hide(); + Dialogs.showConfirmDialog(messages.accessKeyLabel(), messages.accessKeyRevokeConfirmationMessage(), + messages.cancelButton(), messages.confirmButton(), new NoAsyncCallback() { + @Override + public void onSuccess(Boolean confirm) { + if (confirm) { + Services services = new Services("Revoke access key", "revoke"); + services.membersResource(s -> s.revokeAccessKey(key.getId())).whenComplete((response, error) -> { + if (response != null) { + Toast.showInfo(messages.accessKeyLabel(), messages.accessKeySuccessfullyRevoked()); + refresh(); + } else { + Toast.showError(error.getMessage()); + } + }); + } + } + }); + }); + + Button regenBtn = new Button(messages.accessKeyRegenerateButton()); + regenBtn.addStyleName("actionable-button actionable-button-updated actionable-button-label btn-edit"); + regenBtn.addClickHandler(e -> { + popup.hide(); + Dialogs.showConfirmDialog(messages.accessKeyLabel(), messages.accessKeyRegenerateConfirmationMessage(), + messages.cancelButton(), messages.confirmButton(), new NoAsyncCallback() { + @Override + public void onSuccess(Boolean confirm) { + if (confirm) { + AccessKeyDialogs.createAccessKeyDialog(messages.regenerateAccessKeyTitle(), key.getName(), false, + new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + // do nothing + } + + @Override + public void onSuccess(CreateAccessKeyRequest request) { + Services services = new Services("Regenerate access key", "regenerate"); + CreateAccessKeyRequest regenerateAccessKeyRequest = new CreateAccessKeyRequest(); + regenerateAccessKeyRequest.setExpirationDate(request.getExpirationDate()); + services.membersResource(s -> s.regenerateAccessKey(key.getId(), regenerateAccessKeyRequest)) + .whenComplete((response, error) -> { + if (response != null) { + AccessKeyDialogs.showAccessKeyDialog(messages.accessKeyLabel(), response, + new NoAsyncCallback() { + @Override + public void onSuccess(Boolean result) { + Toast.showInfo(messages.accessKeyLabel(), messages.accessKeySuccessfullyRegenerated()); + refresh(); + } + }); + } else { + Toast.showError(error.getMessage()); + } + }); + } + }); + } + } + }); + }); + + Button deleteKeyBtn = new Button(messages.accessKeyDeleteButton()); + deleteKeyBtn.addStyleName("actionable-button actionable-button-updated actionable-button-label btn-edit"); + deleteKeyBtn.addClickHandler(e -> { + popup.hide(); + Dialogs.showConfirmDialog(messages.accessKeyLabel(), messages.accessKeyDeleteConfirmationMessage(), + messages.cancelButton(), messages.confirmButton(), new NoAsyncCallback() { + @Override + public void onSuccess(Boolean confirm) { + if (confirm) { + Services services = new Services("Delete access key", "delete"); + services.membersResource(s -> s.deleteAccessKey(key.getId())).whenComplete((accessKey, error) -> { + if (error != null) { + Toast.showError(error.getMessage()); + } else { + Toast.showInfo(messages.accessKeyLabel(), messages.accessKeySuccessfullyDeleted()); + refresh(); + } + }); + } + } + }); + }); + + if (showRevokeAccessKey(key)) { + menuPanel.add(revokeBtn); + } + + if (showRegenerateAccessKey(key)) { + menuPanel.add(regenBtn); + } + + if (showDeleteAccessKey(key)) { + menuPanel.add(deleteKeyBtn); + } + + // 3. Show the popup at the calculated coordinates + popup.setWidget(menuPanel); + popup.setPopupPosition(left, top); + popup.show(); + } + + private TextColumn getLastUsageDateColumn() { + return new TextColumn() { + @Override + public String getValue(AccessKey accessKey) { + return accessKey.getLastUsageDate() != null ? Humanize.formatDate(accessKey.getLastUsageDate()) + : messages.accessKeyNeverUsedLabel(); + } + }; + } + + private TextColumn getNameColumn() { + return new TextColumn() { + @Override + public String getValue(AccessKey accessKey) { + return accessKey.getName(); + } + }; + } + + private TextColumn getExpirationDateColumn() { + return new TextColumn() { + @Override + public String getValue(AccessKey accessKey) { + return accessKey.getExpirationDate() != null ? Humanize.formatDate(accessKey.getExpirationDate()) + : messages.accessKeyNotFoundLabel(); + } + }; + } + + private Column getStatusColumn() { + return new Column(new SafeHtmlCell()) { + @Override + public SafeHtml getValue(AccessKey accessKey) { + return HtmlSnippetUtils.getAccessKeyStateHtml(accessKey); + } + }; + } + + private Column getActionsColumn() { + ActionMenuCell actionCell = new ActionMenuCell(this::showActionsMenu); + + return new Column(actionCell) { + @Override + public AccessKey getValue(AccessKey object) { + return object; + } + }; + } + + private boolean showActionsColumn() { + return PermissionClientUtils.hasPermissions(RodaConstants.PERMISSION_METHOD_REGENERATE_ACCESS_TOKEN, + RodaConstants.PERMISSION_METHOD_DELETE_ACCESS_TOKEN, RodaConstants.PERMISSION_METHOD_REVOKE_ACCESS_TOKEN); + } + private BasicTablePanel getBasicTableForAccessKey(AccessKeys accessKeys) { if (accessKeys.getObjects().isEmpty()) { return new BasicTablePanel<>(messages.noItemsToDisplay(messages.distributedInstancesLabel())); } else { - return new BasicTablePanel(accessKeys.getObjects().iterator(), - new BasicTablePanel.ColumnInfo(messages.accessKeyNameLabel(), 15, new TextColumn() { - @Override - public String getValue(AccessKey accessKey) { - return accessKey.getName(); - } - }), new BasicTablePanel.ColumnInfo(messages.accessKeyLastUsageDateLabel(), 15, - new TextColumn() { - @Override - public String getValue(AccessKey accessKey) { - return accessKey.getLastUsageDate() != null ? Humanize.formatDate(accessKey.getLastUsageDate()) - : messages.accessKeyNeverUsedLabel(); - } - }), + return new BasicTablePanel<>(accessKeys.getObjects().iterator(), + new BasicTablePanel.ColumnInfo(messages.accessKeyNameLabel(), 15, getNameColumn()), + new BasicTablePanel.ColumnInfo(messages.accessKeyLastUsageDateLabel(), 15, getLastUsageDateColumn()), new BasicTablePanel.ColumnInfo(messages.accessKeyExpirationDateLabel(), 15, - new TextColumn() { - @Override - public String getValue(AccessKey accessKey) { - return accessKey.getExpirationDate() != null ? Humanize.formatDate(accessKey.getExpirationDate()) - : messages.accessKeyNotFoundLabel(); - } - }), - new BasicTablePanel.ColumnInfo(messages.accessKeyStatusLabel(), 15, - new Column(new SafeHtmlCell()) { - @Override - public SafeHtml getValue(AccessKey accessKey) { - return HtmlSnippetUtils.getAccessKeyStateHtml(accessKey); - } - })); + getExpirationDateColumn()), + new BasicTablePanel.ColumnInfo(messages.accessKeyStatusLabel(), 15, getStatusColumn()), + new BasicTablePanel.ColumnInfo(messages.actions(), !showActionsColumn(), 15, getActionsColumn())); + } + } + + private boolean showRevokeAccessKey(AccessKey accessKey) { + switch (accessKey.getStatus()) { + case CREATED: + case ACTIVE: + case INACTIVE: + return true; + default: + return false; } } + + private boolean showRegenerateAccessKey(AccessKey accessKey) { + switch (accessKey.getStatus()) { + case CREATED: + case ACTIVE: + return true; + default: + return false; + } + } + + private boolean showDeleteAccessKey(AccessKey accessKey) { + return AccessKeyStatus.REVOKED.equals(accessKey.getStatus()) + || AccessKeyStatus.EXPIRED.equals(accessKey.getStatus()); + } + + interface MyUiBinder extends UiBinder { + } } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/AccessKeyTablePanel.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/AccessKeyTablePanel.ui.xml index 75fe6c754a..c9596fa7e8 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/AccessKeyTablePanel.ui.xml +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/AccessKeyTablePanel.ui.xml @@ -6,9 +6,6 @@ - - - diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/CreateAccessKey.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/CreateAccessKey.java deleted file mode 100644 index af3fa47a58..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/CreateAccessKey.java +++ /dev/null @@ -1,141 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE file at the root of the source - * tree and available online at - * - * https://github.com/keeps/roda - */ -package org.roda.wui.client.management.access; - -import java.util.List; - -import org.roda.core.data.v2.accessKey.AccessKey; -import org.roda.core.data.v2.accessKey.CreateAccessKeyRequest; -import org.roda.core.data.v2.user.User; -import org.roda.wui.client.common.NoAsyncCallback; -import org.roda.wui.client.common.TitlePanel; -import org.roda.wui.client.common.UserLogin; -import org.roda.wui.client.common.dialogs.AccessKeyDialogs; -import org.roda.wui.client.management.EditUser; -import org.roda.wui.client.management.MemberManagement; -import org.roda.wui.client.management.ShowUser; -import org.roda.wui.client.services.Services; -import org.roda.wui.common.client.HistoryResolver; -import org.roda.wui.common.client.tools.HistoryUtils; -import org.roda.wui.common.client.tools.ListUtils; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.uibinder.client.UiBinder; -import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.uibinder.client.UiHandler; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.Button; -import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.Widget; - -import config.i18n.client.ClientMessages; - -/** - * @author Gabriel Barros - */ -public class CreateAccessKey extends Composite { - public static final HistoryResolver RESOLVER = new HistoryResolver() { - - @Override - public void resolve(List historyTokens, AsyncCallback callback) { - if (historyTokens.size() == 1) { - Services services = new Services("Get User", "get"); - services.membersResource(s -> s.getUser(historyTokens.get(0))).whenComplete((user, error) -> { - if (user != null) { - CreateAccessKey createAccessKey = new CreateAccessKey(user); - callback.onSuccess(createAccessKey); - } - }); - } else { - CreateAccessKey createAccessKey = new CreateAccessKey(null); - callback.onSuccess(createAccessKey); - } - } - - @Override - public void isCurrentUserPermitted(AsyncCallback callback) { - UserLogin.getInstance().checkRoles(new HistoryResolver[] {MemberManagement.RESOLVER}, false, callback); - } - - @Override - public String getHistoryToken() { - return "create_access_key"; - } - - @Override - public List getHistoryPath() { - return ListUtils.concat(MemberManagement.RESOLVER.getHistoryPath(), getHistoryToken()); - } - }; - - interface MyUiBinder extends UiBinder { - } - - private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); - - private static final ClientMessages messages = GWT.create(ClientMessages.class); - - private AccessKey accessKey; - private User user; - - @UiField - TitlePanel titlePanel; - - @UiField - Button buttonSave; - - @UiField - Button buttonCancel; - - @UiField(provided = true) - AccessKeyDataPanel accessKeyDataPanel; - - public CreateAccessKey(User user) { - this.user = user; - this.accessKey = new AccessKey(); - this.accessKey.setUserName(user.getName()); - this.accessKeyDataPanel = new AccessKeyDataPanel(accessKey, false); - this.accessKeyDataPanel.setAccessKey(accessKey); - - initWidget(uiBinder.createAndBindUi(this)); - titlePanel.setText(user.getName()); - } - - @UiHandler("buttonSave") - void buttonApplyHandler(ClickEvent e) { - if (accessKeyDataPanel.isValid()) { - AccessKey accessKeyUpdated = accessKeyDataPanel.getAccessKey(); - accessKey.setName(accessKeyUpdated.getName()); - accessKey.setExpirationDate(accessKeyUpdated.getExpirationDate()); - Services services = new Services("Create access key", "create"); - CreateAccessKeyRequest createAccessKeyRequest = new CreateAccessKeyRequest(this.accessKey.getName(), - this.accessKey.getExpirationDate()); - services.membersResource(s -> s.createAccessKey(user.getId(), createAccessKeyRequest)) - .whenComplete((response, error) -> { - if (response != null) { - AccessKeyDialogs.showAccessKeyDialog(messages.accessKeyLabel(), response, new NoAsyncCallback() { - @Override - public void onSuccess(Boolean result) { - HistoryUtils.newHistory(ShowUser.RESOLVER, response.getUserName()); - } - }); - } - }); - } - } - - @UiHandler("buttonCancel") - void buttonCancelHandler(ClickEvent e) { - cancel(); - } - - private void cancel() { - HistoryUtils.newHistory(EditUser.RESOLVER, accessKey.getUserName()); - } -} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/CreateAccessKey.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/CreateAccessKey.ui.xml deleted file mode 100644 index 00822f9082..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/CreateAccessKey.ui.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/ShowAccessKey.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/ShowAccessKey.java deleted file mode 100644 index 661b5b6bfb..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/ShowAccessKey.java +++ /dev/null @@ -1,284 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE file at the root of the source - * tree and available online at - * - * https://github.com/keeps/roda - */ -package org.roda.wui.client.management.access; - -import java.util.Date; -import java.util.List; - -import org.roda.core.data.v2.accessKey.AccessKey; -import org.roda.core.data.v2.accessKey.CreateAccessKeyRequest; -import org.roda.wui.client.common.NoAsyncCallback; -import org.roda.wui.client.common.TitlePanel; -import org.roda.wui.client.common.UserLogin; -import org.roda.wui.client.common.dialogs.AccessKeyDialogs; -import org.roda.wui.client.common.dialogs.Dialogs; -import org.roda.wui.client.common.utils.HtmlSnippetUtils; -import org.roda.wui.client.management.MemberManagement; -import org.roda.wui.client.services.Services; -import org.roda.wui.common.client.HistoryResolver; -import org.roda.wui.common.client.tools.Humanize; -import org.roda.wui.common.client.tools.ListUtils; -import org.roda.wui.common.client.tools.StringUtils; -import org.roda.wui.common.client.widgets.Toast; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.safehtml.shared.SafeHtmlUtils; -import com.google.gwt.uibinder.client.UiBinder; -import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.uibinder.client.UiHandler; -import com.google.gwt.user.client.History; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.Button; -import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.Widget; - -import config.i18n.client.ClientMessages; - -/** - * @author Gabriel Barros - */ -public class ShowAccessKey extends Composite { - public static final HistoryResolver RESOLVER = new HistoryResolver() { - - @Override - public void resolve(List historyTokens, AsyncCallback callback) { - if (historyTokens.size() == 1) { - Services services = new Services("Get access key", "get"); - services.membersResource(s -> s.getAccessKey(historyTokens.get(0))).whenComplete((accessKey, error) -> { - if (accessKey != null) { - ShowAccessKey showAccessKey = new ShowAccessKey(accessKey); - callback.onSuccess(showAccessKey); - } - }); - } - } - - @Override - public void isCurrentUserPermitted(AsyncCallback callback) { - UserLogin.getInstance().checkRoles(new HistoryResolver[] {MemberManagement.RESOLVER}, false, callback); - } - - @Override - public List getHistoryPath() { - return ListUtils.concat(MemberManagement.RESOLVER.getHistoryPath(), getHistoryToken()); - } - - @Override - public String getHistoryToken() { - return "show_access_key"; - } - }; - private static final ClientMessages messages = GWT.create(ClientMessages.class); - private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); - @UiField - TitlePanel title; - @UiField - Label dateCreated; - @UiField - Label dateUpdated; - @UiField - HTML nameValue; - @UiField - HTML expirationDateValue; - @UiField - HTML lastUsageValue; - @UiField - HTML statusValue; - @UiField - Button buttonRegenerate; - @UiField - Button buttonRevoke; - @UiField - Button buttonDelete; - @UiField - Button buttonCancel; - private AccessKey accessKey; - - public ShowAccessKey(AccessKey accessKey) { - initWidget(uiBinder.createAndBindUi(this)); - initElements(accessKey); - } - - public void refresh() { - reset(); - Services services = new Services("Get access key", "get"); - services.membersResource(s -> s.getAccessKey(accessKey.getId())).whenComplete((accessKey, error) -> { - if (accessKey != null) { - initElements(accessKey); - } - }); - } - - public void reset() { - dateCreated.setText(""); - dateUpdated.setText(""); - expirationDateValue.setText(""); - lastUsageValue.setText(""); - statusValue.setHTML(SafeHtmlUtils.EMPTY_SAFE_HTML); - nameValue.setHTML(SafeHtmlUtils.EMPTY_SAFE_HTML); - } - - private void initElements(AccessKey accessKey) { - this.accessKey = accessKey; - title.setText(accessKey.getName()); - - if (accessKey.getCreatedOn() != null && StringUtils.isNotBlank(accessKey.getCreatedBy())) { - dateCreated - .setText(messages.dateCreated(Humanize.formatDateTime(accessKey.getCreatedOn()), accessKey.getCreatedBy())); - } - - if (accessKey.getUpdatedOn() != null && StringUtils.isNotBlank(accessKey.getUpdatedBy())) { - dateUpdated - .setText(messages.dateUpdated(Humanize.formatDateTime(accessKey.getUpdatedOn()), accessKey.getUpdatedBy())); - } - - if (accessKey.getExpirationDate() != null) { - expirationDateValue.setText(Humanize.formatDateTime(accessKey.getExpirationDate())); - } - - if (accessKey.getLastUsageDate() != null) { - lastUsageValue.setText(Humanize.formatDateTime(accessKey.getLastUsageDate())); - } else { - lastUsageValue.setText("Never"); - } - - if (accessKey.getStatus() != null) { - statusValue.setHTML(HtmlSnippetUtils.getAccessKeyStateHtml(accessKey)); - } - - nameValue.setHTML(accessKey.getName()); - - initSidebarButtons(accessKey); - } - - private void initSidebarButtons(AccessKey accessKey) { - switch (accessKey.getStatus()) { - case CREATED: - case ACTIVE: - enableButtons(buttonRegenerate, buttonRevoke); - disableButtons(buttonDelete); - break; - case EXPIRED: - enableButtons(buttonRevoke); - disableButtons(buttonRegenerate, buttonDelete); - break; - case REVOKED: - enableButtons(buttonDelete); - disableButtons(buttonRegenerate, buttonRevoke); - break; - case INACTIVE: - enableButtons(buttonRevoke, buttonDelete); - disableButtons(buttonDelete, buttonRegenerate); - break; - default: - enableButtons(buttonRegenerate, buttonRevoke, buttonDelete); - break; - } - } - - private void disableButtons(Button... buttons) { - for (Button button : buttons) { - button.setEnabled(false); - } - } - - private void enableButtons(Button... buttons) { - for (Button button : buttons) { - button.setEnabled(true); - } - } - - @UiHandler("buttonDelete") - void buttonDeleteHandler(ClickEvent e) { - Dialogs.showConfirmDialog(messages.accessKeyLabel(), messages.accessKeyDeleteConfirmationMessage(), - messages.cancelButton(), messages.confirmButton(), new NoAsyncCallback() { - @Override - public void onSuccess(Boolean confirm) { - Services services = new Services("Delete access key", "delete"); - services.membersResource(s -> s.deleteAccessKey(accessKey.getId())).whenComplete((accessKey, error) -> { - if (error == null) { - cancel(); - } - }); - } - }); - } - - @UiHandler("buttonRegenerate") - void buttonRegenerateHandler(ClickEvent e) { - Dialogs.showConfirmDialog(messages.accessKeyLabel(), messages.accessKeyRegenerateConfirmationMessage(), - messages.cancelButton(), messages.confirmButton(), new NoAsyncCallback() { - @Override - public void onSuccess(Boolean confirm) { - if (confirm) { - AccessKeyDialogs.showRegenerateAccessKeyDialog("Regenerate access key", new AsyncCallback() { - @Override - public void onFailure(Throwable caught) { - // do nothing - } - - @Override - public void onSuccess(Date date) { - Services services = new Services("Regenerate access key", "regenerate"); - CreateAccessKeyRequest regenerateAccessKeyRequest = new CreateAccessKeyRequest(); - regenerateAccessKeyRequest.setExpirationDate(date); - services.membersResource(s -> s.regenerateAccessKey(accessKey.getId(), regenerateAccessKeyRequest)) - .whenComplete((response, error) -> { - if (response != null) { - AccessKeyDialogs.showAccessKeyDialog(messages.accessKeyLabel(), response, - new NoAsyncCallback() { - @Override - public void onSuccess(Boolean result) { - refresh(); - Toast.showInfo(messages.accessKeyLabel(), messages.accessKeySuccessfullyRegenerated()); - } - }); - } - }); - } - }); - } - } - }); - } - - @UiHandler("buttonRevoke") - void buttonRevokeHandler(ClickEvent e) { - Dialogs.showConfirmDialog(messages.accessKeyLabel(), messages.accessKeyRevokeConfirmationMessage(), - messages.cancelButton(), messages.confirmButton(), new NoAsyncCallback() { - @Override - public void onSuccess(Boolean confirm) { - if (confirm) { - Services services = new Services("Revoke access key", "revoke"); - services.membersResource(s -> s.revokeAccessKey(accessKey.getId())).whenComplete((response, error) -> { - if (response != null) { - refresh(); - Toast.showInfo(messages.accessKeyLabel(), messages.accessKeySuccessfullyRevoked()); - } - }); - } - } - }); - } - - @UiHandler("buttonCancel") - void buttonCancelHandler(ClickEvent e) { - cancel(); - } - - private void cancel() { - History.back(); - } - - interface MyUiBinder extends UiBinder { - } - -} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/ShowAccessKey.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/ShowAccessKey.ui.xml deleted file mode 100644 index bd9bdf458c..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/access/ShowAccessKey.ui.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/CreateUserPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateUserPanel.java similarity index 53% rename from roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/CreateUserPanel.java rename to roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateUserPanel.java index 78bdb2636b..bdbce4643c 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/CreateUserPanel.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateUserPanel.java @@ -5,7 +5,7 @@ * * https://github.com/keeps/roda */ -package org.roda.wui.client.management; +package org.roda.wui.client.management.members; import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ChangeEvent; @@ -19,7 +19,6 @@ import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HTML; @@ -30,18 +29,14 @@ import config.i18n.client.ClientMessages; import org.roda.core.data.exceptions.AuthorizationDeniedException; import org.roda.core.data.v2.generics.MetadataValue; -import org.roda.core.data.v2.user.Group; import org.roda.core.data.v2.user.User; -import org.roda.wui.client.browse.bundle.UserExtraBundle; import org.roda.wui.client.common.utils.AsyncCallbackUtils; import org.roda.wui.client.common.utils.FormUtilities; +import org.roda.wui.client.services.MembersRestService; import org.roda.wui.client.services.Services; import org.roda.wui.common.client.ClientLogger; -import org.roda.wui.common.client.tools.HistoryUtils; -import org.roda.wui.common.client.widgets.wcag.WCAGUtilities; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Set; @@ -50,118 +45,42 @@ */ public class CreateUserPanel extends Composite implements HasValueChangeHandlers { - interface MyUiBinder extends UiBinder { - } - - private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); - private static final ClientMessages messages = GWT.create(ClientMessages.class); - + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); + @SuppressWarnings("unused") + private final ClientLogger logger = new ClientLogger(getClass().getName()); @UiField TextBox username; - @UiField Label usernameError; - @UiField - TextBox fullname; - + TextBox fullName; @UiField - Label fullnameError; - + Label fullNameError; @UiField TextBox email; - @UiField Label emailError; - @UiField FlowPanel extra; - - @UiField - FlowPanel groupSelectPanel; - - @UiField(provided = true) - GroupSelect groupSelect; - - @UiField - FlowPanel permissionsSelectPanel; - @UiField - PermissionsPanel permissionsPanel; - - @SuppressWarnings("unused") - private ClientLogger logger = new ClientLogger(getClass().getName()); - - private boolean enableGroupSelect; - - private boolean editmode; + HTML errors; // has to be true to detected new field changes private boolean changed = true; - private boolean checked = false; private Set userExtra; - - @UiField - HTML errors; + // Add this field + private Runnable onFormReadyCallback; /** * Create a new user data panel * - * @param editmode - * if user name should be editable - * @param enableGroupSelect - * if the list of groups to which the user belong to should be editable - * */ - public CreateUserPanel(boolean editmode, boolean enableGroupSelect) { - this(true, editmode, enableGroupSelect, true); - } - - /** - * Create a new user data panel - * - * @param editmode - * if user name should be editable - * @param enableGroupSelect - * if the list of groups to which the user belong to should be editable - * - */ - public CreateUserPanel(boolean visible, boolean editmode, boolean enableGroupSelect) { - this(visible, editmode, enableGroupSelect, true); - } - - /** - * Create a new user data panel - * - * @param visible - * @param editmode - * @param enableGroupSelect - * @param enablePermissions - */ - public CreateUserPanel(boolean visible, boolean editmode, boolean enableGroupSelect, boolean enablePermissions) { - - groupSelect = new GroupSelect(); - + public CreateUserPanel() { initWidget(uiBinder.createAndBindUi(this)); - - this.editmode = editmode; - super.setVisible(visible); - this.enableGroupSelect = enableGroupSelect; - + super.setVisible(true); errors.setVisible(false); - groupSelectPanel.setVisible(enableGroupSelect); - permissionsSelectPanel.setVisible(enablePermissions); - - ValueChangeHandler valueChangedHandler = new ValueChangeHandler() { - - @Override - public void onValueChange(ValueChangeEvent event) { - onChange(); - } - }; - ChangeHandler changeHandler = new ChangeHandler() { @Override @@ -182,29 +101,13 @@ public void onKeyUp(KeyUpEvent event) { username.addChangeHandler(changeHandler); username.addKeyUpHandler(keyUpHandler); - fullname.addChangeHandler(changeHandler); - fullname.addKeyUpHandler(keyUpHandler); - - permissionsPanel.addValueChangeHandler(new ValueChangeHandler>() { - - @Override - public void onValueChange(ValueChangeEvent> event) { - onChange(); - } - }); - - groupSelect.addValueChangeHandler(new ValueChangeHandler>() { - - @Override - public void onValueChange(ValueChangeEvent> event) { - updatePermissions(event.getValue()); - onChange(); - } - }); + fullName.addChangeHandler(changeHandler); + fullName.addKeyUpHandler(keyUpHandler); usernameError.setVisible(false); - fullnameError.setVisible(false); + fullNameError.setVisible(false); emailError.setVisible(false); + setUser(new User()); } @SuppressWarnings("unused") @@ -231,135 +134,58 @@ private int setSelected(ListBox listbox, String text) { } /** - * Set user information of user + * Get user defined by this panel. This panel defines: name, fullname, title, + * organization name, postal address, postal code, locality, country, email, + * phone number, fax and which groups this user belongs to. * - * @param user + * @return the user modified by this panel */ - public void setUser(User user) { + public User getUser() { + User user = new User(); + user.setId(username.getText()); + user.setName(username.getText()); + user.setFullName(fullName.getText()); + user.setEmail(email.getText()); + + return user; + } + + private void setUser(User user) { this.username.setText(user.getName()); - this.fullname.setText(user.getFullName()); + this.fullName.setText(user.getFullName()); this.email.setText(user.getEmail()); - this.setMemberGroups(user.getGroups()); - this.setPermissions(user.getDirectRoles(), user.getAllRoles()); - Services services = new Services("Get User extra", "get"); - services.membersResource(s -> s.getDefaultUserExtra()).whenComplete((extra, error) -> { + services.membersResource(MembersRestService::getDefaultUserExtra).whenComplete((extra, error) -> { if (extra != null) { CreateUserPanel.this.userExtra = extra.getExtraFormFields(); createForm(userExtra); + + // ADD THIS: Trigger the callback to recenter the dialog + if (onFormReadyCallback != null) { + onFormReadyCallback.run(); + } } else if (error != null) { if (error instanceof AuthorizationDeniedException) { - // TODO inform user he does not have permissions to see to which - // groups he belongs to. GWT.log("No permissions: " + error.getMessage()); } else { AsyncCallbackUtils.defaultFailureTreatment(error); } - } - }); - } - - private void setPermissions(final Set directRoles, final Set allRoles) { - permissionsPanel.init(new AsyncCallback() { - - @Override - public void onSuccess(Boolean result) { - Set indirectRoles = new HashSet<>(allRoles); - indirectRoles.removeAll(directRoles); - - permissionsPanel.checkPermissions(directRoles, false); - permissionsPanel.checkPermissions(indirectRoles, true); - WCAGUtilities.getInstance().makeAccessible(permissionsSelectPanel.getElement()); - } - - @Override - public void onFailure(Throwable caught) { - if (caught instanceof AuthorizationDeniedException) { - // TODO inform user he does not have permissions to see to which - // groups he belongs to. - } else { - AsyncCallbackUtils.defaultFailureTreatment(caught); - HistoryUtils.newHistory(MemberManagement.RESOLVER); + // ADD THIS: It's good practice to also recenter on error + // in case error messages alter the panel's dimensions + if (onFormReadyCallback != null) { + onFormReadyCallback.run(); } } }); } - private void updatePermissions(List groups) { - permissionsPanel.clear(); - permissionsPanel.checkPermissions(new HashSet<>(permissionsPanel.getUserSelections()), false); - for (Group group : groups) { - permissionsPanel.checkPermissions(group.getAllRoles(), true); - } - } - - /** - * Get user defined by this panel. This panel defines: name, fullname, title, - * organization name, postal address, postal code, locality, country, email, - * phone number, fax and which groups this user belongs to. - * - * @return the user modified by this panel - */ - public User getUser() { - User user = new User(); - user.setId(username.getText()); - user.setName(username.getText()); - user.setFullName(fullname.getText()); - user.setEmail(email.getText()); - - if (enableGroupSelect) { - user.setGroups(this.getMemberGroups()); - } - - user.setDirectRoles(permissionsPanel.getDirectRoles()); - return user; - } - public void createForm(Set userExtra) { extra.clear(); FormUtilities.create(extra, userExtra, false); } - /** - * Set the groups of which this user is member of - * - * @param groups - */ - public void setMemberGroups(final Set groups) { - if (enableGroupSelect) { - groupSelect.init(new AsyncCallback() { - - @Override - public void onSuccess(Boolean result) { - groupSelect.setMemberGroups(groups); - WCAGUtilities.getInstance().makeAccessible(groupSelectPanel.getElement()); - } - - @Override - public void onFailure(Throwable caught) { - if (caught instanceof AuthorizationDeniedException) { - // TODO inform user he does not have permissions to see to which - // groups he belongs to. - } else { - AsyncCallbackUtils.defaultFailureTreatment(caught); - } - } - }); - } - } - - /** - * Get the groups of which this user is member of - * - * @return a list of group names - */ - public Set getMemberGroups() { - return enableGroupSelect ? groupSelect.getMemberGroups() : null; - } - - /** * Is user data panel valid * @@ -378,17 +204,17 @@ public boolean isValid() { usernameError.setVisible(false); } - if (fullname.getText().length() == 0) { - fullname.addStyleName("isWrong"); - fullnameError.setText(messages.mandatoryField()); - fullnameError.setVisible(true); + if (fullName.getText().length() == 0) { + fullName.addStyleName("isWrong"); + fullNameError.setText(messages.mandatoryField()); + fullNameError.setVisible(true); if (errorList.isEmpty()) { - Window.scrollTo(fullname.getAbsoluteLeft(), fullname.getAbsoluteTop()); + Window.scrollTo(fullName.getAbsoluteLeft(), fullName.getAbsoluteTop()); } errorList.add(messages.isAMandatoryField(messages.fullname())); } else { - fullname.removeStyleName("isWrong"); - fullnameError.setVisible(false); + fullName.removeStyleName("isWrong"); + fullNameError.setVisible(false); } if (email.getText() == null || "".equals(email.getText().trim())) { @@ -415,7 +241,6 @@ public boolean isValid() { List extraErrors = FormUtilities.validate(userExtra, extra); errorList.addAll(extraErrors); - checked = true; if (!errorList.isEmpty()) { errors.setVisible(true); @@ -431,47 +256,17 @@ public boolean isValid() { return errorList.isEmpty() ? true : false; } - /** - * Is user name read only - * - * @return true if read only - */ - public boolean isUsernameReadOnly() { - return username.isReadOnly(); - } - - /** - * Set user name read only - * - * @param readonly - */ - public void setUsernameReadOnly(boolean readonly) { - username.setReadOnly(readonly); - } - @Override public void setVisible(boolean visible) { super.setVisible(visible); - if (enableGroupSelect) { - groupSelect.setVisible(visible); - } } public void clear() { username.setText(""); - fullname.setText(""); + fullName.setText(""); email.setText(""); } - /** - * Is user data panel editable, i.e. on create user mode - * - * @return true if editable - */ - public boolean isEditmode() { - return editmode; - } - /** * Is user data panel has been changed * @@ -488,12 +283,13 @@ public HandlerRegistration addValueChangeHandler(ValueChangeHandler handle protected void onChange() { changed = true; - if (checked) { - isValid(); - } ValueChangeEvent.fire(this, getValue()); } + public void setOnFormReadyCallback(Runnable callback) { + this.onFormReadyCallback = callback; + } + public User getValue() { return getUser(); } @@ -502,9 +298,6 @@ public Set getUserExtra() { return userExtra; } - public void setUserExtra(Set userExtra) { - this.userExtra = userExtra; - createForm(userExtra); + interface MyUiBinder extends UiBinder { } } - diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateUserPanel.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateUserPanel.ui.xml new file mode 100644 index 0000000000..c2894fe828 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateUserPanel.ui.xml @@ -0,0 +1,49 @@ + + + + + + + + .section { + margin-top: 20px; + } + + + + + + + + + + + + * + + + + + + + * + + + + + + + * + + + + + + + + + + + + diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/GroupDataPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/GroupDataPanel.java new file mode 100644 index 0000000000..3e93dc3c79 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/GroupDataPanel.java @@ -0,0 +1,194 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE file at the root of the source + * tree and available online at + * + * https://github.com/keeps/roda + */ +/** + * + */ +package org.roda.wui.client.management.members; + +import org.roda.core.data.v2.user.Group; +import org.roda.wui.common.client.ClientLogger; +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.ChangeEvent; +import com.google.gwt.event.dom.client.ChangeHandler; +import com.google.gwt.event.dom.client.KeyUpEvent; +import com.google.gwt.event.dom.client.KeyUpHandler; +import com.google.gwt.event.logical.shared.HasValueChangeHandlers; +import com.google.gwt.event.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.TextBox; +import com.google.gwt.user.client.ui.Widget; + +/** + * @author Luis Faria + * + */ +public class GroupDataPanel extends Composite implements HasValueChangeHandlers { + + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); + @UiField + TextBox groupName; + @UiField + TextBox fullName; + + private Group group = new Group(); + @SuppressWarnings("unused") + private final ClientLogger logger = new ClientLogger(getClass().getName()); + + private final boolean editMode; + private boolean changed = false; + private boolean checked = false; + /** + * Create a new group data panel + * + * @param editMode + * if group name should be editable + */ + public GroupDataPanel(boolean editMode) { + initWidget(uiBinder.createAndBindUi(this)); + + this.editMode = editMode; + super.setVisible(true); + + ChangeHandler changeHandler = new ChangeHandler() { + + @Override + public void onChange(ChangeEvent event) { + GroupDataPanel.this.onChange(); + } + }; + + KeyUpHandler keyUpHandler = new KeyUpHandler() { + + @Override + public void onKeyUp(KeyUpEvent event) { + onChange(); + } + }; + + groupName.setEnabled(!isEditMode()); + groupName.addKeyDownHandler(new UserAndGroupKeyDownHandler()); + groupName.addChangeHandler(changeHandler); + groupName.addKeyUpHandler(keyUpHandler); + fullName.addChangeHandler(changeHandler); + fullName.addKeyUpHandler(keyUpHandler); + } + + /** + * Get group defined by this panel. This panel defines: name, fullname + * + * @return the group modified by this panel + */ + public Group getGroup() { + group.setId(groupName.getText()); + group.setName(groupName.getText()); + group.setFullName(fullName.getText()); + return group; + } + + /** + * Set group information of group + * + * @param group + */ + public void setGroup(Group group) { + this.group = group; + this.groupName.setText(group.getName()); + this.fullName.setText(group.getFullName()); + } + + /** + * Is group data panel valid + * + * @return true if valid + */ + public boolean isValid() { + boolean valid = true; + + if (groupName.getText().isEmpty()) { + valid = false; + groupName.addStyleName("isWrong"); + } else { + groupName.removeStyleName("isWrong"); + } + + if (fullName.getText().isEmpty()) { + valid = false; + fullName.addStyleName("isWrong"); + } else { + fullName.removeStyleName("isWrong"); + } + + checked = true; + return valid; + } + + /** + * Is group name read only + * + * @return true if read only + */ + public boolean isGroupNameReadOnly() { + return groupName.isReadOnly(); + } + + /** + * Set group name read only + * + * @param readonly + */ + public void setGroupNameReadOnly(boolean readonly) { + groupName.setReadOnly(readonly); + } + + public void clear() { + groupName.setText(""); + fullName.setText(""); + } + + /** + * Is group data panel editable, i.e. on create group mode + * + * @return true if editable + */ + public boolean isEditMode() { + return editMode; + } + + /** + * Is group data panel has been changed + * + * @return changed + */ + public boolean isChanged() { + return changed; + } + + @Override + public HandlerRegistration addValueChangeHandler(ValueChangeHandler handler) { + return addHandler(handler, ValueChangeEvent.getType()); + } + + protected void onChange() { + changed = true; + if (checked) { + isValid(); + } + ValueChangeEvent.fire(this, getValue()); + } + + public Group getValue() { + return getGroup(); + } + + interface MyUiBinder extends UiBinder { + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/GroupDataPanel.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/GroupDataPanel.ui.xml similarity index 51% rename from roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/GroupDataPanel.ui.xml rename to roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/GroupDataPanel.ui.xml index 759655ad52..707256479c 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/GroupDataPanel.ui.xml +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/GroupDataPanel.ui.xml @@ -1,7 +1,7 @@ + xmlns:u="urn:import:org.roda.wui.client.management.members"> @@ -17,26 +17,11 @@ * - + * - - - - - - - - - - - - - - - - + diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/GroupSelect.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/GroupSelect.java similarity index 99% rename from roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/GroupSelect.java rename to roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/GroupSelect.java index 2bc1873912..9c1b757a75 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/GroupSelect.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/GroupSelect.java @@ -5,7 +5,7 @@ * * https://github.com/keeps/roda */ -package org.roda.wui.client.management; +package org.roda.wui.client.management.members; import java.util.HashMap; import java.util.HashSet; diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/MemberManagement.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/MemberManagement.java similarity index 68% rename from roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/MemberManagement.java rename to roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/MemberManagement.java index 5bf2f5bd40..5d08ebe4b1 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/MemberManagement.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/MemberManagement.java @@ -5,7 +5,7 @@ * * https://github.com/keeps/roda */ -package org.roda.wui.client.management; +package org.roda.wui.client.management.members; import java.util.List; @@ -16,8 +16,7 @@ import org.roda.wui.client.common.lists.utils.AsyncTableCellOptions; import org.roda.wui.client.common.lists.utils.ListBuilder; import org.roda.wui.client.common.search.SearchWrapper; -import org.roda.wui.client.management.access.CreateAccessKey; -import org.roda.wui.client.management.access.ShowAccessKey; +import org.roda.wui.client.management.Management; import org.roda.wui.common.client.HistoryResolver; import org.roda.wui.common.client.tools.HistoryUtils; import org.roda.wui.common.client.tools.ListUtils; @@ -35,8 +34,8 @@ public class MemberManagement extends Composite { private static final ClientMessages messages = GWT.create(ClientMessages.class); - - public static final HistoryResolver RESOLVER = new HistoryResolver() { + private static MemberManagement instance = null; + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); public static final HistoryResolver RESOLVER = new HistoryResolver() { @Override public void resolve(List historyTokens, AsyncCallback callback) { @@ -58,34 +57,10 @@ public String getHistoryToken() { return "user"; } }; - - private static MemberManagement instance = null; - - /** - * Get the singleton instance - * - * @return the instance - */ - public static MemberManagement getInstance() { - if (instance == null) { - instance = new MemberManagement(); - } else { - instance.refresh(); - } - return instance; - } - - interface MyUiBinder extends UiBinder { - } - - private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); - @UiField FlowPanel memberManagementDescription; - @UiField(provided = true) SearchWrapper searchWrapper; - public MemberManagement() { ListBuilder rodaMemberListBuilder = new ListBuilder<>(() -> new RodaMemberList(), @@ -100,6 +75,20 @@ public MemberManagement() { memberManagementDescription.add(new HTMLWidgetWrapper("MemberManagementDescription.html")); } + /** + * Get the singleton instance + * + * @return the instance + */ + public static MemberManagement getInstance() { + if (instance == null) { + instance = new MemberManagement(); + } else { + instance.refresh(); + } + return instance; + } + private void refresh() { searchWrapper.refreshCurrentList(); } @@ -108,27 +97,11 @@ public void resolve(List historyTokens, AsyncCallback callback) if (historyTokens.isEmpty()) { callback.onSuccess(this); } else if (historyTokens.size() == 1) { - if (historyTokens.get(0).equals(CreateUser.RESOLVER.getHistoryToken())) { - CreateUser.RESOLVER.resolve(HistoryUtils.tail(historyTokens), callback); - } else if (historyTokens.get(0).equals(CreateGroup.RESOLVER.getHistoryToken())) { - CreateGroup.RESOLVER.resolve(HistoryUtils.tail(historyTokens), callback); - } else { - HistoryUtils.newHistory(RESOLVER); - callback.onSuccess(null); - } + HistoryUtils.newHistory(RESOLVER); + callback.onSuccess(null); } else if (historyTokens.size() == 2) { - if (historyTokens.get(0).equals(ShowUser.RESOLVER.getHistoryToken())) { - ShowUser.RESOLVER.resolve(HistoryUtils.tail(historyTokens), callback); - } else if (historyTokens.get(0).equals(EditUser.RESOLVER.getHistoryToken())) { - EditUser.RESOLVER.resolve(HistoryUtils.tail(historyTokens), callback); - } else if (historyTokens.get(0).equals(ShowGroup.RESOLVER.getHistoryToken())) { - ShowGroup.RESOLVER.resolve(HistoryUtils.tail(historyTokens), callback); - } else if (historyTokens.get(0).equals(EditGroup.RESOLVER.getHistoryToken())) { - EditGroup.RESOLVER.resolve(HistoryUtils.tail(historyTokens), callback); - } else if (historyTokens.get(0).equals(ShowAccessKey.RESOLVER.getHistoryToken())) { - ShowAccessKey.RESOLVER.resolve(HistoryUtils.tail(historyTokens), callback); - } else if (historyTokens.get(0).equals(CreateAccessKey.RESOLVER.getHistoryToken())) { - CreateAccessKey.RESOLVER.resolve(HistoryUtils.tail(historyTokens), callback); + if (historyTokens.get(0).equals(ShowMember.RESOLVER.getHistoryToken())) { + ShowMember.RESOLVER.resolve(HistoryUtils.tail(historyTokens), callback); } else { HistoryUtils.newHistory(RESOLVER); callback.onSuccess(null); @@ -138,4 +111,10 @@ public void resolve(List historyTokens, AsyncCallback callback) callback.onSuccess(null); } } + + interface MyUiBinder extends UiBinder { + } + + + } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/MemberManagement.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/MemberManagement.ui.xml similarity index 100% rename from roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/MemberManagement.ui.xml rename to roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/MemberManagement.ui.xml diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/PasswordPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/PasswordPanel.java similarity index 99% rename from roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/PasswordPanel.java rename to roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/PasswordPanel.java index 8bef419043..bdcc4117c7 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/PasswordPanel.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/PasswordPanel.java @@ -5,7 +5,7 @@ * * https://github.com/keeps/roda */ -package org.roda.wui.client.management; +package org.roda.wui.client.management.members; import org.roda.wui.common.client.tools.StringUtils; diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/PermissionsPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/PermissionsPanel.java similarity index 99% rename from roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/PermissionsPanel.java rename to roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/PermissionsPanel.java index ab7baca98b..4ec6467a41 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/PermissionsPanel.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/PermissionsPanel.java @@ -8,7 +8,7 @@ /** * */ -package org.roda.wui.client.management; +package org.roda.wui.client.management.members; import java.util.ArrayList; import java.util.HashSet; diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/Profile.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/Profile.java similarity index 97% rename from roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/Profile.java rename to roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/Profile.java index e1d4f0233c..e2247d65bb 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/Profile.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/Profile.java @@ -8,7 +8,7 @@ /** * */ -package org.roda.wui.client.management; +package org.roda.wui.client.management.members; import java.util.Arrays; import java.util.List; @@ -117,7 +117,7 @@ interface MyUiBinder extends UiBinder { public Profile(User user) { this.user = user; - this.userDataPanel = new UserDataPanel(true, true, false, false); + this.userDataPanel = new UserDataPanel(true, true); this.userDataPanel.setUser(user); initWidget(uiBinder.createAndBindUi(this)); diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/Profile.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/Profile.ui.xml similarity index 91% rename from roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/Profile.ui.xml rename to roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/Profile.ui.xml index 69dc9227ae..79374a3543 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/Profile.ui.xml +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/Profile.ui.xml @@ -1,7 +1,7 @@ + xmlns:u="urn:import:org.roda.wui.client.management.members" xmlns:common="urn:import:org.roda.wui.client.common"> diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/RecoverLogin.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/RecoverLogin.java similarity index 99% rename from roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/RecoverLogin.java rename to roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/RecoverLogin.java index 8bfb0b8936..4407b0898b 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/RecoverLogin.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/RecoverLogin.java @@ -8,7 +8,7 @@ /** * */ -package org.roda.wui.client.management; +package org.roda.wui.client.management.members; import java.util.Arrays; import java.util.List; diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/RecoverLogin.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/RecoverLogin.ui.xml similarity index 100% rename from roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/RecoverLogin.ui.xml rename to roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/RecoverLogin.ui.xml diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/Register.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/Register.java similarity index 98% rename from roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/Register.java rename to roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/Register.java index c9fd401005..89598b961b 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/Register.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/Register.java @@ -8,7 +8,7 @@ /** * */ -package org.roda.wui.client.management; +package org.roda.wui.client.management.members; import java.util.Arrays; import java.util.List; @@ -123,7 +123,7 @@ interface MyUiBinder extends UiBinder { * the user to edit */ public Register() { - this.userDataPanel = new UserDataPanel(true, false, false, false); + this.userDataPanel = new UserDataPanel(true, false); Services services = new Services("Get User extra", "get"); services.membersResource(s -> s.getDefaultUserExtra()).whenComplete((userExtra, error) -> { if (userExtra != null) { diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/Register.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/Register.ui.xml similarity index 92% rename from roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/Register.ui.xml rename to roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/Register.ui.xml index 317b4e9ea0..e09094e672 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/Register.ui.xml +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/Register.ui.xml @@ -1,7 +1,7 @@ + xmlns:u="urn:import:org.roda.wui.client.management.members" xmlns:common="urn:import:org.roda.wui.client.common"> diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ResetPassword.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/ResetPassword.java similarity index 72% rename from roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ResetPassword.java rename to roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/ResetPassword.java index a888dacb66..edca3c0435 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ResetPassword.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/ResetPassword.java @@ -8,40 +8,20 @@ /** * */ -package org.roda.wui.client.management; +package org.roda.wui.client.management.members; import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.dom.client.KeyPressEvent; -import com.google.gwt.event.dom.client.KeyUpEvent; -import com.google.gwt.event.dom.client.KeyUpHandler; -import com.google.gwt.event.logical.shared.AttachEvent; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.uibinder.client.UiHandler; import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.Widget; -import config.i18n.client.ClientMessages; -import org.roda.core.data.common.SecureString; -import org.roda.core.data.exceptions.InvalidTokenException; -import org.roda.core.data.exceptions.NotFoundException; import org.roda.core.data.v2.user.User; import org.roda.wui.client.common.UserLogin; -import org.roda.wui.client.common.dialogs.Dialogs; import org.roda.wui.client.main.Login; -import org.roda.wui.client.services.Services; import org.roda.wui.client.welcome.Welcome; -import org.roda.wui.common.client.ClientLogger; import org.roda.wui.common.client.HistoryResolver; import org.roda.wui.common.client.tools.HistoryUtils; -import org.roda.wui.common.client.widgets.Toast; import java.util.Arrays; import java.util.List; diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ResetPassword.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/ResetPassword.ui.xml similarity index 83% rename from roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ResetPassword.ui.xml rename to roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/ResetPassword.ui.xml index aea22e21a3..7e780688aa 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ResetPassword.ui.xml +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/ResetPassword.ui.xml @@ -2,7 +2,7 @@ + xmlns:u="urn:import:org.roda.wui.client.management.members"> diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/SetPassword.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/SetPassword.java similarity index 98% rename from roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/SetPassword.java rename to roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/SetPassword.java index 12aa7546b5..8b35820bcc 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/SetPassword.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/SetPassword.java @@ -8,7 +8,7 @@ /** * */ -package org.roda.wui.client.management; +package org.roda.wui.client.management.members; import com.google.gwt.core.client.GWT; import com.google.gwt.uibinder.client.UiBinder; diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/SetPassword.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/SetPassword.ui.xml similarity index 83% rename from roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/SetPassword.ui.xml rename to roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/SetPassword.ui.xml index a21e3694b7..b411202bf3 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/SetPassword.ui.xml +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/SetPassword.ui.xml @@ -2,7 +2,7 @@ + xmlns:u="urn:import:org.roda.wui.client.management.members"> diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/ShowMember.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/ShowMember.java new file mode 100644 index 0000000000..d286ebec35 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/ShowMember.java @@ -0,0 +1,167 @@ +package org.roda.wui.client.management.members; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.google.gwt.user.client.ui.FocusPanel; +import org.roda.core.data.v2.user.RODAMember; +import org.roda.wui.client.browse.tabs.RODAMemberTabs; +import org.roda.wui.client.common.NavigationToolbar; +import org.roda.wui.client.common.NoAsyncCallback; +import org.roda.wui.client.common.TitlePanel; +import org.roda.wui.client.common.RODAMemberActionsToolbar; +import org.roda.wui.client.common.UserLogin; +import org.roda.wui.client.common.actions.Actionable; +import org.roda.wui.client.main.BreadcrumbUtils; +import org.roda.wui.client.services.Services; +import org.roda.wui.common.client.HistoryResolver; +import org.roda.wui.common.client.tools.HistoryUtils; +import org.roda.wui.common.client.tools.ListUtils; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; + +import config.i18n.client.ClientMessages; +import org.roda.wui.common.client.tools.StringUtils; + +/** + * @author Gabriel Barros + */ +public class ShowMember extends Composite { + public static final HistoryResolver RESOLVER = new HistoryResolver() { + + @Override + public void resolve(List historyTokens, final AsyncCallback callback) { + if (historyTokens.size() == 1) { + String username = historyTokens.get(0); + Services services = new Services("Get User", "get"); + services.membersResource(s -> s.getUser(username)).whenComplete((member, error) -> { + if (member != null) { + ShowMember showMember = new ShowMember(member); + callback.onSuccess(showMember); + } else if (error != null) { + callback.onFailure(error); + } + }); + } else { + HistoryUtils.newHistory(MemberManagement.RESOLVER); + callback.onSuccess(null); + } + } + + @Override + public void isCurrentUserPermitted(AsyncCallback callback) { + UserLogin.getInstance().checkRoles(new HistoryResolver[] {MemberManagement.RESOLVER}, false, callback); + } + + @Override + public List getHistoryPath() { + return ListUtils.concat(MemberManagement.RESOLVER.getHistoryPath(), getHistoryToken()); + } + + @Override + public String getHistoryToken() { + return "show_member"; + } + }; + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); + + @UiField + FocusPanel keyboardFocus; + + @UiField + NavigationToolbar navigationToolbar; + + @UiField + RODAMemberActionsToolbar actionsToolbar; + + @UiField + TitlePanel title; + + @UiField + RODAMemberTabs browseTab; + + private Map handlers = new HashMap<>(); + private AsyncCallback handler = new NoAsyncCallback() { + @Override + public void onSuccess(Actionable.ActionImpact result) { + if (handlers.containsKey(result)) { + handlers.get(result).run(); + } + } + }; + + public ShowMember(RODAMember member) { + initWidget(uiBinder.createAndBindUi(this)); + + initHandlers(member); + + updateView(member); + + keyboardFocus.setFocus(true); + keyboardFocus.addStyleName("browse"); + } + + private void updateView(RODAMember member) { + navigationToolbar.withoutButtons().build(); + navigationToolbar.updateBreadcrumbPath(BreadcrumbUtils.getRODAMemberBreadcrumbs(member)); + + if (member.isUser()) { + actionsToolbar.setLabel(messages.showUserTitle()); + title.setIconClass("User"); + } else { + actionsToolbar.setLabel(messages.showGroupTitle()); + title.setIconClass("Group"); + } + + actionsToolbar.setObjectAndBuild(member, null, handler); + + title.setText(StringUtils.isBlank(member.getFullName()) ? member.getId() : member.getFullName()); + + browseTab.init(member, handler); + } + + private void initHandlers(RODAMember member) { + handlers.put(Actionable.ActionImpact.DESTROYED, + () -> HistoryUtils.newHistory(MemberManagement.RESOLVER.getHistoryPath())); + + // Change this to use the DOM Swap refresh method instead of HistoryUtils + handlers.put(Actionable.ActionImpact.UPDATED, () -> refreshToolbar(member.getUUID(), member.isUser())); + } + + private void refreshToolbar(String id, boolean isUser) { + Services services = new Services("Get Updated Member", "get"); + + if (isUser) { + services.membersResource(s -> s.getUser(id)).whenComplete((updatedUser, error) -> { + if (updatedUser != null) { + updateShowUserUI(updatedUser); + } + }); + } else { + services.membersResource(s -> s.getGroup(id)).whenComplete((updatedGroup, error) -> { + if (updatedGroup != null) { + updateShowUserUI(updatedGroup); + } + }); + } + } + + private void updateShowUserUI(RODAMember updatedMember) { + title + .setText(StringUtils.isBlank(updatedMember.getFullName()) ? updatedMember.getId() : updatedMember.getFullName()); + + actionsToolbar.setObjectAndBuild(updatedMember, null, handler); + + browseTab.init(updatedMember, handler); + } + + interface MyUiBinder extends UiBinder { + } +} \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/ShowMember.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/ShowMember.ui.xml new file mode 100644 index 0000000000..d1d99d32df --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/ShowMember.ui.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/UpdatePasswordPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/UpdatePasswordPanel.java similarity index 99% rename from roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/UpdatePasswordPanel.java rename to roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/UpdatePasswordPanel.java index b5f55ae0ee..cc85d7bf2d 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/UpdatePasswordPanel.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/UpdatePasswordPanel.java @@ -5,7 +5,7 @@ * * https://github.com/keeps/roda */ -package org.roda.wui.client.management; +package org.roda.wui.client.management.members; import org.roda.core.data.common.SecureString; import org.roda.core.data.exceptions.InvalidTokenException; diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/UpdatePasswordPanel.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/UpdatePasswordPanel.ui.xml similarity index 100% rename from roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/UpdatePasswordPanel.ui.xml rename to roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/UpdatePasswordPanel.ui.xml diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/UserAndGroupKeyDownHandler.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/UserAndGroupKeyDownHandler.java similarity index 96% rename from roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/UserAndGroupKeyDownHandler.java rename to roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/UserAndGroupKeyDownHandler.java index c474a049ef..6484841e01 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/UserAndGroupKeyDownHandler.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/UserAndGroupKeyDownHandler.java @@ -5,7 +5,7 @@ * * https://github.com/keeps/roda */ -package org.roda.wui.client.management; +package org.roda.wui.client.management.members; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.dom.client.KeyDownEvent; diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/UserDataPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/UserDataPanel.java similarity index 61% rename from roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/UserDataPanel.java rename to roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/UserDataPanel.java index a1afa3bf82..2c4a9bff0c 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/UserDataPanel.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/UserDataPanel.java @@ -8,22 +8,16 @@ /** * */ -package org.roda.wui.client.management; +package org.roda.wui.client.management.members; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Set; -import org.roda.core.data.exceptions.AuthorizationDeniedException; import org.roda.core.data.v2.generics.MetadataValue; -import org.roda.core.data.v2.user.Group; import org.roda.core.data.v2.user.User; -import org.roda.wui.client.common.utils.AsyncCallbackUtils; import org.roda.wui.client.common.utils.FormUtilities; import org.roda.wui.common.client.ClientLogger; -import org.roda.wui.common.client.tools.HistoryUtils; -import org.roda.wui.common.client.widgets.wcag.WCAGUtilities; import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ChangeEvent; @@ -37,7 +31,6 @@ import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HTML; @@ -54,117 +47,52 @@ */ public class UserDataPanel extends Composite implements HasValueChangeHandlers { - interface MyUiBinder extends UiBinder { - } - - private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); - private static final ClientMessages messages = GWT.create(ClientMessages.class); - + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); + @SuppressWarnings("unused") + private final ClientLogger logger = new ClientLogger(getClass().getName()); + private final boolean editMode; @UiField TextBox username; - @UiField Label usernameError; - @UiField(provided = true) PasswordPanel password; - @UiField Label passwordError; - @UiField - TextBox fullname; - + TextBox fullName; @UiField - Label fullnameError; - + Label fullNameError; @UiField TextBox email; - @UiField Label emailError; - @UiField FlowPanel extra; - - @UiField - FlowPanel groupSelectPanel; - - @UiField(provided = true) - GroupSelect groupSelect; - - @UiField - FlowPanel permissionsSelectPanel; - @UiField - PermissionsPanel permissionsPanel; - - @SuppressWarnings("unused") - private ClientLogger logger = new ClientLogger(getClass().getName()); - - private boolean enableGroupSelect; - - private boolean editmode; - + HTML errors; // has to be true to detected new field changes - private boolean changed = true; + private boolean changed = false; private boolean checked = false; private Set userExtra; - @UiField - HTML errors; - - /** - * Create a new user data panel - * - * @param editmode - * if user name should be editable - * @param enableGroupSelect - * if the list of groups to which the user belong to should be editable - * - */ - public UserDataPanel(boolean editmode, boolean enableGroupSelect) { - this(true, editmode, enableGroupSelect, true); - } - - /** - * Create a new user data panel - * - * @param editmode - * if user name should be editable - * @param enableGroupSelect - * if the list of groups to which the user belong to should be editable - * - */ - public UserDataPanel(boolean visible, boolean editmode, boolean enableGroupSelect) { - this(visible, editmode, enableGroupSelect, true); - } - /** * Create a new user data panel * * @param visible * @param editMode - * @param enableGroupSelect - * @param enablePermissions */ - public UserDataPanel(boolean visible, boolean editMode, boolean enableGroupSelect, boolean enablePermissions) { + public UserDataPanel(boolean visible, boolean editMode) { password = new PasswordPanel(editMode); - groupSelect = new GroupSelect(); - initWidget(uiBinder.createAndBindUi(this)); - this.editmode = editMode; + this.editMode = editMode; super.setVisible(visible); - this.enableGroupSelect = enableGroupSelect; errors.setVisible(false); - groupSelectPanel.setVisible(enableGroupSelect); - permissionsSelectPanel.setVisible(enablePermissions); - ValueChangeHandler valueChangedHandler = new ValueChangeHandler() { @Override @@ -194,29 +122,12 @@ public void onKeyUp(KeyUpEvent event) { username.addChangeHandler(changeHandler); username.addKeyUpHandler(keyUpHandler); password.addValueChangeHandler(valueChangedHandler); - fullname.addChangeHandler(changeHandler); - fullname.addKeyUpHandler(keyUpHandler); - - permissionsPanel.addValueChangeHandler(new ValueChangeHandler>() { - - @Override - public void onValueChange(ValueChangeEvent> event) { - onChange(); - } - }); - - groupSelect.addValueChangeHandler(new ValueChangeHandler>() { - - @Override - public void onValueChange(ValueChangeEvent> event) { - updatePermissions(event.getValue()); - onChange(); - } - }); + fullName.addChangeHandler(changeHandler); + fullName.addKeyUpHandler(keyUpHandler); usernameError.setVisible(false); passwordError.setVisible(false); - fullnameError.setVisible(false); + fullNameError.setVisible(false); emailError.setVisible(false); } @@ -243,59 +154,6 @@ private int setSelected(ListBox listbox, String text) { return index; } - /** - * Set user information of user - * - * @param user - */ - public void setUser(User user) { - this.username.setText(user.getName()); - this.fullname.setText(user.getFullName()); - this.email.setText(user.getEmail()); - this.userExtra = user.getExtra(); - this.setMemberGroups(user.getGroups()); - this.setPermissions(user.getDirectRoles(), user.getAllRoles()); - } - - public void setUserExtra(Set extra) { - UserDataPanel.this.userExtra = extra; - createForm(extra); - } - - private void setPermissions(final Set directRoles, final Set allRoles) { - permissionsPanel.init(new AsyncCallback() { - - @Override - public void onSuccess(Boolean result) { - Set indirectRoles = new HashSet<>(allRoles); - indirectRoles.removeAll(directRoles); - - permissionsPanel.checkPermissions(directRoles, false); - permissionsPanel.checkPermissions(indirectRoles, true); - WCAGUtilities.getInstance().makeAccessible(permissionsSelectPanel.getElement()); - } - - @Override - public void onFailure(Throwable caught) { - if (caught instanceof AuthorizationDeniedException) { - // TODO inform user he does not have permissions to see to which - // groups he belongs to. - } else { - AsyncCallbackUtils.defaultFailureTreatment(caught); - HistoryUtils.newHistory(MemberManagement.RESOLVER); - } - } - }); - } - - private void updatePermissions(List groups) { - permissionsPanel.clear(); - permissionsPanel.checkPermissions(new HashSet(permissionsPanel.getUserSelections()), false); - for (Group group : groups) { - permissionsPanel.checkPermissions(group.getAllRoles(), true); - } - } - /** * Get user defined by this panel. This panel defines: name, fullname, title, * organization name, postal address, postal code, locality, country, email, @@ -307,57 +165,29 @@ public User getUser() { User user = new User(); user.setId(username.getText()); user.setName(username.getText()); - user.setFullName(fullname.getText()); + user.setFullName(fullName.getText()); user.setEmail(email.getText()); - if (enableGroupSelect) { - user.setGroups(this.getMemberGroups()); - } - - user.setDirectRoles(permissionsPanel.getDirectRoles()); return user; } - public void createForm(Set userExtra) { - extra.clear(); - FormUtilities.create(extra, userExtra, false); - } - /** - * Set the groups of which this user is member of + * Set user information of user * - * @param groups + * @param user */ - public void setMemberGroups(final Set groups) { - if (enableGroupSelect) { - groupSelect.init(new AsyncCallback() { - - @Override - public void onSuccess(Boolean result) { - groupSelect.setMemberGroups(groups); - WCAGUtilities.getInstance().makeAccessible(groupSelectPanel.getElement()); - } + public void setUser(User user) { + this.username.setText(user.getName()); + this.fullName.setText(user.getFullName()); + this.email.setText(user.getEmail()); + this.userExtra = user.getExtra(); - @Override - public void onFailure(Throwable caught) { - if (caught instanceof AuthorizationDeniedException) { - // TODO inform user he does not have permissions to see to which - // groups he belongs to. - } else { - AsyncCallbackUtils.defaultFailureTreatment(caught); - } - } - }); - } + this.changed = false; } - /** - * Get the groups of which this user is member of - * - * @return a list of group names - */ - public Set getMemberGroups() { - return enableGroupSelect ? groupSelect.getMemberGroups() : null; + public void createForm(Set userExtra) { + extra.clear(); + FormUtilities.create(extra, userExtra, false); } /** @@ -408,17 +238,17 @@ public boolean isValid() { errorList.add(messages.passwordIsTooSmall()); } - if (fullname.getText().isEmpty()) { - fullname.addStyleName("isWrong"); - fullnameError.setText(messages.mandatoryField()); - fullnameError.setVisible(true); + if (fullName.getText().isEmpty()) { + fullName.addStyleName("isWrong"); + fullNameError.setText(messages.mandatoryField()); + fullNameError.setVisible(true); if (errorList.isEmpty()) { - Window.scrollTo(fullname.getAbsoluteLeft(), fullname.getAbsoluteTop()); + Window.scrollTo(fullName.getAbsoluteLeft(), fullName.getAbsoluteTop()); } errorList.add(messages.isAMandatoryField(messages.fullname())); } else { - fullname.removeStyleName("isWrong"); - fullnameError.setVisible(false); + fullName.removeStyleName("isWrong"); + fullNameError.setVisible(false); } if (email.getText() == null || "".equals(email.getText().trim())) { @@ -483,16 +313,15 @@ public void setUsernameReadOnly(boolean readonly) { @Override public void setVisible(boolean visible) { super.setVisible(visible); - if (enableGroupSelect) { - groupSelect.setVisible(visible); - } } public void clear() { username.setText(""); password.clear(); - fullname.setText(""); + fullName.setText(""); email.setText(""); + + this.changed = false; } /** @@ -500,8 +329,8 @@ public void clear() { * * @return true if editable */ - public boolean isEditmode() { - return editmode; + public boolean isEditMode() { + return editMode; } /** @@ -533,4 +362,12 @@ public User getValue() { public Set getUserExtra() { return userExtra; } + + public void setUserExtra(Set extra) { + UserDataPanel.this.userExtra = extra; + createForm(extra); + } + + interface MyUiBinder extends UiBinder { + } } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/UserDataPanel.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/UserDataPanel.ui.xml similarity index 67% rename from roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/UserDataPanel.ui.xml rename to roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/UserDataPanel.ui.xml index 22a2c2ad9a..49c7ec81b1 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/UserDataPanel.ui.xml +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/UserDataPanel.ui.xml @@ -1,7 +1,7 @@ + xmlns:u="urn:import:org.roda.wui.client.management.members"> @@ -30,8 +30,8 @@ * - - + + * @@ -45,21 +45,5 @@ - - - - - - - - - - - - - - - - diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/UserDetailsPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/UserDetailsPanel.java new file mode 100644 index 0000000000..ec4d000d2d --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/UserDetailsPanel.java @@ -0,0 +1,8 @@ +package org.roda.wui.client.management.members; + +/** + * @author Miguel Guimarães + */ + +public class UserDetailsPanel { +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/AccessKeysTab.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/AccessKeysTab.java new file mode 100644 index 0000000000..a40d57627b --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/AccessKeysTab.java @@ -0,0 +1,83 @@ +package org.roda.wui.client.management.members.tabs; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.Widget; +import config.i18n.client.ClientMessages; +import org.roda.core.data.v2.user.RODAMember; +import org.roda.core.data.v2.user.User; +import org.roda.wui.client.common.ActionsToolbar; +import org.roda.wui.client.common.actions.Actionable; +import org.roda.wui.client.common.actions.RODAMemberAction; +import org.roda.wui.client.common.actions.RODAMemberToolbarActions; +import org.roda.wui.client.common.actions.model.ActionableObject; +import org.roda.wui.client.common.actions.widgets.ActionableWidgetBuilder; +import org.roda.wui.client.management.access.AccessKeyTablePanel; + +import java.util.List; + +/** + * @author Miguel Guimarães + */ + +public class AccessKeysTab extends Composite { + + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); + + @UiField + ActionsToolbar actionsToolbar; + + @UiField + FlowPanel accessKeyTablePanel; + + // Keep a reference to the panel so we can refresh it + private AccessKeyTablePanel tablePanel; + + public AccessKeysTab(RODAMember member, AsyncCallback actionCallback) { + initWidget(uiBinder.createAndBindUi(this)); + + if (member.isUser()) { + User user = (User) member; + tablePanel = new AccessKeyTablePanel(user.getId()); + accessKeyTablePanel.add(tablePanel); + } + + AsyncCallback localCallback = new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + // Pass failures up to the parent handler + actionCallback.onFailure(caught); + } + + @Override + public void onSuccess(Actionable.ActionImpact result) { + if (Actionable.ActionImpact.UPDATED.equals(result)) { + // Refresh the table locally WITHOUT reloading the whole page! + if (tablePanel != null) { + tablePanel.refresh(); + } + } else { + // If it's something else (e.g., DESTROYED), let the parent handle it + actionCallback.onSuccess(result); + } + } + }; + + actionsToolbar.setLabelVisible(false); + actionsToolbar.setTagsVisible(false); + actionsToolbar.setActionableMenu(new ActionableWidgetBuilder(RODAMemberToolbarActions.get()) + .withActionCallback(localCallback).buildGroupedListWithObjects(new ActionableObject<>(member), + List.of(RODAMemberAction.NEW_ACCESS_KEY), List.of(RODAMemberAction.NEW_ACCESS_KEY)), + true); + + } + + interface MyUiBinder extends UiBinder { + Widget createAndBindUi(AccessKeysTab tab); + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/AccessKeysTab.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/AccessKeysTab.ui.xml new file mode 100644 index 0000000000..2246174b56 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/AccessKeysTab.ui.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/PermissionsPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/PermissionsPanel.java new file mode 100644 index 0000000000..3a4d0715a1 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/PermissionsPanel.java @@ -0,0 +1,558 @@ +package org.roda.wui.client.management.members.tabs; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.logical.shared.HasValueChangeHandlers; +import com.google.gwt.event.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.ui.CheckBox; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.Label; +import config.i18n.client.ClientMessages; +import org.roda.core.data.v2.user.Group; +import org.roda.core.data.v2.user.RODAMember; +import org.roda.wui.client.management.members.MemberManagement; +import org.roda.wui.client.services.Services; +import org.roda.wui.common.client.ClientLogger; +import org.roda.wui.common.client.tools.ConfigurationManager; +import org.roda.wui.common.client.tools.HistoryUtils; +import org.roda.wui.common.client.widgets.LoadingPopup; +import org.roda.wui.common.client.widgets.Toast; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * + * @author Miguel Guimarães + */ +public class PermissionsPanel extends FlowPanel implements HasValueChangeHandlers> { + + private static final ClientMessages messages = GWT.create(ClientMessages.class); + + @SuppressWarnings("unused") + private final ClientLogger logger = new ClientLogger(getClass().getName()); + + private final List allPermissions; + private final Map categoryPanels; + private final LoadingPopup loading; + private final FlowPanel categoriesContainer; + private final boolean viewOnly; + private final boolean expanded; + private List userSelections; + private boolean enabled; + // New instance fields to support refreshing + private RODAMember member; + private Runnable onDataLoadedCallback; + + public PermissionsPanel(RODAMember member, boolean viewOnly, boolean expanded) { + this.allPermissions = new ArrayList<>(); + this.userSelections = new ArrayList<>(); + this.categoryPanels = new LinkedHashMap<>(); + + this.member = member; + this.viewOnly = viewOnly; + this.expanded = expanded; + + loading = new LoadingPopup(this); + + this.enabled = true; + this.addStyleName("permissions-dashboard"); + + // 2. Container for Categories + categoriesContainer = new FlowPanel(); + categoriesContainer.setStyleName("permissions-categories"); + this.add(categoriesContainer); + + refresh(); // Trigger initial load + } + + public void refresh() { + loading.show(); + + // Clear old elements and state before rebuilding + categoriesContainer.clear(); + allPermissions.clear(); + categoryPanels.clear(); + userSelections.clear(); + + // 1. Fetch the absolute latest member data (to get freshly edited roles) + Services userServices = new Services("Get updated member", "get"); + userServices.membersResource(s -> s.getUser(member.getUUID())).whenComplete(this::handleMemberRefresh); + } + + private void handleMemberRefresh(RODAMember updatedMember, Throwable error) { + if (error != null) { + loading.hide(); + Toast.showError("Unable to fetch updated user data"); + HistoryUtils.newHistory(MemberManagement.RESOLVER.getHistoryPath()); + return; + } + + // Update local reference so init() uses the new roles + this.member = updatedMember; + + // 2. Fetch the groups + Services groupServices = new Services("Fetch user's group", "get"); + groupServices.membersResource(s -> s.getUserGroups(member.getId())).whenComplete((groups, throwable) -> { + if (throwable != null) { + loading.hide(); + Toast.showError("Unable to fetch user's groups"); + ClientLogger clientLogger = new ClientLogger(); + clientLogger.error("Error fetching user's groups", throwable); + HistoryUtils.newHistory(MemberManagement.RESOLVER.getHistoryPath()); + } else { + init(member, groups, viewOnly, expanded); + } + }); + } + + public void init(RODAMember member, Set groups, boolean viewOnly, boolean expanded) { + if (!viewOnly) { + userSelections = new ArrayList<>(member.getDirectRoles()); + } + + List roles = ConfigurationManager.getStringList("ui.role"); + + for (String role : roles) { + String description; + try { + description = messages.role(role); + } catch (MissingResourceException e) { + description = role + " (needs translation)"; + } + + Permission permission = new Permission(role, description); + permission.setChecked(member.getAllRoles().contains(role)); + permission.setEnabled(!viewOnly); + permission.setLocked(viewOnly); + allPermissions.add(permission); + + permission.addValueChangeHandler(new ValueChangeHandler() { + @Override + public void onValueChange(ValueChangeEvent event) { + if (userSelections.contains(event.getValue())) { + userSelections.remove(event.getValue()); + } else { + userSelections.add(event.getValue()); + } + onChange(); + } + }); + + // Grouping logic + String categoryName = determineCategory(role); + CategoryPanel categoryPanel = categoryPanels.get(categoryName); + if (categoryPanel == null) { + categoryPanel = new CategoryPanel(categoryName, viewOnly, expanded); + categoryPanels.put(categoryName, categoryPanel); + categoriesContainer.add(categoryPanel); + } + categoryPanel.addPermission(permission); + } + + if (!viewOnly) { + checkPermissions(member.getDirectRoles(), groups, true, "Permission locked because it's inherited from a group"); + } + + loading.hide(); + + if (onDataLoadedCallback != null) { + onDataLoadedCallback.run(); + } + } + + // --- The rest of your existing logic remains untouched --- + + private String determineCategory(String role) { + String lowerRole = role.toLowerCase(); + if (lowerRole.startsWith("aip") || (lowerRole.startsWith("representation")) + || lowerRole.startsWith("descriptive_metadata") || lowerRole.startsWith("preservation_metadata")) { + return messages.catalogueAndSearchGroupLabel(); + } + + if (lowerRole.startsWith("transfer") || lowerRole.startsWith("job")) { + return messages.ingestPreservationActionsInternalActionsGroupLabel(); + } + + if (lowerRole.startsWith("disposal_")) { + return messages.disposalGroupLabel(); + } + + if (lowerRole.startsWith("member") || lowerRole.startsWith("access_key") || lowerRole.startsWith("permission") + || lowerRole.startsWith("notification") || lowerRole.startsWith("log_entry") + || lowerRole.startsWith("distributed_instances") || lowerRole.startsWith("local_instance_configuration")) { + return messages.administrationGroupLabel(); + } + + if (lowerRole.startsWith("ri") || lowerRole.startsWith("risk")) { + return messages.planningGroupLabel(); + } + + return "Undefined"; + } + + public List getUserSelections() { + for (Permission permission : allPermissions) { + if (permission.isChecked() && !permission.isLocked() && permission.isEnabled()) { + if (!userSelections.contains(permission.getRole())) { + userSelections.add(permission.getRole()); + } + } else { + userSelections.remove(permission.getRole()); + } + } + + return userSelections; + } + + public void checkPermissions(Set directRoles, Set groups, boolean lock, String description) { + Set allRoles = groups.stream().flatMap(group -> group.getAllRoles().stream()).collect(Collectors.toSet()); + + if (allRoles.containsAll(directRoles)) { + checkPermissions(allRoles, true, description); + } else { + checkPermissions(directRoles, false); + checkPermissions(allRoles, true, description); + } + } + + public void checkPermissions(Set roles, boolean lock) { + checkPermissions(roles, lock, null); + } + + private void checkPermissions(Set roles, boolean lock, String description) { + for (String role : roles) { + boolean foundIt = false; + for (Iterator j = allPermissions.iterator(); j.hasNext() && !foundIt;) { + Permission p = j.next(); + if (p.getRole().equals(role)) { + foundIt = true; + p.setChecked(true); + if (description != null) { + p.setTooltip(description); + } + p.setLocked(lock); + } + } + } + } + + @Override + public void clear() { + for (Permission p : allPermissions) { + p.setChecked(false); + p.setLocked(false); + } + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + for (Permission p : allPermissions) { + p.setEnabled(enabled); + } + } + + public void updateLockedPermissions(Set memberGroups) { + if (!memberGroups.isEmpty()) { + this.setEnabled(false); + loading.show(); + } + } + + public Set getDirectRoles() { + List checkedPermissions = new ArrayList<>(); + for (Permission p : allPermissions) { + if (p.isChecked() && !p.isLocked()) { + checkedPermissions.add(p); + } + } + + Set specialRoles = new HashSet<>(); + for (Permission checkedPermission : checkedPermissions) { + specialRoles.add(checkedPermission.getRole()); + } + return specialRoles; + } + + @Override + public HandlerRegistration addValueChangeHandler(ValueChangeHandler> handler) { + return addHandler(handler, ValueChangeEvent.getType()); + } + + protected void onChange() { + ValueChangeEvent.fire(this, getValue()); + } + + public List getValue() { + return getUserSelections(); + } + + public void setOnDataLoadedCallback(Runnable callback) { + this.onDataLoadedCallback = callback; + } + + // --- INNER CLASSES --- + private class CategoryPanel extends FlowPanel { + private final FlowPanel itemsContainer; + private final List permissionsInCat = new ArrayList<>(); + private final CheckBox selectAllBox; + private final CheckBox readOnlyPermissions; + private final Label iconLabel; + private boolean isExpanded = false; + + public CategoryPanel(String titleText, boolean viewOnly, boolean expanded) { + this.isExpanded = expanded; + this.setStyleName("permission-category"); + // --- HEADER --- + FlowPanel header = new FlowPanel(); + header.setStyleName("permission-category-header"); + + // Title on the Left + Label title = new Label(titleText); + title.setStyleName("permission-category-title"); + + // Right side wrapper: Select All + Arrow + FlowPanel headerRight = new FlowPanel(); + headerRight.setStyleName("permission-category-header-right"); + + // Select All Checkbox (Passing the label text directly to the checkbox) + selectAllBox = new CheckBox(messages.selectAllButton()); + selectAllBox.setStyleName("permission-category-checkbox"); + + // Prevent header expand/collapse when interacting with the checkbox + selectAllBox.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + event.stopPropagation(); + } + }); + + selectAllBox.addValueChangeHandler(new ValueChangeHandler() { + @Override + public void onValueChange(ValueChangeEvent event) { + readOnlyPermissions.setValue(false); + triggerSelectAll(event.getValue()); + } + }); + + readOnlyPermissions = new CheckBox(messages.editPermissionsReadOnlyPermissionsText()); + readOnlyPermissions.setStyleName("permission-category-checkbox"); + // Prevent header expand/collapse when interacting with the checkbox + readOnlyPermissions.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + event.stopPropagation(); + } + }); + + readOnlyPermissions.addValueChangeHandler(new ValueChangeHandler() { + @Override + public void onValueChange(ValueChangeEvent event) { + selectAllBox.setValue(false); + triggerSelectReadOnly(event.getValue()); + } + }); + + // Arrow Icon + iconLabel = new Label(); + iconLabel.setStyleName("fas fa-chevron-down permission-category-icon"); + + // Add elements to the right wrapper + if (!viewOnly) { + headerRight.add(readOnlyPermissions); + headerRight.add(selectAllBox); + } + headerRight.add(iconLabel); + + // Add left title and right wrapper to the main flex header + header.add(title); + header.add(headerRight); + + // Accordion Expand/Collapse Logic + // --- FIXED ACCORDION TOGGLE LOGIC --- + header.addDomHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + + // FIX: Check exactly what element was clicked. + // If it was the Select All box OR its text label, abort the toggle. + com.google.gwt.dom.client.Element target = com.google.gwt.dom.client.Element + .as(event.getNativeEvent().getEventTarget()); + + if (selectAllBox.getElement().isOrHasChild(target) || readOnlyPermissions.getElement().isOrHasChild(target)) { + return; + } + + // Otherwise, toggle normally + isExpanded = !isExpanded; + itemsContainer.setVisible(isExpanded); + if (isExpanded) { + header.removeStyleName("collapsed"); + iconLabel.setStyleName("fas fa-chevron-up permission-category-icon"); + } else { + header.addStyleName("collapsed"); + iconLabel.setStyleName("fas fa-chevron-down permission-category-icon"); + } + } + }, ClickEvent.getType()); + + // --- ITEMS CONTAINER --- + itemsContainer = new FlowPanel(); + itemsContainer.setStyleName("permission-category-items"); + + if (isExpanded) { + header.removeStyleName("collapsed"); + iconLabel.setStyleName("fas fa-chevron-up permission-category-icon"); + itemsContainer.setVisible(true); + } else { + itemsContainer.setVisible(false); + } + + this.add(header); + this.add(itemsContainer); + } + + public void addPermission(Permission p) { + permissionsInCat.add(p); + itemsContainer.add(p); + + // Uncheck "Select All" in the header if a single item is unchecked + p.addValueChangeHandler(new ValueChangeHandler() { + @Override + public void onValueChange(ValueChangeEvent event) { + if (!p.isChecked()) { + selectAllBox.setValue(false, false); + } + } + }); + } + + private void triggerSelectAll(boolean isChecked) { + for (Permission p : permissionsInCat) { + if (p.isVisible() && p.isEnabled() && !p.isLocked()) { + if (p.isChecked() != isChecked) { + p.setChecked(isChecked); + p.onChange(); + } + } + } + } + + private void triggerSelectReadOnly(boolean isChecked) { + for (Permission p : permissionsInCat) { + if (p.isVisible() && p.isEnabled() && !p.isLocked()) + if (p.getRole().contains("read") || p.getRole().contains("view")) { + p.setChecked(isChecked); + p.onChange(); + } else { + p.setChecked(false); + p.onChange(); + } + } + } + } + + private class Permission extends FlowPanel implements HasValueChangeHandlers { + + private final String role; + private final CheckBox checkbox; + private final Label descriptionLabel; + private boolean locked; + private boolean enabled; + + public Permission(String role, String description) { + this.role = role; + this.checkbox = new CheckBox(); + this.descriptionLabel = new Label(description); + + this.add(checkbox); + this.add(descriptionLabel); + this.locked = false; + this.enabled = true; + + this.descriptionLabel.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + if (isEnabled() && !locked) { + checkbox.setValue(!checkbox.getValue()); + onChange(); + } + } + }); + + this.checkbox.addValueChangeHandler(new ValueChangeHandler() { + @Override + public void onValueChange(ValueChangeEvent event) { + onChange(); + } + }); + + this.addStyleName("permission-category-item"); + checkbox.addStyleName("permission-checkbox"); + descriptionLabel.setStylePrimaryName("permission-description"); + } + + public boolean isLocked() { + return locked; + } + + public void setLocked(boolean locked) { + this.locked = locked; + checkbox.setEnabled(!locked); + } + + public void setTooltip(String description) { + this.setTitle(description); + } + + public boolean isChecked() { + return checkbox.getValue(); + } + + public void setChecked(boolean checked) { + checkbox.setValue(checked); + } + + public String getRole() { + return role; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + if (!locked) { + this.checkbox.setEnabled(enabled); + } + } + + @Override + public HandlerRegistration addValueChangeHandler(ValueChangeHandler handler) { + return addHandler(handler, ValueChangeEvent.getType()); + } + + protected void onChange() { + ValueChangeEvent.fire(this, getValue()); + } + + public String getValue() { + return getRole(); + } + } +} \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/RODAMemberDetailsPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/RODAMemberDetailsPanel.java new file mode 100644 index 0000000000..b288f2bd41 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/RODAMemberDetailsPanel.java @@ -0,0 +1,149 @@ +package org.roda.wui.client.management.members.tabs; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; + +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.Widget; +import config.i18n.client.ClientMessages; +import org.roda.core.data.v2.user.Group; +import org.roda.core.data.v2.user.RODAMember; +import org.roda.core.data.v2.user.User; +import org.roda.wui.client.common.ActionsToolbar; +import org.roda.wui.client.common.actions.Actionable; +import org.roda.wui.client.common.actions.RODAMemberAction; +import org.roda.wui.client.common.actions.RODAMemberToolbarActions; +import org.roda.wui.client.common.actions.model.ActionableObject; +import org.roda.wui.client.common.actions.widgets.ActionableWidgetBuilder; +import org.roda.wui.client.common.utils.FormUtilities; +import org.roda.wui.client.common.utils.HtmlSnippetUtils; +import org.roda.wui.client.services.Services; +import org.roda.wui.common.client.widgets.Toast; + +import java.util.List; + +/** + * @author Miguel Guimarães + */ + +public class RODAMemberDetailsPanel extends Composite { + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); + + @UiField + ActionsToolbar actionsToolbar; + + @UiField + FlowPanel detailsPanel; + + private RODAMember member; + + public RODAMemberDetailsPanel(RODAMember member, AsyncCallback actionCallback) { + initWidget(uiBinder.createAndBindUi(this)); + this.member = member; + + AsyncCallback localCallback = new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + actionCallback.onFailure(caught); + } + + @Override + public void onSuccess(Actionable.ActionImpact result) { + if (Actionable.ActionImpact.UPDATED.equals(result)) { + actionCallback.onSuccess(result); + } + } + }; + + actionsToolbar.setLabelVisible(false); + actionsToolbar.setTagsVisible(false); + actionsToolbar.setActionableMenu(new ActionableWidgetBuilder(RODAMemberToolbarActions.get()) + .withActionCallback(localCallback).buildGroupedListWithObjects(new ActionableObject<>(member), + List.of(RODAMemberAction.EDIT), List.of(RODAMemberAction.EDIT)), + true); + + init(member); + } + + public void refresh() { + Services services = new Services("Get updated member details", "get"); + + if (member.isUser()) { + services.membersResource(s -> s.getUser(member.getUUID())).whenComplete((updatedUser, err) -> { + if (err != null) { + Toast.showError("Unable to fetch updated user details"); + } else if (updatedUser != null) { + this.member = updatedUser; + clear(); + init(updatedUser); + } + }); + } else { + services.membersResource(s -> s.getUser(member.getUUID())).whenComplete((updatedGroup, err) -> { + if (err != null) { + Toast.showError("Unable to fetch updated group details"); + } else if (updatedGroup != null) { + this.member = updatedGroup; + clear(); + init(updatedGroup); + } + }); + } + } + + private void init(RODAMember member) { + if (member.isUser()) { + initUser(member); + } else { + initGroup(member); + } + } + + private void initUser(RODAMember member) { + User user = (User) member; + + FormUtilities.addIfNotBlank(detailsPanel, messages.username(), user.getId()); + FormUtilities.addIfNotBlank(detailsPanel, messages.fullname(), user.getFullName()); + FormUtilities.addIfNotBlank(detailsPanel, messages.email(), user.getEmail()); + + FlowPanel topPanel = new FlowPanel(); + FlowPanel status = new FlowPanel(); + Label statusLabel = new Label(); + statusLabel.addStyleName("label"); + statusLabel.setText(messages.showUserStatusLabel()); + status.add(statusLabel); + HTML statusValue = new HTML(); + statusValue.addStyleName("value"); + statusValue.setHTML(HtmlSnippetUtils.getUserStateHtml(user)); + status.add(statusValue); + status.addStyleName("field"); + topPanel.addStyleName("descriptiveMetadata"); + topPanel.add(status); + detailsPanel.add(topPanel); + + if (!user.getExtra().isEmpty()) { + HtmlSnippetUtils.createExtraShow(detailsPanel, user.getExtra(), false); + } + } + + private void initGroup(RODAMember member) { + Group group = (Group) member; + + FormUtilities.addIfNotBlank(detailsPanel, messages.username(), group.getId()); + FormUtilities.addIfNotBlank(detailsPanel, messages.fullname(), group.getFullName()); + } + + public void clear() { + detailsPanel.clear(); + } + + interface MyUiBinder extends UiBinder { + Widget createAndBindUi(RODAMemberDetailsPanel detailsPanel); + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/RODAMemberDetailsPanel.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/RODAMemberDetailsPanel.ui.xml new file mode 100644 index 0000000000..fb3e0cf09d --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/RODAMemberDetailsPanel.ui.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/RODAMemberGroupsTab.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/RODAMemberGroupsTab.java new file mode 100644 index 0000000000..fb3aea69d6 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/RODAMemberGroupsTab.java @@ -0,0 +1,330 @@ +package org.roda.wui.client.management.members.tabs; + +import com.google.gwt.safehtml.shared.SafeHtmlUtils; +import com.google.gwt.user.cellview.client.Column; +import com.google.gwt.user.cellview.client.TextColumn; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.PopupPanel; +import com.google.gwt.user.client.ui.ScrollPanel; +import config.i18n.client.ClientMessages; +import org.roda.core.data.v2.user.Group; +import org.roda.core.data.v2.user.RODAMember; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.Widget; +import org.roda.core.data.v2.user.User; +import org.roda.wui.client.common.ActionsToolbar; +import org.roda.wui.client.common.NoAsyncCallback; +import org.roda.wui.client.common.actions.Actionable; +import org.roda.wui.client.common.actions.RODAMemberAction; +import org.roda.wui.client.common.actions.RODAMemberToolbarActions; +import org.roda.wui.client.common.actions.model.ActionableObject; +import org.roda.wui.client.common.actions.widgets.ActionableWidgetBuilder; +import org.roda.wui.client.common.dialogs.Dialogs; +import org.roda.wui.client.common.lists.utils.ActionMenuCell; +import org.roda.wui.client.common.lists.utils.BasicTablePanel; +import org.roda.wui.client.services.Services; +import org.roda.wui.common.client.widgets.Toast; + +import java.util.List; +import java.util.Set; + +/** + * + * @author Miguel Guimarães + */ +public class RODAMemberGroupsTab extends Composite { + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); + private final String id; + private final RODAMember member; // Keep a reference to check member.isUser() in refresh() + @UiField + ActionsToolbar actionsToolbar; + @UiField + FlowPanel groupsPanel; + + private AsyncCallback actionCallback; + + public RODAMemberGroupsTab(RODAMember member, AsyncCallback actionCallback) { + initWidget(uiBinder.createAndBindUi(this)); + + this.member = member; + this.id = member.getId(); + this.actionCallback = actionCallback; + + // 1. Create a local callback to intercept the UPDATED event + AsyncCallback localCallback = new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + actionCallback.onFailure(caught); + } + + @Override + public void onSuccess(Actionable.ActionImpact result) { + if (Actionable.ActionImpact.UPDATED.equals(result)) { + // Refresh the table locally + refresh(); + actionCallback.onSuccess(result); + } else { + // Pass other events (like DESTROYED) up the chain + actionCallback.onSuccess(result); + } + } + }; + + // 2. Bind the local callback to the toolbar + actionsToolbar.setLabelVisible(false); + actionsToolbar.setTagsVisible(false); + actionsToolbar.setActionableMenu(new ActionableWidgetBuilder(RODAMemberToolbarActions.get()) + .withActionCallback(localCallback).buildGroupedListWithObjects(new ActionableObject<>(member), + List.of(RODAMemberAction.ADD_NEW_GROUP, RODAMemberAction.ADD_NEW_MEMBER), + List.of(RODAMemberAction.ADD_NEW_GROUP, RODAMemberAction.ADD_NEW_MEMBER)), + true); + + // 3. Call unified refresh + refresh(); + } + + public void refresh() { + if (member.isUser()) { + Services services = new Services("Get user groups", "get"); + services.membersResource(s -> s.getUserGroups(id)).whenComplete((groupsSet, error) -> { + if (groupsSet != null) { + groupsPanel.clear(); + groupsPanel.add(createTable(groupsSet)); + } else if (error != null) { + Toast.showError(error.getMessage()); + } + }); + } else { + Services services = new Services("Get members of group", "get"); + services.membersResource(s -> s.getGroupMembers(id)).whenComplete((members, error) -> { + if (members != null) { + groupsPanel.clear(); + groupsPanel.add(createTableGroup(members)); + } else if (error != null) { + Toast.showError(error.getMessage()); + } + }); + } + } + + public ScrollPanel createTableGroup(Set members) { + BasicTablePanel table; + ScrollPanel scrollPanel; + + if (members.isEmpty()) { + String someOfAObject = messages.someOfAObject(User.class.getName()); + Label label = new HTML(SafeHtmlUtils.fromSafeConstant(messages.noItemsToDisplayPreFilters(someOfAObject))); + label.addStyleName("basicTableEmpty"); + scrollPanel = new ScrollPanel(label); + } else { + FlowPanel panel = new FlowPanel(); + table = getBasicTableForGroups(members); + table.removeSelectionModel(); + + panel.add(table); + scrollPanel = new ScrollPanel(panel); + } + + return scrollPanel; + } + + public ScrollPanel createTable(Set groups) { + BasicTablePanel table; + ScrollPanel scrollPanel; + + if (groups.isEmpty()) { + String someOfAObject = messages.someOfAObject(Group.class.getName()); + Label label = new HTML(SafeHtmlUtils.fromSafeConstant(messages.noItemsToDisplayPreFilters(someOfAObject))); + label.addStyleName("basicTableEmpty"); + scrollPanel = new ScrollPanel(label); + } else { + FlowPanel panel = new FlowPanel(); + table = getBasicTableForUsers(groups); + table.removeSelectionModel(); + + panel.add(table); + scrollPanel = new ScrollPanel(panel); + } + + return scrollPanel; + } + + private void showActionsMenu(Group key, int left, int top) { + // 1. Create the Popup + PopupPanel popup = new PopupPanel(true); // true = auto-hide when clicking away + + // 2. Create your FlowPanel and add your action items + FlowPanel menuPanel = new FlowPanel(); + menuPanel.addStyleName("groupedActionableDropdown"); + + Button removeBtn = new Button(messages.removeButton()); + removeBtn.addStyleName("actionable-button actionable-button-updated actionable-button-label btn-edit"); + removeBtn.addClickHandler(e -> { + popup.hide(); + + Dialogs.showConfirmDialog(messages.groups(), messages.removeGroupConfirmationMessage(key.getFullName()), + messages.cancelButton(), messages.confirmButton(), new NoAsyncCallback() { + @Override + public void onSuccess(Boolean confirm) { + if (confirm) { + Services services = new Services("Remove group from user", "update"); + services.membersResource(s -> s.removeGroupsFromUser(id, key.getId())).whenComplete((response, error) -> { + if (response != null) { + Toast.showInfo(messages.groups(), messages.groupSuccessfullyRemoved()); + + // Simple call to the unified refresh method + refresh(); + actionCallback.onSuccess(Actionable.ActionImpact.UPDATED); + + } else { + Toast.showError("Failed to remove group from user"); + } + }); + } + } + }); + }); + + menuPanel.add(removeBtn); + + // 3. Show the popup at the calculated coordinates + popup.setWidget(menuPanel); + popup.setPopupPosition(left, top); + popup.show(); + } + + private void showUserActionsMenu(User key, int left, int top) { + // 1. Create the Popup + PopupPanel popup = new PopupPanel(true); // true = auto-hide when clicking away + + // 2. Create your FlowPanel and add your action items + FlowPanel menuPanel = new FlowPanel(); + menuPanel.addStyleName("groupedActionableDropdown"); + + Button removeBtn = new Button(messages.removeButton()); + removeBtn.addStyleName("actionable-button actionable-button-updated actionable-button-label btn-edit"); + removeBtn.addClickHandler(e -> { + popup.hide(); + + Dialogs.showConfirmDialog(messages.removeMemberConfirmationTitle(), messages.removeMemberConfirmationMessage(key.getFullName()), + messages.cancelButton(), messages.confirmButton(), new NoAsyncCallback() { + @Override + public void onSuccess(Boolean confirm) { + if (confirm) { + Services services = new Services("Remove member from group", "update"); + services.membersResource(s -> s.removeMembersFromGroup(id, key.getId())).whenComplete((response, error) -> { + if (response != null) { + Toast.showInfo(messages.groups(), messages.groupSuccessfullyRemoved()); + + // Simple call to the unified refresh method + refresh(); + } else { + Toast.showError("Failed to remove group from user"); + } + }); + } + } + }); + }); + + menuPanel.add(removeBtn); + + // 3. Show the popup at the calculated coordinates + popup.setWidget(menuPanel); + popup.setPopupPosition(left, top); + popup.show(); + } + + private TextColumn getNameColumn() { + return new TextColumn() { + @Override + public String getValue(Group group) { + return group.getName(); + } + }; + } + + private TextColumn getFullNameColumn() { + return new TextColumn() { + @Override + public String getValue(Group group) { + return group.getFullName(); + } + }; + } + + private TextColumn getUserNameColumn() { + return new TextColumn() { + @Override + public String getValue(User group) { + return group.getName(); + } + }; + } + + private TextColumn getUserFullNameColumn() { + return new TextColumn() { + @Override + public String getValue(User group) { + return group.getFullName(); + } + }; + } + + private Column getActionsColumn() { + ActionMenuCell actionCell = new ActionMenuCell(this::showActionsMenu); + + return new Column(actionCell) { + @Override + public Group getValue(Group object) { + return object; + } + }; + } + + private Column getUserActionsColumn() { + ActionMenuCell actionCell = new ActionMenuCell(this::showUserActionsMenu); + + return new Column(actionCell) { + @Override + public User getValue(User object) { + return object; + } + }; + } + + private BasicTablePanel getBasicTableForUsers(Set groups) { + if (groups.isEmpty()) { + return new BasicTablePanel<>(messages.noItemsToDisplay(messages.distributedInstanceLabel())); + } else { + return new BasicTablePanel<>(groups.iterator(), + new BasicTablePanel.ColumnInfo(messages.groupFullname(), 15, getFullNameColumn()), + new BasicTablePanel.ColumnInfo(messages.groupName(), 15, getNameColumn()), + new BasicTablePanel.ColumnInfo<>(messages.actions(), 5, getActionsColumn())); + } + } + + private BasicTablePanel getBasicTableForGroups(Set members) { + if (members.isEmpty()) { + return new BasicTablePanel<>(messages.noItemsToDisplay(messages.distributedInstanceLabel())); + } else { + return new BasicTablePanel<>(members.iterator(), + new BasicTablePanel.ColumnInfo(messages.groupFullname(), 15, getUserFullNameColumn()), + new BasicTablePanel.ColumnInfo(messages.groupName(), 15, getUserNameColumn()), + new BasicTablePanel.ColumnInfo<>(messages.actions(), 5, getUserActionsColumn())); + } + } + + interface MyUiBinder extends UiBinder { + Widget createAndBindUi(RODAMemberGroupsTab tab); + } +} \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/RODAMemberGroupsTab.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/RODAMemberGroupsTab.ui.xml new file mode 100644 index 0000000000..9b5dc83176 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/RODAMemberGroupsTab.ui.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/RODAMemberPermissionsTab.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/RODAMemberPermissionsTab.java new file mode 100644 index 0000000000..a51a5b357f --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/RODAMemberPermissionsTab.java @@ -0,0 +1,67 @@ +package org.roda.wui.client.management.members.tabs; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; + +import org.roda.core.data.v2.user.RODAMember; +import org.roda.wui.client.common.ActionsToolbar; +import org.roda.wui.client.common.actions.Actionable; +import org.roda.wui.client.common.actions.RODAMemberAction; +import org.roda.wui.client.common.actions.RODAMemberToolbarActions; +import org.roda.wui.client.common.actions.model.ActionableObject; +import org.roda.wui.client.common.actions.widgets.ActionableWidgetBuilder; + +import java.util.List; + +/** + * + * @author Miguel Guimarães + */ +public class RODAMemberPermissionsTab extends Composite { + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); + + @UiField + ActionsToolbar actionsToolbar; + + // Changed from FlowPanel to PermissionsPanel to expose the refresh() method + @UiField(provided = true) + PermissionsPanel permissionsTablePanel; + + public RODAMemberPermissionsTab(RODAMember member, AsyncCallback actionCallback) { + permissionsTablePanel = new PermissionsPanel(member, true, false); + initWidget(uiBinder.createAndBindUi(this)); + + // 1. Create a local callback to intercept the UPDATED event + AsyncCallback localCallback = new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + actionCallback.onFailure(caught); + } + + @Override + public void onSuccess(Actionable.ActionImpact result) { + if (Actionable.ActionImpact.UPDATED.equals(result)) { + // Refresh the table locally WITHOUT reloading the whole page! + permissionsTablePanel.refresh(); + } else { + actionCallback.onSuccess(result); + } + } + }; + + actionsToolbar.setLabelVisible(false); + actionsToolbar.setTagsVisible(false); + actionsToolbar.setActionableMenu(new ActionableWidgetBuilder(RODAMemberToolbarActions.get()) + .withActionCallback(localCallback).buildGroupedListWithObjects(new ActionableObject<>(member), + List.of(RODAMemberAction.EDIT_PERMISSIONS), List.of(RODAMemberAction.EDIT_PERMISSIONS)), + true); + } + + interface MyUiBinder extends UiBinder { + Widget createAndBindUi(RODAMemberPermissionsTab tab); + } +} \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/RODAMemberPermissionsTab.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/RODAMemberPermissionsTab.ui.xml new file mode 100644 index 0000000000..3cb2a58a4c --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/tabs/RODAMemberPermissionsTab.ui.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/CreateRepresentationInformation.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/CreateRepresentationInformation.java index 82952f8a93..1dcab617d3 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/CreateRepresentationInformation.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/CreateRepresentationInformation.java @@ -12,7 +12,6 @@ import org.roda.core.data.v2.ri.RepresentationInformation; import org.roda.core.data.v2.ri.RepresentationInformationCreateRequest; import org.roda.wui.client.common.UserLogin; -import org.roda.wui.client.management.MemberManagement; import org.roda.wui.client.services.Services; import org.roda.wui.common.client.HistoryResolver; import org.roda.wui.common.client.tools.HistoryUtils; @@ -42,7 +41,7 @@ public void resolve(List historyTokens, final AsyncCallback call @Override public void isCurrentUserPermitted(AsyncCallback callback) { - UserLogin.getInstance().checkRoles(new HistoryResolver[] {MemberManagement.RESOLVER}, false, callback); + UserLogin.getInstance().checkRole(this, callback); } @Override diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/CreateRisk.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/CreateRisk.java index 04e38a0f66..6a67524e5d 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/CreateRisk.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/CreateRisk.java @@ -12,7 +12,6 @@ import org.roda.core.data.v2.risks.Risk; import org.roda.wui.client.common.UserLogin; import org.roda.wui.client.common.utils.AsyncCallbackUtils; -import org.roda.wui.client.management.MemberManagement; import org.roda.wui.client.services.Services; import org.roda.wui.common.client.HistoryResolver; import org.roda.wui.common.client.tools.HistoryUtils; @@ -45,7 +44,7 @@ public void resolve(List historyTokens, final AsyncCallback call @Override public void isCurrentUserPermitted(AsyncCallback callback) { - UserLogin.getInstance().checkRoles(new HistoryResolver[] {MemberManagement.RESOLVER}, false, callback); + UserLogin.getInstance().checkRole(this, callback); } @Override @@ -66,12 +65,6 @@ public String getHistoryToken() { @UiField(provided = true) RiskDataPanel riskDataPanel; - /** - * Create a new panel to create a user - * - * @param user - * the user to create - */ public CreateRisk() { this.riskDataPanel = new RiskDataPanel(null, false); initWidget(uiBinder.createAndBindUi(this)); diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/EditRepresentationInformation.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/EditRepresentationInformation.java index 2c7cb09864..365d3cf758 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/EditRepresentationInformation.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/EditRepresentationInformation.java @@ -19,7 +19,6 @@ import org.roda.wui.client.common.dialogs.Dialogs; import org.roda.wui.client.common.utils.AsyncCallbackUtils; import org.roda.wui.client.ingest.process.ShowJob; -import org.roda.wui.client.management.MemberManagement; import org.roda.wui.client.process.InternalProcess; import org.roda.wui.client.services.Services; import org.roda.wui.common.client.HistoryResolver; @@ -67,7 +66,7 @@ public void resolve(List historyTokens, final AsyncCallback call @Override public void isCurrentUserPermitted(AsyncCallback callback) { - UserLogin.getInstance().checkRoles(new HistoryResolver[] {MemberManagement.RESOLVER}, false, callback); + UserLogin.getInstance().checkRole(this, callback); } @Override diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/EditRisk.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/EditRisk.java index 6fb3218045..043858e281 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/EditRisk.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/EditRisk.java @@ -20,7 +20,6 @@ import org.roda.wui.client.common.dialogs.Dialogs; import org.roda.wui.client.common.utils.AsyncCallbackUtils; import org.roda.wui.client.ingest.process.ShowJob; -import org.roda.wui.client.management.MemberManagement; import org.roda.wui.client.process.InternalProcess; import org.roda.wui.client.services.Services; import org.roda.wui.common.client.HistoryResolver; @@ -70,7 +69,7 @@ public void resolve(List historyTokens, final AsyncCallback call @Override public void isCurrentUserPermitted(AsyncCallback callback) { - UserLogin.getInstance().checkRoles(new HistoryResolver[] {MemberManagement.RESOLVER}, false, callback); + UserLogin.getInstance().checkRole(this, callback); } @Override diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/EditRiskIncidence.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/EditRiskIncidence.java index cef3d874d3..afd9b8b3d8 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/EditRiskIncidence.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/EditRiskIncidence.java @@ -17,7 +17,6 @@ import org.roda.core.data.v2.risks.SeverityLevel; import org.roda.wui.client.common.UserLogin; import org.roda.wui.client.common.utils.HtmlSnippetUtils; -import org.roda.wui.client.management.MemberManagement; import org.roda.wui.client.services.Services; import org.roda.wui.common.client.HistoryResolver; import org.roda.wui.common.client.tools.HistoryUtils; @@ -71,7 +70,7 @@ public void resolve(List historyTokens, final AsyncCallback call @Override public void isCurrentUserPermitted(AsyncCallback callback) { - UserLogin.getInstance().checkRoles(new HistoryResolver[] {MemberManagement.RESOLVER}, false, callback); + UserLogin.getInstance().checkRole(this, callback); } @Override @@ -84,51 +83,35 @@ public String getHistoryToken() { return "edit_riskincidence"; } }; - - interface MyUiBinder extends UiBinder { - } - - private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); - private static final List fieldsToReturn = Arrays.asList(RodaConstants.INDEX_UUID, RodaConstants.RISK_INCIDENCE_ID, RodaConstants.RISK_INCIDENCE_RISK_ID, RodaConstants.RISK_INCIDENCE_DESCRIPTION, RodaConstants.RISK_INCIDENCE_STATUS, RodaConstants.RISK_INCIDENCE_SEVERITY, RodaConstants.RISK_INCIDENCE_DETECTED_BY, RodaConstants.RISK_INCIDENCE_DETECTED_ON, RodaConstants.RISK_INCIDENCE_MITIGATED_ON, RodaConstants.RISK_INCIDENCE_MITIGATED_BY, RodaConstants.RISK_INCIDENCE_MITIGATED_DESCRIPTION); - - private RiskIncidence incidence; private static final ClientMessages messages = GWT.create(ClientMessages.class); - + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); @UiField Button buttonApply; - @UiField Button buttonCancel; - @UiField Label incidenceId; - @UiField Label objectLabel; - @UiField Anchor objectLink; - @UiField Anchor riskLink; - @UiField Label detectedOn, detectedBy; - @UiField TextArea description; - @UiField ListBox status, severity; - @UiField TextArea mitigatedDescription; + private RiskIncidence incidence; /** * Create a new panel to edit incidence @@ -227,4 +210,7 @@ private void errorMessage(Throwable caught) { } } + interface MyUiBinder extends UiBinder { + } + } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/RiskHistory.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/RiskHistory.java index 46f7f4ae29..93f8eba2d9 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/RiskHistory.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/RiskHistory.java @@ -21,7 +21,6 @@ import org.roda.wui.client.common.UserLogin; import org.roda.wui.client.common.dialogs.Dialogs; import org.roda.wui.client.common.utils.AsyncCallbackUtils; -import org.roda.wui.client.management.MemberManagement; import org.roda.wui.client.services.Services; import org.roda.wui.common.client.HistoryResolver; import org.roda.wui.common.client.tools.HistoryUtils; @@ -70,7 +69,7 @@ public void resolve(List historyTokens, final AsyncCallback call @Override public void isCurrentUserPermitted(AsyncCallback callback) { - UserLogin.getInstance().checkRoles(new HistoryResolver[] {MemberManagement.RESOLVER}, false, callback); + UserLogin.getInstance().checkRole(this, callback); } @Override diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/ShowRepresentationInformation.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/ShowRepresentationInformation.java index 23ba0e4497..60522efa74 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/ShowRepresentationInformation.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/ShowRepresentationInformation.java @@ -39,7 +39,6 @@ import org.roda.wui.client.common.utils.AsyncCallbackUtils; import org.roda.wui.client.common.utils.HtmlSnippetUtils; import org.roda.wui.client.common.utils.SidebarUtils; -import org.roda.wui.client.management.MemberManagement; import org.roda.wui.client.search.Search; import org.roda.wui.client.services.Services; import org.roda.wui.common.client.HistoryResolver; @@ -84,7 +83,7 @@ public void resolve(List historyTokens, final AsyncCallback call @Override public void isCurrentUserPermitted(AsyncCallback callback) { - UserLogin.getInstance().checkRoles(new HistoryResolver[] {MemberManagement.RESOLVER}, false, callback); + UserLogin.getInstance().checkRole(this, callback); } @Override diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/ShowRisk.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/ShowRisk.java index da12b78da2..c8e59cdcf9 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/ShowRisk.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/ShowRisk.java @@ -12,9 +12,7 @@ import java.util.List; -import org.roda.core.data.v2.ip.IndexedAIP; import org.roda.core.data.v2.risks.IndexedRisk; -import org.roda.wui.client.browse.EditDescriptiveMetadata; import org.roda.wui.client.common.NoAsyncCallback; import org.roda.wui.client.common.UserLogin; import org.roda.wui.client.common.actions.Actionable; @@ -22,7 +20,6 @@ import org.roda.wui.client.common.actions.model.ActionableObject; import org.roda.wui.client.common.actions.widgets.ActionableWidgetBuilder; import org.roda.wui.client.common.utils.SidebarUtils; -import org.roda.wui.client.management.MemberManagement; import org.roda.wui.client.services.Services; import org.roda.wui.common.client.HistoryResolver; import org.roda.wui.common.client.tools.HistoryUtils; @@ -55,7 +52,7 @@ public void resolve(List historyTokens, final AsyncCallback call @Override public void isCurrentUserPermitted(AsyncCallback callback) { - UserLogin.getInstance().checkRoles(new HistoryResolver[] {MemberManagement.RESOLVER}, false, callback); + UserLogin.getInstance().checkRole(this, callback); } @Override diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/ShowRiskIncidence.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/ShowRiskIncidence.java index 3c347709cc..c7afc1e0a8 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/ShowRiskIncidence.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/ShowRiskIncidence.java @@ -22,7 +22,6 @@ import org.roda.wui.client.common.actions.widgets.ActionableWidgetBuilder; import org.roda.wui.client.common.utils.HtmlSnippetUtils; import org.roda.wui.client.common.utils.SidebarUtils; -import org.roda.wui.client.management.MemberManagement; import org.roda.wui.client.services.Services; import org.roda.wui.common.client.HistoryResolver; import org.roda.wui.common.client.tools.HistoryUtils; @@ -60,7 +59,7 @@ public void resolve(List historyTokens, final AsyncCallback call @Override public void isCurrentUserPermitted(AsyncCallback callback) { - UserLogin.getInstance().checkRoles(new HistoryResolver[] {MemberManagement.RESOLVER}, false, callback); + UserLogin.getInstance().checkRole(this, callback); } @Override diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/services/MembersRestService.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/services/MembersRestService.java index 1fe00bc838..7560b00b4c 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/services/MembersRestService.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/services/MembersRestService.java @@ -57,8 +57,7 @@ public interface MembersRestService extends RODAEntityRestService { @Operation(summary = "Get user", description = "Gets a particular user", responses = { @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = User.class))), @ApiResponse(responseCode = "404", description = "Not found", content = @Content(schema = @Schema(implementation = ErrorResponseMessage.class)))}) - User getUser( - @Parameter(description = "The user identifier") @PathVariable(name = "id") String name); + RODAMember getUser(@Parameter(description = "The user identifier") @PathVariable(name = "id") String name); @RequestMapping(path = "/users/authenticated", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Get authenticated user", description = "Gets the authenticated user", responses = { @@ -70,23 +69,20 @@ User getUser( @Operation(summary = "Delete user", description = "Deletes an existing user", responses = { @ApiResponse(responseCode = "204", description = "No Content"), @ApiResponse(responseCode = "404", description = "Not found", content = @Content(schema = @Schema(implementation = ErrorResponseMessage.class)))}) - Void deleteUser( - @Parameter(description = "The user identifier") @PathVariable(name = "id") String name); + Void deleteUser(@Parameter(description = "The user identifier") @PathVariable(name = "id") String name); @RequestMapping(path = "/groups/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Get group", description = "Gets a particular group", responses = { @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = User.class))), @ApiResponse(responseCode = "404", description = "Not found", content = @Content(schema = @Schema(implementation = ErrorResponseMessage.class)))}) - Group getGroup( - @Parameter(description = "The group identifier") @PathVariable(name = "id") String name); + Group getGroup(@Parameter(description = "The group identifier") @PathVariable(name = "id") String name); @RequestMapping(path = "/groups/{id}", method = RequestMethod.DELETE) @ResponseStatus(HttpStatus.NO_CONTENT) @Operation(summary = "Delete group", description = "Deletes an existing group", responses = { @ApiResponse(responseCode = "204", description = "No Content"), @ApiResponse(responseCode = "404", description = "Not found", content = @Content(schema = @Schema(implementation = ErrorResponseMessage.class)))}) - Void deleteGroup( - @Parameter(description = "The group identifier") @PathVariable(name = "id") String name); + Void deleteGroup(@Parameter(description = "The group identifier") @PathVariable(name = "id") String name); @RequestMapping(path = "/users/status", method = RequestMethod.POST) @Operation(summary = "Activate or deactivate a RODA users via search query", requestBody = @RequestBody(required = true, content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ChangeUserStatusRequest.class))), description = "Activates or deactivates RODA users", responses = { @@ -136,6 +132,46 @@ Group createGroup( Void updateGroup( @Parameter(name = "group", required = true, content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)) Group modifiedGroup); + @RequestMapping(path = "/users/{id}/addGroup", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = "Add group to user", requestBody = @RequestBody(required = true, content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = String.class))), description = "Adds a group to a user", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = User.class))), + @ApiResponse(responseCode = "404", description = "Not found", content = @Content(schema = @Schema(implementation = ErrorResponseMessage.class)))}) + User addGroupsToUser(@Parameter(description = "User identifier") @PathVariable(name = "id") String id, + @Parameter(name = "selectedItems", required = true, content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)) SelectedItemsRequest request); + + @RequestMapping(path = "/users/groups/{id}/addMembers", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = "Add group to user", requestBody = @RequestBody(required = true, content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = String.class))), description = "Adds a group to a user", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = Group.class))), + @ApiResponse(responseCode = "404", description = "Not found", content = @Content(schema = @Schema(implementation = ErrorResponseMessage.class)))}) + Group addMembersToGroup(@Parameter(description = "User identifier") @PathVariable(name = "id") String id, + @Parameter(name = "selectedItems", required = true, content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)) SelectedItemsRequest request); + + @RequestMapping(path = "/users/{id}/removeGroup/{groupId}", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = "Remove group from user", requestBody = @RequestBody(required = true, content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = String.class))), description = "Removes a group from a user", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = User.class))), + @ApiResponse(responseCode = "404", description = "Not found", content = @Content(schema = @Schema(implementation = ErrorResponseMessage.class)))}) + User removeGroupsFromUser(@Parameter(description = "User identifier") @PathVariable(name = "id") String id, + @Parameter(description = "Group identifier") @PathVariable(name = "groupId") String groupId); + + @RequestMapping(path = "/users/groups/{id}/removeMember/{userId}", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = "Remove group from user", requestBody = @RequestBody(required = true, content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = String.class))), description = "Removes a group from a user", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = User.class))), + @ApiResponse(responseCode = "404", description = "Not found", content = @Content(schema = @Schema(implementation = ErrorResponseMessage.class)))}) + Group removeMembersFromGroup(@Parameter(description = "User identifier") @PathVariable(name = "id") String id, + @Parameter(description = "User identifier") @PathVariable(name = "userId") String userId); + + @RequestMapping(path = "/users/{id}/groups", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = "Get user groups", description = "Gets the groups of a user", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = Group.class))), + @ApiResponse(responseCode = "404", description = "Not found", content = @Content(schema = @Schema(implementation = ErrorResponseMessage.class)))}) + Set getUserGroups(@Parameter(description = "User identifier") @PathVariable(name = "id") String id); + + @RequestMapping(path = "/users/groups/{id}/members", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = "Get group members", description = "Gets members of a group", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = User.class))), + @ApiResponse(responseCode = "404", description = "Not found", content = @Content(schema = @Schema(implementation = ErrorResponseMessage.class)))}) + Set getGroupMembers(@Parameter(description = "Group identifier") @PathVariable(name = "id") String id); + @RequestMapping(path = "/users", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Update user", requestBody = @RequestBody(required = true, content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = UpdateUserRequest.class))), description = "Updates a user", responses = { @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = User.class))), @@ -163,14 +199,15 @@ Void resetUserPassword(@Parameter(description = "User identifier") @PathVariable @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = StringResponse.class))), @ApiResponse(responseCode = "404", description = "Not found", content = @Content(schema = @Schema(implementation = ErrorResponseMessage.class)))}) StringResponse recoverLogin(@Parameter(description = "User email") @RequestParam(name = "email") String email, - @Parameter(description = "The language to be used for internationalization") @RequestParam(name = "lang", defaultValue = "en", required = false) String localeString, - @Parameter(description = "captcha") @RequestParam(required = false, name = "captcha") String captcha); + @Parameter(description = "The language to be used for internationalization") @RequestParam(name = "lang", defaultValue = "en", required = false) String localeString, + @Parameter(description = "captcha") @RequestParam(required = false, name = "captcha") String captcha); @RequestMapping(path = "/users/{id}/confirm", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Confirm user email", description = "Confirms a user email", responses = { @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = StringResponse.class))), @ApiResponse(responseCode = "400", description = "Bad request", content = @Content(schema = @Schema(implementation = ErrorResponseMessage.class)))}) - StringResponse confirmUserEmail(@Parameter(description = "User identifier") @PathVariable(name = "id") String username, + StringResponse confirmUserEmail( + @Parameter(description = "User identifier") @PathVariable(name = "id") String username, @Parameter(description = "token") @RequestParam(required = false, name = "token") String token); @RequestMapping(path = "/token", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @@ -189,8 +226,7 @@ AccessToken authenticate( @RequestMapping(path = "/users/{id}/resend-verification", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Resend verification email", description = "Resends verification email", responses = { @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = Notification.class)))}) - Notification sendEmailVerification( - @Parameter(description = "User identifier") @PathVariable(name = "id") String id, + Notification sendEmailVerification(@Parameter(description = "User identifier") @PathVariable(name = "id") String id, @Parameter(description = "The language to be used for internationalization") @RequestParam(name = "lang", defaultValue = "en", required = false) String localeString); @RequestMapping(path = "/users/login", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @@ -207,8 +243,7 @@ User login( @Operation(summary = "Delete access key", description = "Deletes an access key", responses = { @ApiResponse(responseCode = "204", description = "No Content"), @ApiResponse(responseCode = "404", description = "Not found", content = @Content(schema = @Schema(implementation = ErrorResponseMessage.class)))}) - Void deleteAccessKey( - @Parameter(description = "The access key id") @PathVariable(name = "id") String accessKeyId); + Void deleteAccessKey(@Parameter(description = "The access key id") @PathVariable(name = "id") String accessKeyId); @RequestMapping(path = "/users/{id}/access-keys", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "Get user access keys list", description = "Gets a particular user access keys", responses = { diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/common/client/tools/HistoryUtils.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/common/client/tools/HistoryUtils.java index baf0c9d3ce..caf4d1b8bb 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/common/client/tools/HistoryUtils.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/common/client/tools/HistoryUtils.java @@ -45,10 +45,9 @@ import org.roda.wui.client.ingest.process.ShowJobReport; import org.roda.wui.client.ingest.transfer.IngestTransfer; import org.roda.wui.client.ingest.transfer.TransferUpload; -import org.roda.wui.client.management.ShowGroup; import org.roda.wui.client.management.ShowLogEntry; import org.roda.wui.client.management.ShowNotification; -import org.roda.wui.client.management.ShowUser; +import org.roda.wui.client.management.members.ShowMember; import org.roda.wui.client.planning.ShowPreservationAgent; import org.roda.wui.client.planning.ShowRepresentationInformation; import org.roda.wui.client.planning.ShowRisk; @@ -412,7 +411,7 @@ public static void resolve(T object, boolean replace) { path = HistoryUtils.getHistory(jobPath, ShowJobReport.RESOLVER.getHistoryToken(), report.getUUID()); } else if (object instanceof RODAMember) { RODAMember member = (RODAMember) object; - HistoryUtils.newHistory(member.isUser() ? ShowUser.RESOLVER : ShowGroup.RESOLVER, member.getId()); + HistoryUtils.newHistory(ShowMember.RESOLVER, member.getUUID()); } else if (object instanceof IndexedPreservationAgent) { IndexedPreservationAgent agent = (IndexedPreservationAgent) object; HistoryUtils.newHistory(ShowPreservationAgent.RESOLVER, agent.getId()); diff --git a/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages.properties b/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages.properties index 90914d9e91..534245e153 100644 --- a/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages.properties +++ b/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages.properties @@ -234,6 +234,8 @@ someOfAObject[org.roda.core.data.v2.disposal.confirmation.DisposalConfirmation]: someOfAObject[org.roda.core.data.v2.disposal.rule.DisposalRules]:disposal rules someOfAObject[org.roda.core.data.v2.synchronization.central.DistributedInstances]:distributed instances someOfAObject[org.roda.core.data.v2.accessKey.AccessKeys]:access tokens +someOfAObject[org.roda.core.data.v2.user.Group]:groups +someOfAObject[org.roda.core.data.v2.user.User]:users oneOfAObject:{0} oneOfAObject[org.roda.core.data.v2.ip.AIP]:intellectual entity oneOfAObject[org.roda.core.data.v2.ip.IndexedAIP]:intellectual entity @@ -348,6 +350,7 @@ moveSelectedConfirmDialogMessage:Are you sure you want to move the selected {0} # Generic Buttons backButton:Back cancelButton:Cancel +updateButton: Update revertButton:Revert removeButton:Remove refreshButton:Refresh @@ -732,6 +735,10 @@ editUserEmailAlreadyExists:The email address {0} is already used in the records. editGroupNotFound:The group {0} no longer exists. userRemoveConfirmDialogTitle:User and group remove process userRemoveConfirmDialogMessage:Do you want to remove the selected users and groups? +singleUserRemoveConfirmDialogTitle:User remove process +singleGroupRemoveConfirmDialogTitle:Group remove process +singleUserRemoveConfirmDialogMessage:Do you want to remove the user "{0}"? +singleGroupRemoveConfirmDialogMessage:Do you want to remove the group "{0}"? userGroups:Groups addUserButton:Add user addGroupButton:Add group @@ -1708,9 +1715,11 @@ synchronizingStatus[INACTIVE]:Inactive synchronizingStatus[APPLYINGIDENTIFIER]:Applying identifier synchronizingStatus[SYNCHRONIZING]:Synchronizing #Access Token -addAccessKeyButton:New access Token -manageAccessKeyTitle:Manage Access Tokens -createAccessKeyTitle:Create Access Token +addAccessKeyButton:New access token +manageAccessKeyTitle:Manage Access tokens +createAccessKeyTitle:Create Access token +regenerateAccessKeyTitle:Regenerate Access token +accessKeyExpirationDateInThePast:Expiration date must be in the future showAccessKeyTitle:Access Token editAccessKeyTitle:Edit Access Token accessKeyLabel:Access Token @@ -1723,20 +1732,23 @@ accessKeyStatusValue:{0} accessKeyStatusValue[CREATED]:Created accessKeyStatusValue[ACTIVE]:Active accessKeyStatusValue[INACTIVE]:Inactive +accessKeyStatusValue[REVOKED]:Revoked +accessKeyStatusValue[EXPIRED]:Expired accessKeyWarningLabel:To keep your access token secure, we`ll permanently hide it after you close this window. So please be sure to save it somewhere secure. In case you lose your access token, you can regenerate a new one at any time. accessKeyInfo:This token and the instance identifier allow the registration of the local instance. In the local instance go to Administration -> Local instance and fill in the fields with the respective values. -accessKeyEditButton:Edit access Token -accessKeyUpdateButton:Update access Token -accessKeyDeleteButton:Delete access Token -accessKeyRegenerateButton:Regenerate access Token -accessKeyRevokeButton:Revoke access Token +accessKeyEditButton:Edit access token +accessKeyUpdateButton:Update access token +accessKeyDeleteButton:Delete access token +accessKeyRegenerateButton:Regenerate access token +accessKeyRevokeButton:Revoke access token accessKeyNeverUsedLabel:Never used accessKeyNotFoundLabel:Not found -accessKeySuccessfullyRegenerated:Access Token was successfully regenerated -accessKeySuccessfullyRevoked:Access Token was successfully revoked -accessKeyDeleteConfirmationMessage:Are you sure you want to permanently delete this access Token? This action can not be undone once executed. -accessKeyRevokeConfirmationMessage:Are you sure you want to revoke this access Token? -accessKeyRegenerateConfirmationMessage:Are you sure you want to regenerate this access Token? +accessKeySuccessfullyRegenerated:Access token was successfully regenerated +accessKeySuccessfullyDeleted:Access token was successfully deleted +accessKeySuccessfullyRevoked:Access token was successfully revoked +accessKeyDeleteConfirmationMessage:Are you sure you want to permanently delete this access token? This action can not be undone once executed. +accessKeyRevokeConfirmationMessage:Are you sure you want to revoke this access token? +accessKeyRegenerateConfirmationMessage:Are you sure you want to regenerate this access token? #Market marketPluginsActionsTabLabel:Actions marketVersionLabel:Version {0} available in the store @@ -1808,6 +1820,38 @@ ingestIdentifier: Jobs sipIdentifier: Identifiers sipDeleted:Deleted +# RODA Members - Toolbar actions +deactivateUserTitle: Deactivate user +deactivateUserConfirmationMessage: Are you sure you want to deactivate this user? This will prevent the user from logging in and performing any action in the system. +activateUserTitle: Activate user +activateUserConfirmationMessage: Are you sure you want to activate this user? This will allow the user to log in and perform actions in the system. + +# RODA Members +membersTabTitle: Members +addNewGroupModalTitle: Add to group +addToGroupButton: Add to group +addNewMemberToGroupButton: Add a member +addNewMemberToGroupTitle: Add a member to group +addNewMemberAction: Add member +groupSuccessfullyAdded: Group(s) successfully added +memberSuccessfullyAdded: Member(s) successfully added +removeGroupConfirmationTitle: Remove group from user +removeGroupConfirmationMessage: Are you sure you want to remove the group {0} from this user? +groupSuccessfullyRemoved: Group successfully removed +removeMemberConfirmationTitle: Remove member from group +removeMemberConfirmationMessage: Are you sure you want to remove the member {0} from this group? +memberSuccessfullyRemoved: Member successfully removed + + +# RODA Members - Permissions +catalogueAndSearchGroupLabel: Catalogue & search +ingestPreservationActionsInternalActionsGroupLabel: Ingest, preservation actions & internal actions +administrationGroupLabel: Administration +planningGroupLabel: Planning +disposalGroupLabel: Disposal +editPermissionsReadOnlyPermissionsText: Read-only +permissionsUpdateWithSuccess: Permissions successfully updated +editPermissionsModalTitle: Edit permissions # Email viewer emailViewerSubject=Subject diff --git a/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_pt_PT.properties b/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_pt_PT.properties index e24a296784..1b1a887aa4 100644 --- a/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_pt_PT.properties +++ b/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_pt_PT.properties @@ -234,6 +234,8 @@ someOfAObject[org.roda.core.data.v2.disposal.confirmation.DisposalConfirmation]: someOfAObject[org.roda.core.data.v2.disposal.rule.DisposalRules]:condições de eliminação someOfAObject[org.roda.core.data.v2.synchronization.central.DistributedInstances]:instâncias distribuídas someOfAObject[org.roda.core.data.v2.accessKey.AccessKeys]:chaves de acesso +someOfAObject[org.roda.core.data.v2.user.Group]:grupos +someOfAObject[org.roda.core.data.v2.user.User]:utilizadores oneOfAObject:{0} oneOfAObject[org.roda.core.data.v2.ip.AIP]:entidade intelectual oneOfAObject[org.roda.core.data.v2.ip.IndexedAIP]:entidade intelectual @@ -348,6 +350,7 @@ moveSelectedConfirmDialogMessage:Tem a certeza que pretende mover o(s) {0} iten( # Generic Buttons backButton:Voltar cancelButton:Cancelar +updateButton: Atualizar revertButton:Reverter removeButton:Eliminar refreshButton:Atualizar @@ -732,6 +735,10 @@ editUserEmailAlreadyExists:O endereço electrónico {0} já se encontra nos regi editGroupNotFound:O grupo {0} já não existe. userRemoveConfirmDialogTitle:Processo de remoção de utilizadores e grupos userRemoveConfirmDialogMessage:Tem a certeza que pretende eliminar os utilizadores e grupos selecionados? +singleUserRemoveConfirmDialogTitle:Processo de remoção de utilizador +singleGroupRemoveConfirmDialogTitle:Processo de remoção de grupo +singleUserRemoveConfirmDialogMessage:Tem a certeza que pretende eliminar o utilizador {0}? +singleGroupRemoveConfirmDialogMessage:Tem a certeza que pretende eliminar o grupo {0}? userGroups:Grupos addUserButton:Novo utilizador addGroupButton:Novo grupo @@ -1801,3 +1808,35 @@ detailsIngest: Ingestão ingestIdentifier: Processos sipIdentifier: Identificadores sipDeleted: Eliminado + +# RODA Members - Toolbar actions +deactivateUserTitle: Deactivar utilizador +deactivateUserConfirmationMessage: Tem a certeza que quer desativar este utilizador? Isso impedirá que o utilizador faça login e execute ações no sistema. +activateUserTitle: Ativar utilizador +activateUserConfirmationMessage: Tem a certeza que quer ativar este utilizador? Isso permitirá que o utilizador faça login e execute ações no sistema. + +# RODA Members/Groups +membersTabTitle: Membros +addNewGroupModalTitle: Adicionar grupo +addToGroupButton: Adicionar grupo +addNewMemberToGroupButton: Adicionar membro +addNewMemberToGroupTitle: Adicionar membro ao grupo +addNewMemberAction: Adicionar membro +groupSuccessfullyAdded: Grupo adicionado com sucesso +memberSuccessfullyAdded: Membro adicionado com sucesso +removeGroupConfirmationTitle: Remover grupo do utilizador +removeGroupConfirmationMessage: Tem a certeza que quer remover o grupo {0} deste utilizador? +groupSuccessfullyRemoved: Grupo removido com sucesso +removeMemberConfirmationTitle: Remover membro do grupo +removeMemberConfirmationMessage: Tem a certeza que quer remover o membro {0} do grupo? +memberSuccessfullyRemoved: Membro removido com sucesso + +# RODA Members - Permissions +catalogueAndSearchGroupLabel: Catálogo & pesquisa +ingestPreservationActionsInternalActionsGroupLabel: Ingestão, ações de preservação & ações internas +administrationGroupLabel: Administração +planningGroupLabel: Planeamento +disposalGroupLabel: Avaliação e seleção +editPermissionsReadOnlyPermissionsText: Leitura +permissionsUpdateWithSuccess: Permissões atualizadas com sucesso +editPermissionsModalTitle: Editar permissões \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/resources/config/roda-wui.properties b/roda-ui/roda-wui/src/main/resources/config/roda-wui.properties index de7f42b8ad..972891acbc 100644 --- a/roda-ui/roda-wui/src/main/resources/config/roda-wui.properties +++ b/roda-ui/roda-wui/src/main/resources/config/roda-wui.properties @@ -284,14 +284,6 @@ ui.role: log_entry.read ui.role: log_entry.create ui.role: log_entry.delete -ui.role: risk.read -ui.role: risk.manage - -ui.role: ri.read -ui.role: ri.manage - -ui.role: permission.read - ui.role: disposal_rule.read ui.role: disposal_rule.manage ui.role: disposal_schedule.read @@ -306,6 +298,14 @@ ui.role: disposal_confirmation.destroy ui.role: disposal_confirmation.restore ui.role: disposal_confirmation.delete_bin +ui.role: risk.read +ui.role: risk.manage + +ui.role: ri.read +ui.role: ri.manage + +ui.role: permission.read + ui.role: distributed_instances.read ui.role: distributed_instances.manage @@ -907,7 +907,7 @@ ui.lists.IngestAppraisal_searchFiles.search.prefilters.visible = true ui.lists.IngestAppraisal_searchRepresentations.search.prefilters.visible = true ui.lists.IngestTransfer_transferredResources.search.prefilters.visible = true ui.lists.MemberManagement_rodaMembers.search.prefilters.visible = true -ui.lists.MemberSelectDialog_rodaMembers.search.prefilters.visible = true +ui.lists.MemberSelectDialog_rodaMembers.search.prefilters.visible = false ui.lists.NotificationRegister_notifications.search.prefilters.visible = true ui.lists.PreservationEvents_events.search.prefilters.visible = true ui.lists.RepresentationInformationAssociations_RI.search.prefilters.visible = true @@ -2613,11 +2613,11 @@ ui.css.color.button.info=#d9534f ui.css.color.button.info.hover=#c9302c -ui.css.color.label.default=#777 -ui.css.color.label.success=#5cb85c -ui.css.color.label.danger=${ui.css.color.button.danger} -ui.css.color.label.warning=#f0ad4e -ui.css.color.label.info=#3e9ce8 +ui.css.color.label.default=#f5f5f5 +ui.css.color.label.success=#D1FAE5 +ui.css.color.label.danger=#FEE2E2 +ui.css.color.label.warning=#f9f2d2 +ui.css.color.label.info=#d2e7f9 ui.css.color.actions.primary=rgba(0,0,0,0.5) ui.css.color.actions.primary.hover=rgba(0,0,0,0.6) From 321ba996ca8c2acb73c6710130c5b852cb4431ca Mon Sep 17 00:00:00 2001 From: Eduardo Teixeira <58005905+eduardojst10@users.noreply.github.com> Date: Tue, 21 Apr 2026 10:24:58 +0100 Subject: [PATCH 02/37] audit logs new design. (#3643) correct DetailsPanelLogEntry location package. --- .../browse/tabs/BrowseLogEntryTabs.java | 21 +++ .../wui/client/browse/tabs/DetailsTab.java | 8 ++ .../roda/wui/client/main/BreadcrumbUtils.java | 19 +++ .../management/DetailsPanelLogEntry.java | 94 ++++++++++++ .../management/DetailsPanelLogEntry.ui.xml | 12 ++ .../wui/client/management/ShowLogEntry.java | 134 ++++-------------- .../wui/client/management/ShowLogEntry.ui.xml | 106 +++----------- 7 files changed, 197 insertions(+), 197 deletions(-) create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/BrowseLogEntryTabs.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/DetailsPanelLogEntry.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/DetailsPanelLogEntry.ui.xml diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/BrowseLogEntryTabs.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/BrowseLogEntryTabs.java new file mode 100644 index 0000000000..3ff7cac8db --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/BrowseLogEntryTabs.java @@ -0,0 +1,21 @@ +package org.roda.wui.client.browse.tabs; + +import com.google.gwt.safehtml.shared.SafeHtmlUtils; +import com.google.gwt.user.client.ui.Widget; +import org.roda.core.data.v2.log.LogEntry; + +/** + * + * @author Eduardo Teixeira + */ +public class BrowseLogEntryTabs extends Tabs { + public void init(LogEntry logEntry) { + // Details + createAndAddTab(SafeHtmlUtils.fromSafeConstant(messages.detailsTab()), new TabContentBuilder() { + @Override + public Widget buildTabWidget() { + return new DetailsTab(logEntry); + } + }); + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DetailsTab.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DetailsTab.java index 734718e613..ac18d8a148 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DetailsTab.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DetailsTab.java @@ -11,8 +11,10 @@ import org.roda.core.data.v2.ip.IndexedFile; import org.roda.core.data.v2.ip.TransferredResource; +import org.roda.core.data.v2.log.LogEntry; import org.roda.wui.client.common.model.BrowseAIPResponse; import org.roda.wui.client.common.model.BrowseRepresentationResponse; +import org.roda.wui.client.management.DetailsPanelLogEntry; import org.roda.wui.client.ingest.transfer.DetailsPanelTransferredResource; import org.roda.wui.client.planning.DetailsPanelAIP; import org.roda.wui.client.planning.DetailsPanelFile; @@ -65,6 +67,12 @@ public DetailsTab(TransferredResource resource) { content.add(detailsPanel); } + public DetailsTab(LogEntry logEntry) { + initWidget(uiBinder.createAndBindUi(this)); + DetailsPanelLogEntry detailsPanel = new DetailsPanelLogEntry(logEntry); + content.add(detailsPanel); + } + interface MyUiBinder extends UiBinder { } } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java index d62435b148..6a9cf19647 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java @@ -20,7 +20,10 @@ import org.roda.core.data.v2.ip.IndexedRepresentation; import org.roda.core.data.v2.ip.TransferredResource; import org.roda.core.data.v2.user.RODAMember; +import org.roda.core.data.v2.log.LogEntry; import org.roda.wui.client.browse.BrowseTop; +import org.roda.wui.client.management.ShowLogEntry; +import org.roda.wui.client.management.UserLog; import org.roda.wui.client.browse.PreservationEvents; import org.roda.wui.client.disposal.DisposalDestroyedRecords; import org.roda.wui.client.ingest.appraisal.IngestAppraisal; @@ -30,6 +33,7 @@ import org.roda.wui.common.client.tools.DescriptionLevelUtils; import org.roda.wui.common.client.tools.HistoryUtils; import org.roda.wui.common.client.tools.ListUtils; +import org.roda.wui.common.client.tools.StringUtils; import org.roda.wui.common.client.widgets.Toast; import com.google.gwt.core.client.GWT; @@ -355,6 +359,21 @@ public static List getTransferredResourceBreadcrumbs(Transferred return ret; } + public static List getLogEntryBreadcrumbs(LogEntry logEntry) { + List ret = new ArrayList<>(); + ret.add(new BreadcrumbItem(SafeHtmlUtils.fromSafeConstant(messages.activityLogTitle()), + messages.activityLogTitle(), UserLog.RESOLVER.getHistoryPath())); + + if (logEntry != null) { + List path = new ArrayList<>(ShowLogEntry.RESOLVER.getHistoryPath()); + path.add(logEntry.getUUID()); + String label = StringUtils.isNotBlank(logEntry.getId()) ? logEntry.getId() : logEntry.getUUID(); + ret.add(new BreadcrumbItem(SafeHtmlUtils.fromString(label), label, path)); + } + + return ret; + } + public static List getDipBreadcrumbs(IndexedDIP dip, DIPFile dipFile, List dipFileAncestors) { List ret = new ArrayList<>(); diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/DetailsPanelLogEntry.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/DetailsPanelLogEntry.java new file mode 100644 index 0000000000..b544364ba1 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/DetailsPanelLogEntry.java @@ -0,0 +1,94 @@ +package org.roda.wui.client.management; + +import org.roda.core.data.v2.log.LogEntry; +import org.roda.wui.client.common.utils.HtmlSnippetUtils; +import org.roda.wui.common.client.tools.Humanize; +import org.roda.wui.common.client.tools.StringUtils; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.safehtml.shared.SafeHtmlUtils; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.InlineHTML; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.Widget; + +import config.i18n.client.ClientMessages; + +/** + * + * @author Eduardo Teixeira + */ +public class DetailsPanelLogEntry extends Composite { + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private static final MyUiBinder uiBinder = GWT.create(MyUiBinder.class); + + @UiField + FlowPanel details; + + public DetailsPanelLogEntry(LogEntry logEntry) { + initWidget(uiBinder.createAndBindUi(this)); + init(logEntry); + } + + private void init(LogEntry logEntry) { + addIfNotBlank(messages.logEntryIdentifier(), logEntry.getId()); + addIfNotBlank(messages.logEntryReason(), + logEntry.getAuditLogRequestHeaders() != null ? logEntry.getAuditLogRequestHeaders().getReason() : null); + addIfNotBlank(messages.logEntryInstanceId(), logEntry.getInstanceId()); + addIfNotBlank(messages.logEntryComponent(), logEntry.getActionComponent()); + addIfNotBlank(messages.logEntryMethod(), logEntry.getActionMethod()); + addIfNotBlank(messages.logEntryAddress(), logEntry.getAddress()); + + if (logEntry.getDatetime() != null) { + details.add(buildField(messages.logEntryDatetime(), + new InlineHTML(SafeHtmlUtils.htmlEscape(Humanize.formatDateTime(logEntry.getDatetime()))))); + } + + details.add(buildField(messages.logEntryDuration(), + new InlineHTML(SafeHtmlUtils.htmlEscape(Humanize.durationMillisToShortDHMS(logEntry.getDuration()))))); + + addIfNotBlank(messages.logEntryRelatedObject(), logEntry.getRelatedObjectID()); + addIfNotBlank(messages.logEntryUsername(), logEntry.getUsername()); + + if (logEntry.getParameters() != null && !logEntry.getParameters().isEmpty()) { + String paramsInline = logEntry.getParameters().stream().map(p -> messages.logParameter(p.getName(), p.getValue())) + .collect(java.util.stream.Collectors.joining(" | ")); + details.add(buildField(messages.logEntryParameters(), new InlineHTML(SafeHtmlUtils.htmlEscape(paramsInline)))); + } + + if (logEntry.getState() != null) { + details.add(buildField(messages.logEntryState(), + new InlineHTML(HtmlSnippetUtils.getLogEntryStateHtml(logEntry.getState())))); + } + } + + private void addIfNotBlank(String label, String value) { + if (StringUtils.isNotBlank(value)) { + details.add(buildField(label, new InlineHTML(SafeHtmlUtils.htmlEscape(value)))); + } + } + + private FlowPanel buildField(String labelText, InlineHTML html) { + FlowPanel field = new FlowPanel(); + field.setStyleName("field"); + + Label label = new Label(labelText); + label.setStyleName("label"); + + FlowPanel value = new FlowPanel(); + value.setStyleName("value"); + value.add(html); + + field.add(label); + field.add(value); + return field; + } + + interface MyUiBinder extends UiBinder { + Widget createAndBindUi(DetailsPanelLogEntry detailsPanelLogEntry); + } + +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/DetailsPanelLogEntry.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/DetailsPanelLogEntry.ui.xml new file mode 100644 index 0000000000..0e9f8d9e72 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/DetailsPanelLogEntry.ui.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowLogEntry.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowLogEntry.java index bd2ac3a59b..a2b6015967 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowLogEntry.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowLogEntry.java @@ -12,35 +12,35 @@ import java.util.List; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.SimplePanel; import org.roda.core.data.common.RodaConstants; import org.roda.core.data.v2.index.filter.Filter; import org.roda.core.data.v2.index.filter.SimpleFilterParameter; import org.roda.core.data.v2.log.LogEntry; -import org.roda.core.data.v2.log.LogEntryParameter; +import org.roda.wui.client.browse.tabs.BrowseLogEntryTabs; +import org.roda.wui.client.common.ActionsToolbar; +import org.roda.wui.client.common.NavigationToolbar; +import org.roda.wui.client.common.TitlePanel; import org.roda.wui.client.common.UserLogin; import org.roda.wui.client.common.lists.InternalLogEntryList; import org.roda.wui.client.common.lists.utils.AsyncTableCellOptions; import org.roda.wui.client.common.lists.utils.ListBuilder; import org.roda.wui.client.common.search.SearchWrapper; -import org.roda.wui.client.common.utils.HtmlSnippetUtils; +import org.roda.wui.client.main.BreadcrumbUtils; import org.roda.wui.client.services.Services; import org.roda.wui.common.client.HistoryResolver; import org.roda.wui.common.client.tools.HistoryUtils; -import org.roda.wui.common.client.tools.Humanize; import org.roda.wui.common.client.tools.ListUtils; import org.roda.wui.common.client.tools.StringUtils; import com.google.gwt.core.client.GWT; import com.google.gwt.i18n.client.LocaleInfo; -import com.google.gwt.safehtml.shared.SafeHtmlUtils; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.FocusPanel; import com.google.gwt.user.client.ui.Widget; import config.i18n.client.ClientMessages; @@ -90,51 +90,15 @@ public String getHistoryToken() { private static final MyUiBinder uiBinder = GWT.create(MyUiBinder.class); private static final ClientMessages messages = GWT.create(ClientMessages.class); @UiField - Label logIdLabel; - @UiField - Label logIdValue; - @UiField - Label logReasonLabel; - @UiField - Label logReasonValue; - @UiField - Label logComponentLabel; - @UiField - Label logComponentValue; - @UiField - Label logMethodLabel; - @UiField - Label logMethodValue; - @UiField - Label logAddressLabel; - @UiField - Label logAddressValue; - @UiField - Label logDatetimeLabel; - @UiField - Label logDatetimeValue; - @UiField - Label logDurationValue; - @UiField - Label logRelatedObjectLabel; - @UiField - Label logRelatedObjectValue; - @UiField - Label logUsernameLabel; + FocusPanel keyboardFocus; @UiField - Label logUsernameValue; + NavigationToolbar navigationToolbar; @UiField - Label logParametersLabel; + ActionsToolbar actionsToolbar; @UiField - FlowPanel logParametersValue; + TitlePanel title; @UiField - Label logStateLabel; - @UiField - HTML logStateValue; - @UiField - Label logInstanceIdLabel; - @UiField - Label logInstanceIdValue; + BrowseLogEntryTabs browseTab; @UiField SimplePanel expandedAuditLogs; @UiField @@ -145,65 +109,14 @@ public String getHistoryToken() { */ public ShowLogEntry(LogEntry logEntry) { initWidget(uiBinder.createAndBindUi(this)); + navigationToolbar.withoutButtons().build(); + navigationToolbar.updateBreadcrumbPath(BreadcrumbUtils.getLogEntryBreadcrumbs(logEntry)); - logIdValue.setText(logEntry.getId()); - logIdLabel.setVisible(StringUtils.isNotBlank(logEntry.getId())); - logIdValue.setVisible(StringUtils.isNotBlank(logEntry.getId())); - - logReasonValue.setText(logEntry.getAuditLogRequestHeaders().getReason()); - logReasonLabel.setVisible(StringUtils.isNotBlank(logEntry.getAuditLogRequestHeaders().getReason())); - logReasonValue.setVisible(StringUtils.isNotBlank(logEntry.getAuditLogRequestHeaders().getReason())); - - logInstanceIdValue.setText(logEntry.getInstanceId()); - logInstanceIdLabel.setVisible(StringUtils.isNotBlank(logEntry.getInstanceId())); - logInstanceIdValue.setVisible(StringUtils.isNotBlank(logEntry.getInstanceId())); - - logComponentValue.setText(logEntry.getActionComponent()); - logComponentLabel.setVisible(StringUtils.isNotBlank(logEntry.getActionComponent())); - logComponentValue.setVisible(StringUtils.isNotBlank(logEntry.getActionComponent())); - - logMethodValue.setText(logEntry.getActionMethod()); - logMethodLabel.setVisible(StringUtils.isNotBlank(logEntry.getActionMethod())); - logMethodValue.setVisible(StringUtils.isNotBlank(logEntry.getActionMethod())); - - logAddressValue.setText(logEntry.getAddress()); - logAddressLabel.setVisible(StringUtils.isNotBlank(logEntry.getAddress())); - logAddressValue.setVisible(StringUtils.isNotBlank(logEntry.getAddress())); - - logDatetimeValue.setText(Humanize.formatDateTime(logEntry.getDatetime())); - logDatetimeLabel.setVisible(logEntry.getDatetime() != null); - logDatetimeValue.setVisible(logEntry.getDatetime() != null); - - logDurationValue.setText(Humanize.durationMillisToShortDHMS(logEntry.getDuration())); - - logRelatedObjectValue.setText(logEntry.getRelatedObjectID()); - logRelatedObjectLabel.setVisible(StringUtils.isNotBlank(logEntry.getRelatedObjectID())); - logRelatedObjectValue.setVisible(StringUtils.isNotBlank(logEntry.getRelatedObjectID())); - - logUsernameValue.setText(logEntry.getUsername()); - logUsernameLabel.setVisible(StringUtils.isNotBlank(logEntry.getUsername())); - logUsernameValue.setVisible(StringUtils.isNotBlank(logEntry.getUsername())); - - List parameters = logEntry.getParameters(); - - if (parameters != null && !parameters.isEmpty()) { - for (LogEntryParameter par : parameters) { - HTML parPanel = new HTML(); - parPanel.setHTML(SafeHtmlUtils.fromString(messages.logParameter(par.getName(), par.getValue()))); - logParametersValue.add(parPanel); - } - logParametersLabel.setVisible(true); - logParametersValue.setVisible(true); - } else { - logParametersLabel.setVisible(false); - logParametersValue.setVisible(false); - } - - logStateValue.setHTML(HtmlSnippetUtils.getLogEntryStateHtml(logEntry.getState())); - logStateLabel.setVisible(logEntry.getState() != null); - logStateValue.setVisible(logEntry.getState() != null); - - expandedAuditLogsList.setVisible(false); + actionsToolbar.setLabel(messages.showLogEntryTitle()); + browseTab.init(logEntry); + title.setText(StringUtils.isNotBlank(logEntry.getId()) ? logEntry.getId() : logEntry.getUUID()); + keyboardFocus.setFocus(true); + keyboardFocus.addStyleName("browse browse-file browse_main_panel"); if (logEntry.getAuditLogRequestHeaders() != null) { Label relatedAuditLogs = new Label(); @@ -212,16 +125,17 @@ public ShowLogEntry(LogEntry logEntry) { expandedAuditLogs.add(relatedAuditLogs); Filter filter = new Filter(new SimpleFilterParameter(RodaConstants.LOG_REQUEST_HEADER_UUID, - logEntry.getAuditLogRequestHeaders().getUuid())); + logEntry.getAuditLogRequestHeaders().getUuid())); ListBuilder auditLogListBuilder = new ListBuilder<>(() -> new InternalLogEntryList(), - new AsyncTableCellOptions<>(LogEntry.class, "AuditLogs_triggeredLogs").withFilter(filter) - .withSummary(messages.listOfAIPs()).bindOpener()); + new AsyncTableCellOptions<>(LogEntry.class, "AuditLogs_triggeredLogs").withFilter(filter) + .withSummary(messages.listOfAIPs()).bindOpener()); SearchWrapper aipsSearchWrapper = new SearchWrapper(false).createListAndSearchPanel(auditLogListBuilder); expandedAuditLogsList.setWidget(aipsSearchWrapper); expandedAuditLogsList.setVisible(true); } + } interface MyUiBinder extends UiBinder { diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowLogEntry.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowLogEntry.ui.xml index 04bba1d8e1..188090c562 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowLogEntry.ui.xml +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowLogEntry.ui.xml @@ -1,97 +1,29 @@ + xmlns:common="urn:import:org.roda.wui.client.common" + xmlns:tabs="urn:import:org.roda.wui.client.browse.tabs"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - + + \ No newline at end of file From 6be5a43ccefae3af3bd0783a58ab628114e139fd Mon Sep 17 00:00:00 2001 From: Eduardo Teixeira <58005905+eduardojst10@users.noreply.github.com> Date: Tue, 21 Apr 2026 15:47:23 +0100 Subject: [PATCH 03/37] notification logs UI update. (#3642) --- .../browse/tabs/BrowseNotificationsTabs.java | 20 ++ .../wui/client/browse/tabs/DetailsTab.java | 8 + .../roda/wui/client/common/resources/main.gss | 12 ++ .../client/common/utils/JavascriptUtils.java | 10 +- .../roda/wui/client/main/BreadcrumbUtils.java | 15 ++ .../management/DetailsPanelNotification.java | 180 ++++++++++++++++++ .../DetailsPanelNotification.ui.xml | 12 ++ .../client/management/ShowNotification.java | 101 ++-------- .../client/management/ShowNotification.ui.xml | 94 ++------- 9 files changed, 288 insertions(+), 164 deletions(-) create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/BrowseNotificationsTabs.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/DetailsPanelNotification.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/DetailsPanelNotification.ui.xml diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/BrowseNotificationsTabs.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/BrowseNotificationsTabs.java new file mode 100644 index 0000000000..a9c9a172a7 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/BrowseNotificationsTabs.java @@ -0,0 +1,20 @@ +package org.roda.wui.client.browse.tabs; + +import com.google.gwt.safehtml.shared.SafeHtmlUtils; +import com.google.gwt.user.client.ui.Widget; +import org.roda.core.data.v2.notifications.Notification; + +/** + * + * @author Eduardo Teixeira + */ +public class BrowseNotificationsTabs extends Tabs { + public void init(Notification notification) { + createAndAddTab(SafeHtmlUtils.fromSafeConstant(messages.detailsTab()), new TabContentBuilder() { + @Override + public Widget buildTabWidget() { + return new DetailsTab(notification); + } + }); + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DetailsTab.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DetailsTab.java index ac18d8a148..c6f11dcb8b 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DetailsTab.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DetailsTab.java @@ -11,9 +11,11 @@ import org.roda.core.data.v2.ip.IndexedFile; import org.roda.core.data.v2.ip.TransferredResource; +import org.roda.core.data.v2.notifications.Notification; import org.roda.core.data.v2.log.LogEntry; import org.roda.wui.client.common.model.BrowseAIPResponse; import org.roda.wui.client.common.model.BrowseRepresentationResponse; +import org.roda.wui.client.management.DetailsPanelNotification; import org.roda.wui.client.management.DetailsPanelLogEntry; import org.roda.wui.client.ingest.transfer.DetailsPanelTransferredResource; import org.roda.wui.client.planning.DetailsPanelAIP; @@ -67,6 +69,12 @@ public DetailsTab(TransferredResource resource) { content.add(detailsPanel); } + public DetailsTab(Notification notification) { + initWidget(uiBinder.createAndBindUi(this)); + DetailsPanelNotification detailsPanel = new DetailsPanelNotification(notification); + content.add(detailsPanel); + } + public DetailsTab(LogEntry logEntry) { initWidget(uiBinder.createAndBindUi(this)); DetailsPanelLogEntry detailsPanel = new DetailsPanelLogEntry(logEntry); diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/resources/main.gss b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/resources/main.gss index 2b9bf9f549..5597ef3d8f 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/resources/main.gss +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/resources/main.gss @@ -714,6 +714,18 @@ pre code { border-radius: 4px; } +.notification-body-content pre > code { + overflow: visible; +} + +.notification-body-content pre code { + max-height: none; +} + +.notification-body-content { + overflow: visible; +} + .error { color: #D20707; } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/utils/JavascriptUtils.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/utils/JavascriptUtils.java index 80438d1709..095c1c8486 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/utils/JavascriptUtils.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/utils/JavascriptUtils.java @@ -42,10 +42,14 @@ public static native void runHighlighter() /*-{ }-*/; public static native void runHighlighter(JavaScriptObject parent) /*-{ - $wnd.jQuery(parent).find('pre code').each(function(i, block) { + $wnd.jQuery(parent).find('pre code').each(function(i, block) { + if ($wnd.hljs && $wnd.hljs.highlightElement) { + $wnd.hljs.highlightElement(block); + } else if ($wnd.hljs && $wnd.hljs.highlightBlock) { $wnd.hljs.highlightBlock(block); - }); - }-*/; + } + }); +}-*/; public static native void runHighlighterOn(JavaScriptObject parent) /*-{ $wnd.jQuery(parent).each(function(i, block) { diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java index 6a9cf19647..f70a2386c4 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java @@ -374,6 +374,21 @@ public static List getLogEntryBreadcrumbs(LogEntry logEntry) { return ret; } + public static List getNotificationBreadcrumbs(Notification notification) { + List ret = new ArrayList<>(); + ret.add(new BreadcrumbItem(SafeHtmlUtils.fromSafeConstant(messages.notificationsTitle()), + messages.notificationsTitle(), NotificationRegister.RESOLVER.getHistoryPath())); + + if (notification != null) { + List path = new ArrayList<>(ShowNotification.RESOLVER.getHistoryPath()); + path.add(notification.getUUID()); + String label = StringUtils.isNotBlank(notification.getId()) ? notification.getId() : notification.getUUID(); + ret.add(new BreadcrumbItem(SafeHtmlUtils.fromString(label), label, path)); + } + + return ret; + } + public static List getDipBreadcrumbs(IndexedDIP dip, DIPFile dipFile, List dipFileAncestors) { List ret = new ArrayList<>(); diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/DetailsPanelNotification.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/DetailsPanelNotification.java new file mode 100644 index 0000000000..9ad6300aa9 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/DetailsPanelNotification.java @@ -0,0 +1,180 @@ +package org.roda.wui.client.management; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.google.gwt.json.client.JSONParser; +import com.google.gwt.user.client.ui.HTML; +import org.roda.core.data.v2.notifications.Notification; +import org.roda.wui.client.common.utils.HtmlSnippetUtils; +import org.roda.wui.client.common.utils.JavascriptUtils; +import org.roda.wui.common.client.tools.Humanize; +import org.roda.wui.common.client.tools.StringUtils; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.safehtml.shared.SafeHtmlUtils; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.InlineHTML; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.Widget; + +import config.i18n.client.ClientMessages; + +/** + * + * @author Eduardo Teixeira + */ +public class DetailsPanelNotification extends Composite { + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private static final MyUiBinder uiBinder = GWT.create(DetailsPanelNotification.MyUiBinder.class); + + @UiField + FlowPanel details; + + public DetailsPanelNotification(Notification resource) { + initWidget(uiBinder.createAndBindUi(this)); + init(resource); + } + + public void init(Notification n) { + addIfNotBlank(messages.notificationIdentifier(), n.getId()); + addIfNotBlank(messages.notificationSubject(), n.getSubject()); + + if (StringUtils.isNotBlank(n.getBody())) { + details.add(buildBodyField(messages.notificationBody(), n.getBody())); + } + + if (n.getSentOn() != null) { + details.add(buildField(messages.notificationSentOn(), + new InlineHTML(SafeHtmlUtils.htmlEscape(Humanize.formatDateTime(n.getSentOn()))))); + } + + addIfNotBlank(messages.notificationFromUser(), n.getFromUser()); + addIfNotBlank(messages.notificationIsAcknowledged(), + messages.isAcknowledged(Boolean.toString(n.isAcknowledged()).toLowerCase())); + + if (n.getState() != null) { + details.add(buildField(messages.notificationState(), + new InlineHTML(HtmlSnippetUtils.getNotificationStateHTML(n.getState())))); + } + + if (n.getAcknowledgedUsers() != null && !n.getAcknowledgedUsers().isEmpty()) { + FlowPanel ack = new FlowPanel(); + for (Map.Entry e : n.getAcknowledgedUsers().entrySet()) { + ack.add(new InlineHTML(SafeHtmlUtils.htmlEscape(e.getKey() + " " + e.getValue()))); + } + details.add(buildField(messages.notificationAcknowledgedUsers(), ack)); + } + + List remaining = new ArrayList<>(); + if (n.getRecipientUsers() != null) { + remaining.addAll(n.getRecipientUsers()); + } + + if (n.getAcknowledgedUsers() != null) { + remaining.removeAll(n.getAcknowledgedUsers().keySet()); + } + if (!remaining.isEmpty()) { + FlowPanel notAck = new FlowPanel(); + for (String user : remaining) { + notAck.add(new InlineHTML(SafeHtmlUtils.htmlEscape(user))); + } + details.add(buildField(messages.notificationNotAcknowledgedUsers(), notAck)); + } + } + + private void addIfNotBlank(String label, String value) { + if (StringUtils.isNotBlank(value)) { + details.add(buildField(label, new InlineHTML(SafeHtmlUtils.htmlEscape(value)))); + } + } + + private FlowPanel buildField(String label, Widget valueWidget) { + FlowPanel fieldPanel = new FlowPanel(); + fieldPanel.setStyleName("field"); + + Label fieldLabel = new Label(label); + fieldLabel.setStyleName("label"); + + FlowPanel fieldValuePanel = new FlowPanel(); + fieldValuePanel.setStyleName("value"); + fieldValuePanel.add(valueWidget); + + fieldPanel.add(fieldLabel); + fieldPanel.add(fieldValuePanel); + + return fieldPanel; + } + + private FlowPanel buildBodyField(String label, String rawBody) { + FlowPanel bodyPanel = new FlowPanel(); + bodyPanel.setStyleName("field"); + + Label fieldLabel = new Label(label); + fieldLabel.setStyleName("label"); + + FlowPanel fieldValuePanel = new FlowPanel(); + fieldValuePanel.setStyleName("value"); + fieldValuePanel.addStyleName("code-pre"); + fieldValuePanel.addStyleName("notification-body-content"); + + fieldValuePanel.add(buildNotificationBody(rawBody)); + + bodyPanel.add(fieldLabel); + bodyPanel.add(fieldValuePanel); + + return bodyPanel; + } + + private Widget buildNotificationBody(String rawBody) { + String body = rawBody == null ? "" : rawBody.trim(); + + if (body.isEmpty()) { + return new InlineHTML(""); + } + + if (isJson(body)) { + return buildHighlightedCodeBlock(body, "json"); + } + + if (isHtml(body)) { + return buildHighlightedCodeBlock(body, "html"); + } + + return new HTML("
" + SafeHtmlUtils.htmlEscape(body) + "
"); + } + + private boolean isJson(String body) { + try { + JSONParser.parseStrict(body); + return true; + } catch (Exception e) { + return false; + } + } + + private boolean isHtml(String body) { + String s = body == null ? "" : body.trim().toLowerCase(); + return s.contains("" + escaped + ""); + codeHtml.addAttachHandler(event -> { + if (event.isAttached()) { + JavascriptUtils.runHighlighter(codeHtml.getElement()); + } + }); + return codeHtml; + } + + interface MyUiBinder extends UiBinder { + Widget createAndBindUi(DetailsPanelNotification detailsPanelNotification); + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/DetailsPanelNotification.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/DetailsPanelNotification.ui.xml new file mode 100644 index 0000000000..3f340b0194 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/DetailsPanelNotification.ui.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowNotification.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowNotification.java index 12a9679b3d..c53249a08e 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowNotification.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowNotification.java @@ -10,31 +10,29 @@ */ package org.roda.wui.client.management; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.roda.core.data.common.RodaConstants; import org.roda.core.data.v2.notifications.Notification; +import org.roda.wui.client.browse.tabs.BrowseNotificationsTabs; +import org.roda.wui.client.common.ActionsToolbar; +import org.roda.wui.client.common.NavigationToolbar; +import org.roda.wui.client.common.TitlePanel; import org.roda.wui.client.common.UserLogin; -import org.roda.wui.client.common.utils.HtmlSnippetUtils; +import org.roda.wui.client.main.BreadcrumbUtils; import org.roda.wui.client.services.Services; import org.roda.wui.common.client.HistoryResolver; import org.roda.wui.common.client.tools.HistoryUtils; -import org.roda.wui.common.client.tools.Humanize; import org.roda.wui.common.client.tools.ListUtils; +import org.roda.wui.common.client.tools.StringUtils; import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.safehtml.shared.SafeHtmlUtils; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.uibinder.client.UiHandler; import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.FocusPanel; import com.google.gwt.user.client.ui.Widget; import config.i18n.client.ClientMessages; @@ -96,89 +94,30 @@ interface MyUiBinder extends UiBinder { RodaConstants.NOTIFICATION_ACKNOWLEDGED_USERS, RodaConstants.NOTIFICATION_STATE); @UiField - Label notificationId; - + TitlePanel title; @UiField - Label notificationSubject; - + ActionsToolbar actionsToolbar; @UiField - HTML notificationBody; - + NavigationToolbar navigationToolbar; @UiField - Label notificationSentOn; - + FocusPanel keyboardFocus; @UiField - Label notificationFromUser; + BrowseNotificationsTabs browseTab; - @UiField - Label notificationIsAcknowledged; - - @UiField - Label acknowledgedUsersKey; - - @UiField - HTML acknowledgedUsersValue; - - @UiField - Label stateLabel; - - @UiField - HTML stateValue; - - @UiField - Label notAcknowledgedUsersKey; - - @UiField - HTML notAcknowledgedUsersValue; - - @UiField - Button buttonCancel; - - /** - * Create a new panel to view a notification - * - */ public ShowNotification(Notification notification) { initWidget(uiBinder.createAndBindUi(this)); + navigationToolbar.withoutButtons().build(); + navigationToolbar.updateBreadcrumbPath(BreadcrumbUtils.getNotificationBreadcrumbs(notification)); - notificationId.setText(notification.getId()); - notificationSubject.setText(notification.getSubject()); - notificationBody.setHTML(notification.getBody()); - notificationSentOn.setText(Humanize.formatDateTime(notification.getSentOn())); - notificationFromUser.setText(notification.getFromUser()); - notificationIsAcknowledged - .setText(messages.isAcknowledged(Boolean.toString(notification.isAcknowledged()).toLowerCase())); - acknowledgedUsersKey.setVisible(false); - notAcknowledgedUsersKey.setVisible(false); - - List recipientUsers = new ArrayList<>(notification.getRecipientUsers()); - - for (String user : notification.getAcknowledgedUsers().keySet()) { - String ackDate = notification.getAcknowledgedUsers().get(user); - acknowledgedUsersKey.setVisible(true); - acknowledgedUsersValue.setHTML(SafeHtmlUtils.fromSafeConstant( - acknowledgedUsersValue.getHTML() + "

" + user + " " + ackDate + "

")); - recipientUsers.remove(user); - } - - for (String user : recipientUsers) { - notAcknowledgedUsersKey.setVisible(true); - notAcknowledgedUsersValue - .setHTML(SafeHtmlUtils.fromSafeConstant(notAcknowledgedUsersValue.getHTML() + "

" + user + "

")); - } + actionsToolbar.setLabel(messages.notificationTitle()); + actionsToolbar.setTagsVisible(false); - stateValue.setHTML(HtmlSnippetUtils.getNotificationStateHTML(notification.getState())); - stateLabel.setVisible(notification.getState() != null); - stateValue.setVisible(notification.getState() != null); - } + title.setText(StringUtils.isNotBlank(notification.getSubject()) ? notification.getSubject() : notification.getId()); - @UiHandler("buttonCancel") - void handleButtonCancel(ClickEvent e) { - cancel(); - } + browseTab.init(notification); - private void cancel() { - HistoryUtils.newHistory(NotificationRegister.RESOLVER); + keyboardFocus.setFocus(true); + keyboardFocus.addStyleName("browse browse-file browse_main_panel"); } } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowNotification.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowNotification.ui.xml index 301e8dfab4..04157e14d6 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowNotification.ui.xml +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/ShowNotification.ui.xml @@ -1,89 +1,23 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - + + \ No newline at end of file From ac0de51d23f371f4e166177c80c1d95a2887056e Mon Sep 17 00:00:00 2001 From: Eduardo Teixeira <58005905+eduardojst10@users.noreply.github.com> Date: Tue, 21 Apr 2026 15:50:07 +0100 Subject: [PATCH 04/37] update ui for representation information. (#3645) --- .../config/i18n/client/ClientMessages.java | 9 + .../BrowseRepresentationInformationTabs.java | 114 ++++++ .../wui/client/browse/tabs/DetailsTab.java | 9 + ...presentationInformationActionsToolbar.java | 33 ++ .../RepresentationInformationActions.java | 4 +- .../roda/wui/client/common/resources/main.gss | 1 + .../roda/wui/client/main/BreadcrumbUtils.java | 20 + ...DetailsPanelRepresentationInformation.java | 189 ++++++++++ ...tailsPanelRepresentationInformation.ui.xml | 9 + .../ShowRepresentationInformation.java | 347 ++---------------- .../ShowRepresentationInformation.ui.xml | 88 +---- .../i18n/client/ClientMessages.properties | 8 +- 12 files changed, 431 insertions(+), 400 deletions(-) create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/BrowseRepresentationInformationTabs.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/BrowseRepresentationInformationActionsToolbar.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/DetailsPanelRepresentationInformation.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/DetailsPanelRepresentationInformation.ui.xml diff --git a/roda-ui/roda-wui/src/main/java/config/i18n/client/ClientMessages.java b/roda-ui/roda-wui/src/main/java/config/i18n/client/ClientMessages.java index 135c05fdf1..52eb703443 100644 --- a/roda-ui/roda-wui/src/main/java/config/i18n/client/ClientMessages.java +++ b/roda-ui/roda-wui/src/main/java/config/i18n/client/ClientMessages.java @@ -1018,6 +1018,10 @@ SafeHtml searchPreFilterLongRangeFilterParameterGreaterThan(String searchPreFilt String detailsCreatedBy(); + String detailsUpdatedOn(); + + String detailsUpdatedBy(); + String detailsModifiedOn(); String detailsModifiedBy(); @@ -1322,10 +1326,15 @@ SafeHtml searchPreFilterLongRangeFilterParameterGreaterThan(String searchPreFilt String representationInformationIntellectualEntities(@PluralCount int size, String link); + String representationInformationIntellectualEntitiesAssociations(); + String representationInformationRepresentations(@PluralCount int size, String link); + String representationInformationRepresentationsAssociations(); + String representationInformationFiles(@PluralCount int size, String link); + String representationInformationFilesAssociations(); /****** Descriptive Metadata ****/ String metadataType(); diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/BrowseRepresentationInformationTabs.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/BrowseRepresentationInformationTabs.java new file mode 100644 index 0000000000..5cb7184b55 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/BrowseRepresentationInformationTabs.java @@ -0,0 +1,114 @@ +package org.roda.wui.client.browse.tabs; + +import java.util.ArrayList; +import java.util.List; + +import org.roda.core.data.common.RodaConstants; +import org.roda.core.data.utils.RepresentationInformationUtils; +import org.roda.core.data.v2.index.IsIndexed; +import org.roda.core.data.v2.index.filter.Filter; +import org.roda.core.data.v2.index.filter.FilterParameter; +import org.roda.core.data.v2.index.filter.OrFiltersParameters; +import org.roda.core.data.v2.index.filter.SimpleFilterParameter; +import org.roda.core.data.v2.ip.IndexedAIP; +import org.roda.core.data.v2.ip.IndexedFile; +import org.roda.core.data.v2.ip.IndexedRepresentation; +import org.roda.core.data.v2.ri.RepresentationInformation; +import org.roda.wui.client.common.lists.utils.AsyncTableCellOptions; +import org.roda.wui.client.common.lists.utils.ConfigurableAsyncTableCell; +import org.roda.wui.client.common.lists.utils.ListBuilder; +import org.roda.wui.client.common.search.SearchWrapper; + +import com.google.gwt.safehtml.shared.SafeHtmlUtils; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; + +/** + * + * @author Eduardo Teixeira + */ +public class BrowseRepresentationInformationTabs extends Tabs { + public void init(RepresentationInformation ri) { + + List aipParams = new ArrayList<>(); + List representationParams = new ArrayList<>(); + List fileParams = new ArrayList<>(); + initEntityFilters(ri, aipParams, representationParams, fileParams); + + createAndAddTab(SafeHtmlUtils.fromSafeConstant(messages.detailsTab()), new TabContentBuilder() { + @Override + public Widget buildTabWidget() { + return new DetailsTab(ri); + } + }); + + createAndAddTab(SafeHtmlUtils.fromString(messages.representationInformationIntellectualEntitiesAssociations()), + () -> buildAssociationsTab(IndexedAIP.class, aipParams, "Search_AIPs")); + + createAndAddTab(SafeHtmlUtils.fromString(messages.representationInformationRepresentationsAssociations()), + () -> buildAssociationsTab(IndexedRepresentation.class, representationParams, "Search_representations")); + + createAndAddTab(SafeHtmlUtils.fromString(messages.representationInformationFilesAssociations()), + () -> buildAssociationsTab(IndexedFile.class, fileParams, "Search_files")); + } + + private void initEntityFilters(RepresentationInformation ri, List aipParams, + List representationParams, List fileParams) { + for (String filter : ri.getFilters()) { + String[] parts = filter.split(RepresentationInformationUtils.REPRESENTATION_INFORMATION_FILTER_SEPARATOR); + if (parts.length < 3) { + continue; + } + + if (RodaConstants.INDEX_AIP.equals(parts[0])) { + aipParams.add(new SimpleFilterParameter(parts[1], parts[2])); + } else if (RodaConstants.INDEX_REPRESENTATION.equals(parts[0])) { + representationParams.add(new SimpleFilterParameter(parts[1], parts[2])); + } else if (RodaConstants.INDEX_FILE.equals(parts[0])) { + fileParams.add(new SimpleFilterParameter(parts[1], parts[2])); + } + } + } + + private Widget buildAssociationsTab(Class clazz, List params, + String listId) { + + if (params == null || params.isEmpty()) { + if (IndexedAIP.class.equals(clazz)) { + return buildEmptyAssociationsCardLikeTab(messages.representationInformationIntellectualEntities(0, "")); + } + if (IndexedRepresentation.class.equals(clazz)) { + return buildEmptyAssociationsCardLikeTab(messages.representationInformationRepresentations(0, "")); + } + return buildEmptyAssociationsCardLikeTab(messages.representationInformationFiles(0, "")); + } + Filter filter = new Filter(new OrFiltersParameters(params)); + + ListBuilder listBuilder = new ListBuilder<>(() -> new ConfigurableAsyncTableCell<>(), + new AsyncTableCellOptions<>(clazz, listId).withFilter(filter).withJustActive(false).bindOpener()); + + return new SearchWrapper(false).createListAndSearchPanel(listBuilder); + } + + private Widget buildEmptyAssociationsCardLikeTab(String messageHtml) { + FlowPanel card = new FlowPanel(); + card.addStyleName("roda6CardWithHeader"); + card.addStyleName("wrapper"); + card.addStyleName("skip_padding"); + + FlowPanel body = new FlowPanel(); + body.addStyleName("cardBody"); + + SimplePanel info = new SimplePanel(); + info.addStyleName("table-empty-inner"); + HTML label = new HTML(messageHtml); + label.addStyleName("table-empty-inner-label"); + info.setWidget(label); + + body.add(info); + card.add(body); + return card; + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DetailsTab.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DetailsTab.java index c6f11dcb8b..7bf777e351 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DetailsTab.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DetailsTab.java @@ -11,10 +11,12 @@ import org.roda.core.data.v2.ip.IndexedFile; import org.roda.core.data.v2.ip.TransferredResource; +import org.roda.core.data.v2.ri.RepresentationInformation; import org.roda.core.data.v2.notifications.Notification; import org.roda.core.data.v2.log.LogEntry; import org.roda.wui.client.common.model.BrowseAIPResponse; import org.roda.wui.client.common.model.BrowseRepresentationResponse; +import org.roda.wui.client.planning.DetailsPanelRepresentationInformation; import org.roda.wui.client.management.DetailsPanelNotification; import org.roda.wui.client.management.DetailsPanelLogEntry; import org.roda.wui.client.ingest.transfer.DetailsPanelTransferredResource; @@ -62,6 +64,13 @@ public DetailsTab(IndexedFile file, List riRules) { content.add(detailsPanel); } + public DetailsTab(RepresentationInformation ri){ + initWidget(uiBinder.createAndBindUi(this)); + + DetailsPanelRepresentationInformation detailsPanel = new DetailsPanelRepresentationInformation(ri); + content.add(detailsPanel); + } + public DetailsTab(TransferredResource resource) { initWidget(uiBinder.createAndBindUi(this)); diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/BrowseRepresentationInformationActionsToolbar.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/BrowseRepresentationInformationActionsToolbar.java new file mode 100644 index 0000000000..08d48a1839 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/BrowseRepresentationInformationActionsToolbar.java @@ -0,0 +1,33 @@ +package org.roda.wui.client.common; + +import java.util.List; + +import org.roda.core.data.v2.ri.RepresentationInformation; +import org.roda.wui.client.common.actions.RepresentationInformationActions; +import org.roda.wui.client.common.actions.model.ActionableObject; +import org.roda.wui.client.common.actions.widgets.ActionableWidgetBuilder; + +/** + * + * @author Eduardo Teixeira + */ +public class BrowseRepresentationInformationActionsToolbar + extends BrowseObjectActionsToolbar { + public void buildIcon() { + setIcon(null); + } + + public void buildTags() { + // do nothing + } + + public void buildActions() { + this.actions.clear(); + RepresentationInformationActions representationInformationActions; + representationInformationActions = RepresentationInformationActions.get(); + this.actions.add(new ActionableWidgetBuilder(representationInformationActions) + .buildGroupedListWithObjects(new ActionableObject<>(object), List.of(), + List.of(RepresentationInformationActions.RepresentationInformationAction.START_PROCESS))); + } + +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RepresentationInformationActions.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RepresentationInformationActions.java index 1e791fa7a5..76bd9b67bb 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RepresentationInformationActions.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RepresentationInformationActions.java @@ -45,8 +45,6 @@ import config.i18n.client.ClientMessages; -import javax.naming.Context; - public class RepresentationInformationActions extends AbstractActionable { private static final RepresentationInformationActions INSTANCE = new RepresentationInformationActions(); private static final ClientMessages messages = GWT.create(ClientMessages.class); @@ -328,7 +326,7 @@ public ActionableBundle createActionsBundle() { ActionableBundle formatActionableBundle = new ActionableBundle<>(); // MANAGEMENT - ActionableGroup managementGroup = new ActionableGroup<>(messages.sidebarActionsTitle()); + ActionableGroup managementGroup = new ActionableGroup<>(messages.manage(), "btn-edit"); managementGroup.addButton(messages.newButton(), RepresentationInformationAction.NEW, ActionImpact.UPDATED, "btn-plus-circle"); managementGroup.addButton(messages.createNewRepresentationInformation(), diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/resources/main.gss b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/resources/main.gss index 5597ef3d8f..92ef24eced 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/resources/main.gss +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/resources/main.gss @@ -2581,6 +2581,7 @@ td.datePickerMonth, td.datePickerYear { .main .wui-breadcrumbPanel .breadcrumb-root { color: COLOR_PRIMARY; font-weight: bold; + max-width: none !important; } .main .wui-breadcrumbPanel .breadcrumb-last { diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java index f70a2386c4..ef342d269d 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java @@ -21,6 +21,7 @@ import org.roda.core.data.v2.ip.TransferredResource; import org.roda.core.data.v2.user.RODAMember; import org.roda.core.data.v2.log.LogEntry; +import org.roda.core.data.v2.ri.RepresentationInformation; import org.roda.wui.client.browse.BrowseTop; import org.roda.wui.client.management.ShowLogEntry; import org.roda.wui.client.management.UserLog; @@ -30,6 +31,8 @@ import org.roda.wui.client.ingest.transfer.IngestTransfer; import org.roda.wui.client.management.members.MemberManagement; import org.roda.wui.client.management.members.ShowMember; +import org.roda.wui.client.planning.RepresentationInformationNetwork; +import org.roda.wui.client.planning.ShowRepresentationInformation; import org.roda.wui.common.client.tools.DescriptionLevelUtils; import org.roda.wui.common.client.tools.HistoryUtils; import org.roda.wui.common.client.tools.ListUtils; @@ -389,6 +392,23 @@ public static List getNotificationBreadcrumbs(Notification notif return ret; } + public static List getRepresentationInformationBreadCrumbs(RepresentationInformation ri){ + List ret = new ArrayList<>(); + ret.add(new BreadcrumbItem( + SafeHtmlUtils.fromSafeConstant(messages.representationInformationTitle()), + messages.representationInformationTitle(), + RepresentationInformationNetwork.RESOLVER.getHistoryPath())); + + if (ri != null) { + List path = new ArrayList<>(ShowRepresentationInformation.RESOLVER.getHistoryPath()); + path.add(ri.getUUID()); + String label = ri.getName() != null ? ri.getName() : ri.getId(); + ret.add(new BreadcrumbItem(SafeHtmlUtils.fromString(label), label, path)); + } + + return ret; + } + public static List getDipBreadcrumbs(IndexedDIP dip, DIPFile dipFile, List dipFileAncestors) { List ret = new ArrayList<>(); diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/DetailsPanelRepresentationInformation.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/DetailsPanelRepresentationInformation.java new file mode 100644 index 0000000000..7a82e7a941 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/DetailsPanelRepresentationInformation.java @@ -0,0 +1,189 @@ +package org.roda.wui.client.planning; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import org.roda.core.data.common.RodaConstants; +import org.roda.core.data.v2.ri.RelationObjectType; +import org.roda.core.data.v2.ri.RepresentationInformation; +import org.roda.core.data.v2.ri.RepresentationInformationRelation; +import org.roda.wui.client.common.utils.HtmlSnippetUtils; +import org.roda.wui.client.search.Search; +import org.roda.wui.client.services.Services; +import org.roda.wui.common.client.tools.HistoryUtils; +import org.roda.wui.common.client.tools.Humanize; +import org.roda.wui.common.client.tools.StringUtils; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.i18n.client.LocaleInfo; +import com.google.gwt.safehtml.shared.SafeHtmlUtils; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Anchor; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.InlineHTML; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.Widget; + +import config.i18n.client.ClientMessages; + +/** + * + * @author Eduardo Teixeira + */ +public class DetailsPanelRepresentationInformation extends Composite { + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private static final MyUiBinder uiBinder = GWT.create(MyUiBinder.class); + + @UiField + FlowPanel details; + + public DetailsPanelRepresentationInformation(RepresentationInformation ri) { + initWidget(uiBinder.createAndBindUi(this)); + init(ri); + } + + private void init(RepresentationInformation ri) { + if (ri.getCreatedOn() != null && StringUtils.isNotBlank(ri.getCreatedBy())) { + addIfNotBlank(messages.detailsCreatedOn(), Humanize.formatDateTime(ri.getCreatedOn())); + addIfNotBlank(messages.detailsCreatedBy(), ri.getCreatedBy()); + } + + if (ri.getUpdatedOn() != null && StringUtils.isNotBlank(ri.getUpdatedBy())) { + addIfNotBlank(messages.detailsUpdatedOn(), Humanize.formatDateTime(ri.getUpdatedOn())); + addIfNotBlank(messages.detailsUpdatedBy(), ri.getUpdatedBy()); + } + addIfNotBlank(messages.representationInformationIdentifier(), ri.getId()); + addIfNotBlank(messages.representationInformationName(), ri.getName()); + addIfNotBlank(messages.representationInformationDescription(), ri.getDescription()); + addIfNotBlank(messages.representationInformationFamily(), ri.getFamilyI18n()); + + if (ri.getSupport() != null) { + addIfNotBlank(messages.representationInformationSupport(), + messages.representationInformationSupportValue(ri.getSupport().toString())); + } + + addTagsField(ri); + addExtrasField(ri); + initRelations(ri); + + } + + private void addIfNotBlank(String label, String value) { + if (StringUtils.isNotBlank(value)) { + details.add(buildField(label, new InlineHTML(SafeHtmlUtils.htmlEscape(value)))); + } + } + + private Widget buildField(String label, Widget valueWidget) { + FlowPanel fieldPanel = new FlowPanel(); + fieldPanel.setStyleName("field"); + + Label fieldLabel = new Label(label); + fieldLabel.setStyleName("label"); + + FlowPanel fieldValuePanel = new FlowPanel(); + fieldValuePanel.setStyleName("value"); + fieldValuePanel.add(valueWidget); + + fieldPanel.add(fieldLabel); + fieldPanel.add(fieldValuePanel); + + return fieldPanel; + } + + private void addTagsField(RepresentationInformation ri) { + List tags = ri.getTags(); + if (tags == null || tags.isEmpty()) + return; + + FlowPanel tagsPanel = new FlowPanel(); + tagsPanel.addStyleName("value"); + for (String category : tags) { + InlineHTML tag = new InlineHTML("" + + messages.representationInformationListItems(SafeHtmlUtils.htmlEscape(category)) + ""); + tag.addClickHandler(event -> { + List history = new ArrayList<>(RepresentationInformationNetwork.RESOLVER.getHistoryPath()); + history.add(Search.RESOLVER.getHistoryToken()); + history.add(RodaConstants.REPRESENTATION_INFORMATION_TAGS); + history.add(category); + HistoryUtils.newHistory(history); + }); + tagsPanel.add(tag); + } + + details.add(buildField(messages.representationInformationTags(), tagsPanel)); + } + + private void addExtrasField(RepresentationInformation ri) { + Services services = new Services("Retrieve representation information family metadata", "get"); + services + .representationInformationResource( + s -> s.retrieveRepresentationInformationFamily(ri.getId(), LocaleInfo.getCurrentLocale().getLocaleName())) + .whenComplete((family, throwable) -> { + if (throwable == null && family != null && family.getFamilyValues() != null + && !family.getFamilyValues().isEmpty()) { + + FlowPanel extrasContent = new FlowPanel(); + extrasContent.addStyleName("ri-extras-panel"); + + HtmlSnippetUtils.createExtraShow(extrasContent, family.getFamilyValues(), false); + + if (extrasContent.getWidgetCount() > 0) { + details.add(extrasContent); + } + } + }); + } + + private void initRelations(RepresentationInformation ri) { + if (ri.getRelations() == null) { + return; + } + + ri.getRelations().sort(Comparator.comparingInt(o -> o.getObjectType().getWeight())); + for (RepresentationInformationRelation relation : ri.getRelations()) { + Widget relationValue = createRelationViewer(relation); + if (relationValue != null) { + relationValue.addStyleName("ri-links-panel"); + details.add(buildField(relation.getRelationTypeI18n(), relationValue)); + } + } + } + + private Widget createRelationViewer(RepresentationInformationRelation relation) { + Widget widgetToAdd = null; + String title = StringUtils.isNotBlank(relation.getTitle()) ? relation.getTitle() : relation.getLink(); + + if (relation.getObjectType().equals(RelationObjectType.TEXT)) { + widgetToAdd = new Label(title); + } else { + Anchor anchor = null; + + if (relation.getObjectType().equals(RelationObjectType.AIP)) { + anchor = new Anchor(title, + HistoryUtils.createHistoryHashLink(HistoryUtils.getHistoryBrowse(relation.getLink()))); + } else if (relation.getObjectType().equals(RelationObjectType.REPRESENTATION_INFORMATION)) { + List history = new ArrayList<>(); + history.addAll(ShowRepresentationInformation.RESOLVER.getHistoryPath()); + history.add(relation.getLink()); + anchor = new Anchor(title, HistoryUtils.createHistoryHashLink(history)); + } else if (relation.getObjectType().equals(RelationObjectType.WEB)) { + anchor = new Anchor(title, relation.getLink()); + anchor.getElement().setAttribute("target", "_blank"); + } + + if (anchor != null) { + widgetToAdd = anchor; + } + } + + return widgetToAdd; + } + + interface MyUiBinder extends UiBinder { + Widget createAndBindUi(DetailsPanelRepresentationInformation representationInformationPanel); + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/DetailsPanelRepresentationInformation.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/DetailsPanelRepresentationInformation.ui.xml new file mode 100644 index 0000000000..b875eb3ac9 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/DetailsPanelRepresentationInformation.ui.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/ShowRepresentationInformation.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/ShowRepresentationInformation.java index 60522efa74..7fd0a89407 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/ShowRepresentationInformation.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/ShowRepresentationInformation.java @@ -10,58 +10,27 @@ */ package org.roda.wui.client.planning; -import java.util.ArrayList; -import java.util.Comparator; import java.util.List; -import org.roda.core.data.common.RodaConstants; -import org.roda.core.data.utils.RepresentationInformationUtils; -import org.roda.core.data.v2.index.CountRequest; -import org.roda.core.data.v2.index.filter.Filter; -import org.roda.core.data.v2.index.filter.FilterParameter; -import org.roda.core.data.v2.index.filter.OrFiltersParameters; -import org.roda.core.data.v2.index.filter.SimpleFilterParameter; -import org.roda.core.data.v2.ip.IndexedAIP; -import org.roda.core.data.v2.ip.IndexedFile; -import org.roda.core.data.v2.ip.IndexedRepresentation; -import org.roda.core.data.v2.ri.RelationObjectType; import org.roda.core.data.v2.ri.RepresentationInformation; -import org.roda.core.data.v2.ri.RepresentationInformationCreateRequest; -import org.roda.core.data.v2.ri.RepresentationInformationRelation; -import org.roda.wui.client.common.NoAsyncCallback; +import org.roda.wui.client.browse.tabs.BrowseRepresentationInformationTabs; +import org.roda.wui.client.common.BrowseRepresentationInformationActionsToolbar; +import org.roda.wui.client.common.NavigationToolbar; import org.roda.wui.client.common.TitlePanel; import org.roda.wui.client.common.UserLogin; -import org.roda.wui.client.common.actions.Actionable; -import org.roda.wui.client.common.actions.RepresentationInformationActions; -import org.roda.wui.client.common.actions.model.ActionableObject; -import org.roda.wui.client.common.actions.widgets.ActionableWidgetBuilder; -import org.roda.wui.client.common.dialogs.RepresentationInformationDialogs; -import org.roda.wui.client.common.utils.AsyncCallbackUtils; -import org.roda.wui.client.common.utils.HtmlSnippetUtils; -import org.roda.wui.client.common.utils.SidebarUtils; -import org.roda.wui.client.search.Search; +import org.roda.wui.client.main.BreadcrumbUtils; import org.roda.wui.client.services.Services; import org.roda.wui.common.client.HistoryResolver; import org.roda.wui.common.client.tools.HistoryUtils; -import org.roda.wui.common.client.tools.Humanize; import org.roda.wui.common.client.tools.ListUtils; -import org.roda.wui.common.client.tools.StringUtils; import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.i18n.client.LocaleInfo; -import com.google.gwt.safehtml.shared.SafeHtmlUtils; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.Anchor; import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.InlineHTML; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.FocusPanel; import com.google.gwt.user.client.ui.Widget; import config.i18n.client.ClientMessages; @@ -100,49 +69,21 @@ public String getHistoryToken() { private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); @UiField - Label representationInformationId; + NavigationToolbar navigationToolbar; + @UiField - Label dateCreated; + BrowseRepresentationInformationActionsToolbar actionsToolbar; + @UiField - Label dateUpdated; + FocusPanel keyboardFocus; + @UiField TitlePanel title; + @UiField - Label representationInformationDescriptionKey; - @UiField - HTML representationInformationDescriptionValue; - @UiField - Label representationInformationFamilyKey; - @UiField - Label representationInformationFamilyValue; - @UiField - Label representationInformationTagKey; - @UiField - FlowPanel representationInformationTagValue; - @UiField - Label representationInformationSupportKey; - @UiField - Label representationInformationSupportValue; - @UiField - FlowPanel representationInformationRelationsValue; - @UiField - FlowPanel objectPanel; - @UiField - FlowPanel additionalSeparator; - @UiField - FlowPanel extras; - @UiField - SimplePanel actionsSidebar; - @UiField - FlowPanel contentFlowPanel; - @UiField - FlowPanel sidebarFlowPanel; + BrowseRepresentationInformationTabs browseRepresentationInformationTabs; private RepresentationInformation ri; - private ActionableWidgetBuilder actionableWidgetBuilder; - private List aipParams = new ArrayList<>(); - private List representationParams = new ArrayList<>(); - private List fileParams = new ArrayList<>(); public ShowRepresentationInformation() { this.ri = new RepresentationInformation(); @@ -153,268 +94,26 @@ public ShowRepresentationInformation(final RepresentationInformation ri) { this.ri = ri; initWidget(uiBinder.createAndBindUi(this)); - initEntityFilters(); - objectPanel.addStyleName("ri-entity-relation-section"); - initElements(); - } - public static ShowRepresentationInformation getInstance() { - if (instance == null) { - instance = new ShowRepresentationInformation(); - } - return instance; - } + navigationToolbar.withoutButtons().build(); + navigationToolbar.updateBreadcrumbPath(BreadcrumbUtils.getRepresentationInformationBreadCrumbs(ri)); - public void initElements() { title.setText(ri.getName()); - representationInformationId.setText(messages.representationInformationIdentifier() + ": " + ri.getId()); - - if (ri.getCreatedOn() != null && StringUtils.isNotBlank(ri.getCreatedBy())) { - dateCreated.setText(messages.dateCreated(Humanize.formatDateTime(ri.getCreatedOn()), ri.getCreatedBy())); - } - - if (ri.getUpdatedOn() != null && StringUtils.isNotBlank(ri.getUpdatedBy())) { - dateUpdated.setText(messages.dateUpdated(Humanize.formatDateTime(ri.getUpdatedOn()), ri.getUpdatedBy())); - } - - String description = (ri.getDescription() == null) ? "" : ri.getDescription(); - representationInformationDescriptionValue.setHTML(SafeHtmlUtils.fromString(description)); - representationInformationDescriptionKey.setVisible(StringUtils.isNotBlank(ri.getDescription())); - - representationInformationFamilyKey.setVisible(StringUtils.isNotBlank(ri.getFamily())); - representationInformationFamilyValue.setText(ri.getFamilyI18n()); - - List tagsList = ri.getTags(); - representationInformationTagValue.setVisible(tagsList != null && !tagsList.isEmpty()); - representationInformationTagKey.setVisible(tagsList != null && !tagsList.isEmpty()); - - if (tagsList != null) { - for (final String category : tagsList) { - InlineHTML parPanel = new InlineHTML(); - parPanel.setHTML("" - + messages.representationInformationListItems(SafeHtmlUtils.htmlEscape(category)) + ""); - parPanel.addClickHandler(event -> { - List history = new ArrayList<>(RepresentationInformationNetwork.RESOLVER.getHistoryPath()); - history.add(Search.RESOLVER.getHistoryToken()); - history.add(RodaConstants.REPRESENTATION_INFORMATION_TAGS); - history.add(category); - HistoryUtils.newHistory(history); - }); - representationInformationTagValue.add(parPanel); - } - } - - if (ri.getSupport() != null) { - representationInformationSupportValue - .setText(messages.representationInformationSupportValue(ri.getSupport().toString())); - representationInformationSupportKey.setVisible(true); - } else { - representationInformationSupportKey.setVisible(false); - } - - Services services = new Services("Retrieve representation information family metadata", "get"); - services - .representationInformationResource( - s -> s.retrieveRepresentationInformationFamily(ri.getId(), LocaleInfo.getCurrentLocale().getLocaleName())) - .whenComplete((representationInformationFamily, throwable) -> { - if (throwable == null) { - HtmlSnippetUtils.createExtraShow(extras, representationInformationFamily.getFamilyValues(), false); - } - }); + actionsToolbar.setObjectAndBuild(ri, null, null); + browseRepresentationInformationTabs.init(ri); - RepresentationInformationActions representationInformationActions = RepresentationInformationActions.get(); - - actionableWidgetBuilder = new ActionableWidgetBuilder<>(representationInformationActions) - .withActionCallback(new NoAsyncCallback() { - @Override - public void onSuccess(Actionable.ActionImpact result) { - if (result.equals(Actionable.ActionImpact.DESTROYED)) { - HistoryUtils.newHistory(RepresentationInformationNetwork.RESOLVER); - } - } - }); - - SidebarUtils.toggleSidebar(contentFlowPanel, sidebarFlowPanel, representationInformationActions.hasAnyRoles()); - actionsSidebar.setWidget(actionableWidgetBuilder.buildListWithObjects(new ActionableObject<>(ri))); - - initRelations(); + keyboardFocus.setFocus(true); + keyboardFocus.addStyleName("browse browse-file browse_main_panel"); } - public void updateLists() { - aipParams.clear(); - representationParams.clear(); - fileParams.clear(); - - initEntityFilters(); - initRelations(); - } - - private void initEntityFilters() { - for (String filter : ri.getFilters()) { - String[] splittedFilter = filter - .split(RepresentationInformationUtils.REPRESENTATION_INFORMATION_FILTER_SEPARATOR); - - if (splittedFilter[0].equals(RodaConstants.INDEX_AIP)) { - aipParams.add(new SimpleFilterParameter(splittedFilter[1], splittedFilter[2])); - } else if (splittedFilter[0].equals(RodaConstants.INDEX_REPRESENTATION)) { - representationParams.add(new SimpleFilterParameter(splittedFilter[1], splittedFilter[2])); - } else if (splittedFilter[0].equals(RodaConstants.INDEX_FILE)) { - fileParams.add(new SimpleFilterParameter(splittedFilter[1], splittedFilter[2])); - } - } - - if (!aipParams.isEmpty()) { - Filter aipFilter = new Filter(); - aipFilter.add(new OrFiltersParameters(aipParams)); - Services services = new Services("Count AIPs associated with representation information", "count"); - CountRequest countRequest = new CountRequest(aipFilter, true); - services.rodaEntityRestService(s -> s.count(countRequest), IndexedAIP.class).whenComplete((count, - throwable) -> initEntityFiltersObjectPanel(count.getResult(), throwable, IndexedAIP.class.getSimpleName())); - } else if (!representationParams.isEmpty()) { - Filter representationFilter = new Filter(); - representationFilter.add(new OrFiltersParameters(representationParams)); - - Services services = new Services("Count Representations associated with representation information", "count"); - CountRequest countRequest = new CountRequest(representationFilter, true); - services.rodaEntityRestService(s -> s.count(countRequest), IndexedRepresentation.class) - .whenComplete((count, throwable) -> initEntityFiltersObjectPanel(count.getResult(), throwable, - IndexedRepresentation.class.getSimpleName())); - } else if (!fileParams.isEmpty()) { - Filter fileFilter = new Filter(); - fileFilter.add(new OrFiltersParameters(fileParams)); - - Services services = new Services("Count Files associated with representation information", "count"); - CountRequest countRequest = new CountRequest(fileFilter, true); - services.rodaEntityRestService(s -> s.count(countRequest), IndexedFile.class).whenComplete((count, - throwable) -> initEntityFiltersObjectPanel(count.getResult(), throwable, IndexedFile.class.getSimpleName())); - } else { - initEntityFiltersObjectPanel(0L, null, IndexedAIP.class.getSimpleName()); - } - } - - private void initEntityFiltersObjectPanel(final Long count, final Throwable throwable, final String searchType) { - if (throwable != null) { - AsyncCallbackUtils.defaultFailureTreatment(throwable); - } else { - ShowRepresentationInformation.this.objectPanel.clear(); - String url = HistoryUtils.getSearchHistoryByRepresentationInformationFilter( - ShowRepresentationInformation.this.ri.getFilters(), searchType); - - InlineHTML label = new InlineHTML(); - label.addStyleName("ri-form-label-inline"); - if (IndexedAIP.class.getSimpleName().equals(searchType)) { - label.setHTML(messages.representationInformationIntellectualEntities(count.intValue(), url)); - } else if (IndexedRepresentation.class.getSimpleName().equals(searchType)) { - label.setHTML(messages.representationInformationRepresentations(count.intValue(), url)); - } else if (IndexedFile.class.getSimpleName().equals(searchType)) { - label.setHTML(messages.representationInformationFiles(count.intValue(), url)); - } - - ShowRepresentationInformation.this.objectPanel.add(label); - - InlineHTML edit = new InlineHTML(""); - edit.setTitle("Edit association rules"); - edit.addStyleName("ri-category link-color"); - - edit.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - RepresentationInformationDialogs.showPromptDialogRepresentationInformation( - messages.representationInformationEditAssociations(), messages.cancelButton(), messages.confirmButton(), - messages.searchButton(), ShowRepresentationInformation.this.ri, - new AsyncCallback() { - @Override - public void onFailure(Throwable caught) { - // do nothing - } - - @Override - public void onSuccess(RepresentationInformation result) { - // result is ri with updated filters - Services services = new Services("Update representation information", "update"); - RepresentationInformationCreateRequest createRequest = new RepresentationInformationCreateRequest(); - createRequest.setRepresentationInformation(result); - services.representationInformationResource(s -> s.updateRepresentationInformation(createRequest)) - .whenComplete((representationInformation, throwable1) -> { - if (throwable1 == null) { - ShowRepresentationInformation.getInstance().updateLists(); - } - }); - } - }); - } - }); - - ShowRepresentationInformation.this.objectPanel.add(edit); - } - } - - private void initRelations() { - additionalSeparator.setVisible(!ri.getRelations().isEmpty()); - - ri.getRelations().sort(Comparator.comparingInt(o -> o.getObjectType().getWeight())); - - for (RepresentationInformationRelation relation : ri.getRelations()) { - representationInformationRelationsValue.add(createRelationsLayout(relation)); - } - } - - private FlowPanel createRelationsLayout(RepresentationInformationRelation relation) { - FlowPanel panel = new FlowPanel(); - FlowPanel linksPanel = new FlowPanel(); - - Label entryLabel = new Label(relation.getRelationTypeI18n()); - entryLabel.setStyleName("label"); - panel.add(entryLabel); - - Widget w = createRelationViewer(relation); - if (w != null) { - w.addStyleName("ri-links-panel"); - linksPanel.add(w); - } - - panel.add(w); - - return panel; - } - - private Widget createRelationViewer(RepresentationInformationRelation relation) { - Widget widgetToAdd = null; - String title = StringUtils.isNotBlank(relation.getTitle()) ? relation.getTitle() : relation.getLink(); - - if (relation.getObjectType().equals(RelationObjectType.TEXT)) { - widgetToAdd = new Label(title); - } else { - Anchor anchor = null; - - if (relation.getObjectType().equals(RelationObjectType.AIP)) { - anchor = new Anchor(title, - HistoryUtils.createHistoryHashLink(HistoryUtils.getHistoryBrowse(relation.getLink()))); - } else if (relation.getObjectType().equals(RelationObjectType.REPRESENTATION_INFORMATION)) { - List history = new ArrayList<>(); - history.addAll(ShowRepresentationInformation.RESOLVER.getHistoryPath()); - history.add(relation.getLink()); - anchor = new Anchor(title, HistoryUtils.createHistoryHashLink(history)); - } else if (relation.getObjectType().equals(RelationObjectType.WEB)) { - anchor = new Anchor(title, relation.getLink()); - anchor.getElement().setAttribute("target", "_blank"); - } - - if (anchor != null) { - widgetToAdd = anchor; - } + public static ShowRepresentationInformation getInstance() { + if (instance == null) { + instance = new ShowRepresentationInformation(); } - - return widgetToAdd; + return instance; } - // Java method - public native boolean isValidUrl(String url) /*-{ - var pattern = /(http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/; - return pattern.test(url); - }-*/; - void resolve(List historyTokens, final AsyncCallback callback) { if (historyTokens.size() == 1) { Services services = new Services("Retrieve representation information", "get"); diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/ShowRepresentationInformation.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/ShowRepresentationInformation.ui.xml index 26591826cf..440ef146e8 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/ShowRepresentationInformation.ui.xml +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/ShowRepresentationInformation.ui.xml @@ -1,78 +1,22 @@ + xmlns:common="urn:import:org.roda.wui.client.common" + xmlns:tabs="urn:import:org.roda.wui.client.browse.tabs"> - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages.properties b/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages.properties index 534245e153..ceba63c65c 100644 --- a/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages.properties +++ b/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages.properties @@ -874,6 +874,10 @@ representationInformationRepresentations[\=1]:There is one represe representationInformationFiles:There are {0,number} files associated with this representation information representationInformationFiles[\=0]:There are no files associated with this representation information representationInformationFiles[\=1]:There is one file associated with this representation information +representationInformationIntellectualEntitiesAssociations=Intellectual entities associations +representationInformationRepresentationsAssociations=Representation associations +representationInformationFilesAssociations=File associations + # Descriptive Metadata metadataType:Type @@ -1811,7 +1815,9 @@ detailsIdentifier: Identifier detailsLevel: Level detailsType: Type detailsState: State -detailsCreatedOn: Created +detailsCreatedOn: Created on +detailsUpdatedOn: Updated on +detailsUpdatedBy: Updated by detailsCreatedBy: Creator detailsModifiedOn: Modified detailsModifiedBy: Modifier From 4b23e9c7b4a62503cb4d074b82dd6a13fd5db9bf Mon Sep 17 00:00:00 2001 From: Eduardo Teixeira <58005905+eduardojst10@users.noreply.github.com> Date: Tue, 21 Apr 2026 15:58:11 +0100 Subject: [PATCH 05/37] ui updated for risk detailed layout. (#3651) Co-authored-by: Luis Faria --- .../client/browse/tabs/BrowseRiskTabs.java | 46 +++ .../wui/client/browse/tabs/DetailsTab.java | 10 + .../common/BrowseRiskActionsToolbar.java | 39 +++ .../wui/client/common/NavigationToolbar.java | 5 + .../client/common/actions/RiskActions.java | 2 +- .../roda/wui/client/main/BreadcrumbUtils.java | 18 ++ .../wui/client/planning/DetailsPanelRisk.java | 216 +++++++++++++ .../client/planning/DetailsPanelRisk.ui.xml | 10 + .../roda/wui/client/planning/RiskHistory.java | 4 +- .../wui/client/planning/RiskHistory.ui.xml | 2 +- .../wui/client/planning/RiskShowPanel.java | 289 ------------------ .../wui/client/planning/RiskShowPanel.ui.xml | 154 ---------- .../roda/wui/client/planning/ShowRisk.java | 100 +++--- .../roda/wui/client/planning/ShowRisk.ui.xml | 34 +-- 14 files changed, 406 insertions(+), 523 deletions(-) create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/BrowseRiskTabs.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/BrowseRiskActionsToolbar.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/DetailsPanelRisk.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/DetailsPanelRisk.ui.xml delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/RiskShowPanel.java delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/RiskShowPanel.ui.xml diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/BrowseRiskTabs.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/BrowseRiskTabs.java new file mode 100644 index 0000000000..4539c6c530 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/BrowseRiskTabs.java @@ -0,0 +1,46 @@ +package org.roda.wui.client.browse.tabs; + +import org.roda.core.data.common.RodaConstants; +import org.roda.core.data.v2.index.filter.Filter; +import org.roda.core.data.v2.index.filter.SimpleFilterParameter; +import org.roda.core.data.v2.risks.IndexedRisk; +import org.roda.core.data.v2.risks.RiskIncidence; +import org.roda.wui.client.common.lists.RiskIncidenceList; +import org.roda.wui.client.common.lists.utils.AsyncTableCellOptions; +import org.roda.wui.client.common.lists.utils.ListBuilder; +import org.roda.wui.client.common.search.SearchWrapper; + +import com.google.gwt.safehtml.shared.SafeHtmlUtils; +import com.google.gwt.user.client.ui.Widget; + +public class BrowseRiskTabs extends Tabs { + + public void init(IndexedRisk risk) { + createAndAddTab(SafeHtmlUtils.fromSafeConstant(messages.detailsTab()), new TabContentBuilder() { + ; + @Override + public Widget buildTabWidget() { + return new DetailsTab(risk); + } + }); + + createAndAddTab(SafeHtmlUtils.fromSafeConstant(messages.riskIncidences()), new TabContentBuilder() { + @Override + public Widget buildTabWidget() { + return buildIncidencesSearchWrapper(risk); + } + }); + } + + private Widget buildIncidencesSearchWrapper(IndexedRisk risk) { + String RISK_INCIDENCE_LIST_ID = "RiskShowPanel_riskIncidences"; + Filter filter = new Filter(new SimpleFilterParameter(RodaConstants.RISK_INCIDENCE_RISK_ID, risk.getId())); + + ListBuilder listBuilder = new ListBuilder<>(() -> new RiskIncidenceList(), + new AsyncTableCellOptions<>(RiskIncidence.class, RISK_INCIDENCE_LIST_ID).withSummary(messages.riskIncidences()) + .withFilter(filter).bindOpener().withSearchPlaceholder(messages.riskIncidenceRegisterSearchPlaceHolder())); + + return new SearchWrapper(false).createListAndSearchPanel(listBuilder); + } + +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DetailsTab.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DetailsTab.java index 7bf777e351..74d79ca634 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DetailsTab.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DetailsTab.java @@ -14,6 +14,8 @@ import org.roda.core.data.v2.ri.RepresentationInformation; import org.roda.core.data.v2.notifications.Notification; import org.roda.core.data.v2.log.LogEntry; +import org.roda.core.data.v2.risks.IndexedRisk; +import org.roda.core.data.v2.risks.Risk; import org.roda.wui.client.common.model.BrowseAIPResponse; import org.roda.wui.client.common.model.BrowseRepresentationResponse; import org.roda.wui.client.planning.DetailsPanelRepresentationInformation; @@ -32,6 +34,7 @@ import com.google.gwt.user.client.ui.Widget; import config.i18n.client.ClientMessages; +import org.roda.wui.client.planning.DetailsPanelRisk; /** * @author Carlos Afonso @@ -78,6 +81,13 @@ public DetailsTab(TransferredResource resource) { content.add(detailsPanel); } + public DetailsTab(IndexedRisk risk) { + initWidget(uiBinder.createAndBindUi(this)); + + DetailsPanelRisk detailsPanel = new DetailsPanelRisk(risk, "RiskShowPanel_riskIncidences"); + content.add(detailsPanel); + } + public DetailsTab(Notification notification) { initWidget(uiBinder.createAndBindUi(this)); DetailsPanelNotification detailsPanel = new DetailsPanelNotification(notification); diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/BrowseRiskActionsToolbar.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/BrowseRiskActionsToolbar.java new file mode 100644 index 0000000000..86a6717002 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/BrowseRiskActionsToolbar.java @@ -0,0 +1,39 @@ +package org.roda.wui.client.common; + +import java.util.List; + +import org.roda.core.data.v2.risks.IndexedRisk; +import org.roda.wui.client.common.actions.RiskActions; +import org.roda.wui.client.common.actions.model.ActionableObject; +import org.roda.wui.client.common.actions.widgets.ActionableWidgetBuilder; + +/** + * + * @author Eduardo Teixeira + */ +public class BrowseRiskActionsToolbar extends BrowseObjectActionsToolbar { + public void buildIcon() { + setIcon("fa-solid fa-triangle-exclamation"); + } + + public void buildTags() { + // do nothing + } + + public void buildActions() { + this.actions.clear(); + if (object == null) + return; + + RiskActions riskActions = RiskActions.get(); + if (object.hasVersions()) { + riskActions = RiskActions.getWithHistory(); + } + this.actions + .add(new ActionableWidgetBuilder(riskActions).buildGroupedListWithObjects( + new ActionableObject<>(object), List.of(RiskActions.IndexedRiskAction.EDIT, + RiskActions.IndexedRiskAction.REMOVE, RiskActions.IndexedRiskAction.START_PROCESS), + List.of(RiskActions.IndexedRiskAction.START_PROCESS))); + } + +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/NavigationToolbar.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/NavigationToolbar.java index e14596bffc..178c49ae0a 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/NavigationToolbar.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/NavigationToolbar.java @@ -20,6 +20,7 @@ import org.roda.core.data.v2.ip.IndexedRepresentation; import org.roda.core.data.v2.ip.Permissions; import org.roda.core.data.v2.ip.TransferredResource; +import org.roda.core.data.v2.risks.IndexedRisk; import org.roda.wui.client.common.actions.Actionable; import org.roda.wui.client.common.lists.pagination.ListSelectionUtils; import org.roda.wui.client.common.lists.pagination.ListSelectionUtils.ProcessRelativeItem; @@ -233,6 +234,10 @@ public void updateBreadcrumb(TransferredResource transferredResource) { breadcrumb.updatePath(BreadcrumbUtils.getTransferredResourceBreadcrumbs(transferredResource)); } + public void updateBreadcrumb(IndexedRisk risk) { + breadcrumb.updatePath(BreadcrumbUtils.getRiskBreadCrumbs(risk)); + } + public void updateBreadcrumbPath(BreadcrumbItem... items) { updateBreadcrumbPath(Arrays.asList(items)); } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RiskActions.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RiskActions.java index 909bd9dd22..e262afebc3 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RiskActions.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RiskActions.java @@ -252,7 +252,7 @@ public ActionableBundle createActionsBundle() { ActionableBundle formatActionableBundle = new ActionableBundle<>(); // MANAGEMENT - ActionableGroup managementGroup = new ActionableGroup<>(messages.sidebarActionsTitle()); + ActionableGroup managementGroup = new ActionableGroup<>(messages.manage(), "btn-edit"); managementGroup.addButton(messages.riskHistoryButton(), IndexedRiskAction.HISTORY, ActionImpact.NONE, "btn-history"); managementGroup.addButton(messages.newButton(), IndexedRiskAction.NEW, ActionImpact.UPDATED, "btn-plus-circle"); diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java index ef342d269d..b0ff6341ef 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java @@ -22,6 +22,7 @@ import org.roda.core.data.v2.user.RODAMember; import org.roda.core.data.v2.log.LogEntry; import org.roda.core.data.v2.ri.RepresentationInformation; +import org.roda.core.data.v2.risks.IndexedRisk; import org.roda.wui.client.browse.BrowseTop; import org.roda.wui.client.management.ShowLogEntry; import org.roda.wui.client.management.UserLog; @@ -29,6 +30,8 @@ import org.roda.wui.client.disposal.DisposalDestroyedRecords; import org.roda.wui.client.ingest.appraisal.IngestAppraisal; import org.roda.wui.client.ingest.transfer.IngestTransfer; +import org.roda.wui.client.planning.RiskRegister; +import org.roda.wui.client.planning.ShowRisk; import org.roda.wui.client.management.members.MemberManagement; import org.roda.wui.client.management.members.ShowMember; import org.roda.wui.client.planning.RepresentationInformationNetwork; @@ -409,6 +412,21 @@ public static List getRepresentationInformationBreadCrumbs(Repre return ret; } + public static List getRiskBreadCrumbs(IndexedRisk risk) { + List ret = new ArrayList<>(); + + ret.add(new BreadcrumbItem(SafeHtmlUtils.fromSafeConstant(messages.riskRegisterTitle()), + messages.riskRegisterTitle(), RiskRegister.RESOLVER.getHistoryPath())); + + if (risk != null) { + List path = new ArrayList<>(ShowRisk.RESOLVER.getHistoryPath()); + String label = StringUtils.isNotBlank(risk.getName()) ? risk.getName() : risk.getId(); + ret.add(new BreadcrumbItem(SafeHtmlUtils.fromString(label), label, path)); + } + + return ret; + } + public static List getDipBreadcrumbs(IndexedDIP dip, DIPFile dipFile, List dipFileAncestors) { List ret = new ArrayList<>(); diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/DetailsPanelRisk.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/DetailsPanelRisk.java new file mode 100644 index 0000000000..ae49fb7fd5 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/DetailsPanelRisk.java @@ -0,0 +1,216 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE file at the root of the source + * tree and available online at + * + * https://github.com/keeps/roda + */ + +package org.roda.wui.client.planning; + +import com.google.gwt.safehtml.shared.SafeHtmlUtils; +import com.google.gwt.user.client.ui.InlineHTML; +import org.roda.core.data.v2.risks.Risk; +import org.roda.wui.client.common.utils.HtmlSnippetUtils; +import org.roda.wui.client.ingest.transfer.DetailsPanelTransferredResource; +import org.roda.wui.client.services.Services; +import org.roda.wui.common.client.tools.Humanize; +import org.roda.wui.common.client.tools.StringUtils; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.logical.shared.HasValueChangeHandlers; +import com.google.gwt.event.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.Widget; + +import config.i18n.client.ClientMessages; + +import java.util.List; + +/** + * @author Luis Faria + * + */ +public class DetailsPanelRisk extends Composite implements HasValueChangeHandlers { + + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); + + @UiField + FlowPanel detailsPanel; + + + public DetailsPanelRisk() { + initWidget(uiBinder.createAndBindUi(this)); + } + + public DetailsPanelRisk(String listId) { + initWidget(uiBinder.createAndBindUi(this)); + } + + public DetailsPanelRisk(Risk risk, String listId) { + initWidget(uiBinder.createAndBindUi(this)); + init(risk); + } + + public void init(Risk risk) { + // riskId.setText(risk.getId()); + addIfNotBlank(messages.riskIdentifier(), risk.getId()); + addIfNotBlank(messages.riskName(), risk.getName()); + addIfNotBlank(messages.riskDescription(), risk.getDescription()); + addIfNotBlank(messages.riskIdentifiedOn(), Humanize.formatDate(risk.getIdentifiedOn())); + addIfNotBlank(messages.riskIdentifiedBy(), risk.getIdentifiedBy()); + addIfNotBlank(messages.riskNotes(), risk.getNotes()); + + addCategoriesField(risk.getCategories()); + + addMitigationTermsFields(risk); + + // Pre-mitigation notes (termos vêm async abaixo) + addIfNotBlank(messages.riskPreMitigationNotes(), risk.getPreMitigationNotes()); + + // Post-mitigation notes + addIfNotBlank(messages.riskPostMitigationNotes(), risk.getPostMitigationNotes()); + + // Mitigation metadata + addIfNotBlank(messages.riskMitigationStrategy(), risk.getMitigationStrategy()); + addIfNotBlank(messages.riskMitigationOwner(), risk.getMitigationOwner()); + + } + + // public void clear() { + // riskId.setText(""); + // riskName.setText(""); + // riskDescriptionKey.setVisible(false); + // riskDescriptionValue.setText(""); + // riskIdentifiedOn.setText(""); + // riskIdentifiedBy.setText(""); + // riskCategories.clear(); + // riskNotesKey.setVisible(false); + // riskNotesValue.setText(""); + // + // riskPreMitigationProbability.setText(""); + // riskPreMitigationImpact.setText(""); + // riskPreMitigationSeverity.setText(""); + // riskPreMitigationNotesKey.setVisible(false); + // riskPreMitigationNotesValue.setText(""); + // + // riskPosMitigationKey.setVisible(false); + // riskPosMitigationProbabilityKey.setVisible(false); + // riskPosMitigationProbability.setText(""); + // riskPosMitigationImpactKey.setVisible(false); + // riskPosMitigationImpact.setText(""); + // riskPosMitigationSeverityKey.setVisible(false); + // riskPosMitigationSeverity.setText(""); + // riskPosMitigationNotesKey.setVisible(false); + // riskPosMitigationNotesValue.setText(""); + // + // riskMitigationKey.setVisible(false); + // riskMitigationStrategyKey.setVisible(false); + // riskMitigationStrategyValue.setText(""); + // riskMitigationOwnerTypeKey.setVisible(false); + // riskMitigationOwnerTypeValue.setText(""); + // riskMitigationOwnerKey.setVisible(false); + // riskMitigationOwnerValue.setText(""); + // riskMitigationRelatedEventIdentifierTypeKey.setVisible(false); + // riskMitigationRelatedEventIdentifierTypeValue.setText(""); + // riskMitigationRelatedEventIdentifierValueKey.setVisible(false); + // riskMitigationRelatedEventIdentifierValueValue.setText(""); + // } + + public void clear() { + detailsPanel.clear(); + } + + private void addIfNotBlank(String label, String value) { + if (StringUtils.isNotBlank(value)) { + detailsPanel.add(buildField(label, new InlineHTML(SafeHtmlUtils.htmlEscape(value)))); + } + } + + private void addCategoriesField(List categories) { + if (categories == null || categories.isEmpty()) { + return; + } + + FlowPanel categoriesPanel = new FlowPanel(); + categoriesPanel.addStyleName("risk-categories"); + for (String category : categories) { + if (StringUtils.isBlank(category)) { + continue; + } + InlineHTML tag = new InlineHTML( + "" + SafeHtmlUtils.htmlEscape(category) + ""); + categoriesPanel.add(tag); + } + + if (categoriesPanel.getWidgetCount() > 0) { + detailsPanel.add(buildField(messages.riskCategories(), categoriesPanel)); + } + } + + private Widget buildField(String label, Widget valueWidget) { + FlowPanel field = new FlowPanel(); + field.setStyleName("field"); + + Label l = new Label(label); + l.setStyleName("label"); + + FlowPanel value = new FlowPanel(); + value.setStyleName("value"); + value.add(valueWidget); + + field.add(l); + field.add(value); + return field; + } + + @Override + public HandlerRegistration addValueChangeHandler(ValueChangeHandler handler) { + return addHandler(handler, ValueChangeEvent.getType()); + } + + private void addMitigationTermsFields(Risk risk) { + final int preSeverity = risk.getPreMitigationSeverity(); + final int posSeverity = risk.getPostMitigationSeverity(); + + Services services = new Services("Retrieve risk mitigation terms", "get"); + services.riskResource(s -> s.retrieveRiskMitigationTerms(risk.getUUID())).whenComplete((terms, throwable) -> { + if (throwable != null || terms == null) { + return; + } + + int severityLowLimit = terms.getSeverityLowLimit(); + int severityHighLimit = terms.getSeverityHighLimit(); + + addIfNotBlank(messages.riskPreMitigationProbability(), + messages.riskMitigationProbability(terms.getPreMitigationProbability().replace(' ', '_'))); + addIfNotBlank(messages.riskPreMitigationImpact(), + messages.riskMitigationImpact(terms.getPreMitigationImpact().replace(' ', '_'))); + + InlineHTML preSeverityHtml = new InlineHTML( + HtmlSnippetUtils.getSeverityDefinition(preSeverity, severityLowLimit, severityHighLimit)); + detailsPanel.add(buildField(messages.riskPreMitigationSeverity(), preSeverityHtml)); + + addIfNotBlank(messages.riskPostMitigationProbability(), + messages.riskMitigationProbability(terms.getPosMitigationProbability().replace(' ', '_'))); + addIfNotBlank(messages.riskPostMitigationImpact(), + messages.riskMitigationImpact(terms.getPosMitigationImpact().replace(' ', '_'))); + + InlineHTML posSeverityHtml = new InlineHTML( + HtmlSnippetUtils.getSeverityDefinition(posSeverity, severityLowLimit, severityHighLimit)); + detailsPanel.add(buildField(messages.riskPostMitigationSeverity(), posSeverityHtml)); + }); + } + + interface MyUiBinder extends UiBinder { + Widget createAndBindUi(DetailsPanelRisk detailsPanelRisk); + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/DetailsPanelRisk.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/DetailsPanelRisk.ui.xml new file mode 100644 index 0000000000..395d049b06 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/DetailsPanelRisk.ui.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/RiskHistory.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/RiskHistory.java index 93f8eba2d9..eca46de268 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/RiskHistory.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/RiskHistory.java @@ -90,7 +90,7 @@ public String getHistoryToken() { ListBox list; @UiField(provided = true) - RiskShowPanel oldRisk; + DetailsPanelRisk oldRisk; @UiField Button buttonRevert; @UiField @@ -105,7 +105,7 @@ public RiskHistory(final String riskId, final RiskVersions versions) { this.riskId = riskId; this.riskVersions = versions; - oldRisk = new RiskShowPanel("RiskHistory_riskIncidences"); + oldRisk = new DetailsPanelRisk("RiskHistory_riskIncidences"); initWidget(uiBinder.createAndBindUi(this)); init(); diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/RiskHistory.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/RiskHistory.ui.xml index 724cc8b071..ed41e25724 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/RiskHistory.ui.xml +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/RiskHistory.ui.xml @@ -10,7 +10,7 @@ - + diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/RiskShowPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/RiskShowPanel.java deleted file mode 100644 index 58c54b9aaf..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/RiskShowPanel.java +++ /dev/null @@ -1,289 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE file at the root of the source - * tree and available online at - * - * https://github.com/keeps/roda - */ - -package org.roda.wui.client.planning; - -import org.roda.core.data.common.RodaConstants; -import org.roda.core.data.v2.index.filter.Filter; -import org.roda.core.data.v2.index.filter.SimpleFilterParameter; -import org.roda.core.data.v2.risks.Risk; -import org.roda.core.data.v2.risks.RiskIncidence; -import org.roda.wui.client.common.actions.RiskIncidenceActions; -import org.roda.wui.client.common.lists.RiskIncidenceList; -import org.roda.wui.client.common.lists.utils.AsyncTableCellOptions; -import org.roda.wui.client.common.lists.utils.ListBuilder; -import org.roda.wui.client.common.search.SearchWrapper; -import org.roda.wui.client.common.utils.HtmlSnippetUtils; -import org.roda.wui.client.services.Services; -import org.roda.wui.common.client.tools.Humanize; -import org.roda.wui.common.client.tools.StringUtils; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.event.logical.shared.HasValueChangeHandlers; -import com.google.gwt.event.logical.shared.ValueChangeEvent; -import com.google.gwt.event.logical.shared.ValueChangeHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.uibinder.client.UiBinder; -import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.Widget; - -import config.i18n.client.ClientMessages; - -/** - * @author Luis Faria - * - */ -public class RiskShowPanel extends Composite implements HasValueChangeHandlers { - - private static final ClientMessages messages = GWT.create(ClientMessages.class); - private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); - @UiField - Label riskId; - @UiField - Label riskName; - @UiField - Label riskDescriptionKey, riskDescriptionValue; - @UiField - Label riskIdentifiedOn; - @UiField - Label riskIdentifiedBy; - @UiField - FlowPanel riskCategories; - @UiField - Label riskNotesKey, riskNotesValue; - @UiField - Label riskPreMitigationKey; - @UiField - Label riskPreMitigationProbability; - @UiField - Label riskPreMitigationImpact; - @UiField - HTML riskPreMitigationSeverity; - @UiField - Label riskPreMitigationNotesKey, riskPreMitigationNotesValue; - @UiField - Label riskPosMitigationKey; - @UiField - Label riskPosMitigationProbabilityKey; - @UiField - Label riskPosMitigationProbability; - @UiField - Label riskPosMitigationImpactKey; - @UiField - Label riskPosMitigationImpact; - @UiField - Label riskPosMitigationSeverityKey; - @UiField - HTML riskPosMitigationSeverity; - @UiField - Label riskPosMitigationNotesKey, riskPosMitigationNotesValue; - @UiField - Label riskMitigationKey; - @UiField - Label riskMitigationStrategyKey, riskMitigationStrategyValue; - @UiField - Label riskMitigationOwnerTypeKey, riskMitigationOwnerTypeValue; - @UiField - Label riskMitigationOwnerKey, riskMitigationOwnerValue; - @UiField - Label riskMitigationRelatedEventIdentifierTypeKey, riskMitigationRelatedEventIdentifierTypeValue; - @UiField - Label riskMitigationRelatedEventIdentifierValueKey, riskMitigationRelatedEventIdentifierValueValue; - @UiField(provided = true) - SearchWrapper searchWrapper; - - public RiskShowPanel(String listId) { - - ListBuilder riskIncidenceListBuilder = new ListBuilder<>(() -> new RiskIncidenceList(), - new AsyncTableCellOptions<>(RiskIncidence.class, listId).withSummary(messages.riskIncidences()) - .withSearchPlaceholder(messages.riskIncidenceRegisterSearchPlaceHolder()).bindOpener() - .withActionable(RiskIncidenceActions.getForMultipleEdit())); - - searchWrapper = new SearchWrapper(false).createListAndSearchPanel(riskIncidenceListBuilder); - - initWidget(uiBinder.createAndBindUi(this)); - } - - public RiskShowPanel(Risk risk, String listId) { - Filter filter = new Filter(new SimpleFilterParameter(RodaConstants.RISK_INCIDENCE_RISK_ID, risk.getId())); - - ListBuilder riskIncidenceListBuilder = new ListBuilder<>(() -> new RiskIncidenceList(), - new AsyncTableCellOptions<>(RiskIncidence.class, listId).withSummary(messages.riskIncidences()).withFilter(filter) - .bindOpener().withSearchPlaceholder(messages.riskIncidenceRegisterSearchPlaceHolder()) - .withActionable(RiskIncidenceActions.getForMultipleEdit())); - - searchWrapper = new SearchWrapper(false).createListAndSearchPanel(riskIncidenceListBuilder); - - initWidget(uiBinder.createAndBindUi(this)); - init(risk); - } - - public void init(Risk risk) { - riskId.setText(risk.getId()); - riskName.setText(risk.getName()); - - riskDescriptionValue.setText(risk.getDescription()); - riskDescriptionKey.setVisible(StringUtils.isNotBlank(risk.getDescription())); - - riskIdentifiedOn.setText(Humanize.formatDate(risk.getIdentifiedOn())); - riskIdentifiedBy.setText(risk.getIdentifiedBy()); - riskNotesValue.setText(risk.getNotes()); - riskNotesKey.setVisible(StringUtils.isNotBlank(risk.getNotes())); - - for (String category : risk.getCategories()) { - Label categoryLabel = new Label(category); - categoryLabel.addStyleName("value"); - riskCategories.add(categoryLabel); - } - - final int preSeverity = risk.getPreMitigationSeverity(); - final int posSeverity = risk.getPostMitigationSeverity(); - - Services services = new Services("Retrieve risk mitigation terms", "get"); - services.riskResource(s -> s.retrieveRiskMitigationTerms(risk.getUUID())) - .whenComplete((riskMitigationTerms, throwable) -> { - if (throwable == null) { - int severityLowLimit = riskMitigationTerms.getSeverityLowLimit(); - int severityHighLimit = riskMitigationTerms.getSeverityHighLimit(); - - riskPreMitigationProbability.setText( - messages.riskMitigationProbability(riskMitigationTerms.getPreMitigationProbability().replace(' ', '_'))); - riskPreMitigationImpact - .setText(messages.riskMitigationImpact(riskMitigationTerms.getPreMitigationImpact().replace(' ', '_'))); - - riskPreMitigationSeverity - .setHTML(HtmlSnippetUtils.getSeverityDefinition(preSeverity, severityLowLimit, severityHighLimit)); - - riskPosMitigationProbability.setText( - messages.riskMitigationProbability(riskMitigationTerms.getPosMitigationProbability().replace(' ', '_'))); - riskPosMitigationImpact - .setText(messages.riskMitigationImpact(riskMitigationTerms.getPosMitigationImpact().replace(' ', '_'))); - - riskPosMitigationKey.setVisible(true); - riskPosMitigationProbabilityKey.setVisible(true); - riskPosMitigationProbability.setVisible(true); - riskPosMitigationImpactKey.setVisible(true); - riskPosMitigationImpact.setVisible(true); - riskPosMitigationSeverityKey.setVisible(true); - riskPosMitigationSeverity.setVisible(true); - riskPosMitigationSeverity - .setHTML(HtmlSnippetUtils.getSeverityDefinition(posSeverity, severityLowLimit, severityHighLimit)); - } - }); - - riskPreMitigationNotesValue.setText(risk.getPreMitigationNotes()); - riskPreMitigationNotesKey.setVisible(StringUtils.isNotBlank(risk.getPreMitigationNotes())); - - riskPosMitigationNotesValue.setText(risk.getPostMitigationNotes()); - riskPosMitigationNotesKey.setVisible(StringUtils.isNotBlank(risk.getPostMitigationNotes())); - - int mitigationCounter = 0; - - if (StringUtils.isNotBlank(risk.getMitigationStrategy())) { - mitigationCounter++; - riskMitigationStrategyKey.setVisible(true); - riskMitigationStrategyValue.setText(risk.getMitigationStrategy()); - } else { - riskMitigationStrategyKey.setVisible(false); - } - - if (StringUtils.isNotBlank(risk.getMitigationOwnerType())) { - mitigationCounter++; - riskMitigationOwnerTypeKey.setVisible(true); - riskMitigationOwnerTypeValue.setText(risk.getMitigationOwnerType()); - } else { - riskMitigationOwnerTypeKey.setVisible(false); - } - - if (StringUtils.isNotBlank(risk.getMitigationOwner())) { - mitigationCounter++; - riskMitigationOwnerKey.setVisible(true); - riskMitigationOwnerValue.setText(risk.getMitigationOwner()); - } else { - riskMitigationOwnerKey.setVisible(false); - } - - if (StringUtils.isNotBlank(risk.getMitigationRelatedEventIdentifierType())) { - mitigationCounter++; - riskMitigationRelatedEventIdentifierTypeKey.setVisible(true); - riskMitigationRelatedEventIdentifierTypeValue.setText(risk.getMitigationRelatedEventIdentifierType()); - } else { - riskMitigationRelatedEventIdentifierTypeKey.setVisible(false); - } - - if (StringUtils.isNotBlank(risk.getMitigationRelatedEventIdentifierValue())) { - mitigationCounter++; - riskMitigationRelatedEventIdentifierValueKey.setVisible(true); - riskMitigationRelatedEventIdentifierValueValue.setText(risk.getMitigationRelatedEventIdentifierValue()); - } else { - riskMitigationRelatedEventIdentifierValueKey.setVisible(false); - } - - riskMitigationKey.setVisible(mitigationCounter != 0); - - // FIXME it must be visible later - riskMitigationOwnerTypeKey.setVisible(false); - riskMitigationOwnerTypeValue.setVisible(false); - riskMitigationRelatedEventIdentifierTypeKey.setVisible(false); - riskMitigationRelatedEventIdentifierTypeValue.setVisible(false); - riskMitigationRelatedEventIdentifierValueKey.setVisible(false); - riskMitigationRelatedEventIdentifierValueValue.setVisible(false); - } - - public void clear() { - riskId.setText(""); - riskName.setText(""); - riskDescriptionKey.setVisible(false); - riskDescriptionValue.setText(""); - riskIdentifiedOn.setText(""); - riskIdentifiedBy.setText(""); - riskCategories.clear(); - riskNotesKey.setVisible(false); - riskNotesValue.setText(""); - - riskPreMitigationProbability.setText(""); - riskPreMitigationImpact.setText(""); - riskPreMitigationSeverity.setText(""); - riskPreMitigationNotesKey.setVisible(false); - riskPreMitigationNotesValue.setText(""); - - riskPosMitigationKey.setVisible(false); - riskPosMitigationProbabilityKey.setVisible(false); - riskPosMitigationProbability.setText(""); - riskPosMitigationImpactKey.setVisible(false); - riskPosMitigationImpact.setText(""); - riskPosMitigationSeverityKey.setVisible(false); - riskPosMitigationSeverity.setText(""); - riskPosMitigationNotesKey.setVisible(false); - riskPosMitigationNotesValue.setText(""); - - riskMitigationKey.setVisible(false); - riskMitigationStrategyKey.setVisible(false); - riskMitigationStrategyValue.setText(""); - riskMitigationOwnerTypeKey.setVisible(false); - riskMitigationOwnerTypeValue.setText(""); - riskMitigationOwnerKey.setVisible(false); - riskMitigationOwnerValue.setText(""); - riskMitigationRelatedEventIdentifierTypeKey.setVisible(false); - riskMitigationRelatedEventIdentifierTypeValue.setText(""); - riskMitigationRelatedEventIdentifierValueKey.setVisible(false); - riskMitigationRelatedEventIdentifierValueValue.setText(""); - } - - @Override - public HandlerRegistration addValueChangeHandler(ValueChangeHandler handler) { - return addHandler(handler, ValueChangeEvent.getType()); - } - - interface MyUiBinder extends UiBinder { - } -} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/RiskShowPanel.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/RiskShowPanel.ui.xml deleted file mode 100644 index 1e4cdfb682..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/RiskShowPanel.ui.xml +++ /dev/null @@ -1,154 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/ShowRisk.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/ShowRisk.java index c8e59cdcf9..1900726da0 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/ShowRisk.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/ShowRisk.java @@ -13,17 +13,19 @@ import java.util.List; import org.roda.core.data.v2.risks.IndexedRisk; +import org.roda.wui.client.browse.tabs.BrowseRiskTabs; +import org.roda.wui.client.common.BrowseRiskActionsToolbar; +import org.roda.wui.client.common.NavigationToolbar; import org.roda.wui.client.common.NoAsyncCallback; +import org.roda.wui.client.common.TitlePanel; import org.roda.wui.client.common.UserLogin; import org.roda.wui.client.common.actions.Actionable; -import org.roda.wui.client.common.actions.RiskActions; -import org.roda.wui.client.common.actions.model.ActionableObject; -import org.roda.wui.client.common.actions.widgets.ActionableWidgetBuilder; -import org.roda.wui.client.common.utils.SidebarUtils; +import org.roda.wui.client.main.BreadcrumbUtils; import org.roda.wui.client.services.Services; import org.roda.wui.common.client.HistoryResolver; import org.roda.wui.common.client.tools.HistoryUtils; import org.roda.wui.common.client.tools.ListUtils; +import org.roda.wui.common.client.tools.StringUtils; import com.google.gwt.core.client.GWT; import com.google.gwt.i18n.client.LocaleInfo; @@ -31,8 +33,7 @@ import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.FocusPanel; import com.google.gwt.user.client.ui.Widget; import config.i18n.client.ClientMessages; @@ -47,7 +48,7 @@ public class ShowRisk extends Composite { @Override public void resolve(List historyTokens, final AsyncCallback callback) { - getInstance().resolve(historyTokens, callback); + resolveShowRisk(historyTokens, callback); } @Override @@ -66,14 +67,7 @@ public String getHistoryToken() { } }; - private static ShowRisk instance = null; private static final ClientMessages messages = GWT.create(ClientMessages.class); - - interface MyUiBinder extends UiBinder { - } - - private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); - private static final AsyncCallback actionCallback = new NoAsyncCallback() { @Override public void onSuccess(Actionable.ActionImpact result) { @@ -82,69 +76,61 @@ public void onSuccess(Actionable.ActionImpact result) { } } }; - - @UiField(provided = true) - RiskShowPanel riskShowPanel; - + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); @UiField - SimplePanel actionsSidebar; + FocusPanel keyboardFocus; @UiField - FlowPanel contentFlowPanel; + NavigationToolbar navigationToolbar; @UiField - FlowPanel sidebarFlowPanel; + TitlePanel title; - /** - * Create a new panel to view a risk - */ - public ShowRisk() { - this.riskShowPanel = new RiskShowPanel("RiskShowPanel_riskIncidences"); - initWidget(uiBinder.createAndBindUi(this)); - } + @UiField(provided = true) + BrowseRiskTabs riskTabs; + + @UiField + BrowseRiskActionsToolbar actionsToolbar; public ShowRisk(IndexedRisk risk) { - this.riskShowPanel = new RiskShowPanel(risk, "RiskShowPanel_riskIncidences"); + riskTabs = new BrowseRiskTabs(); + riskTabs.init(risk); initWidget(uiBinder.createAndBindUi(this)); - // actionsSidebar is set in the resolve callback + keyboardFocus.setFocus(true); + keyboardFocus.addStyleName("browse browse-file browse_main_panel"); + + navigationToolbar.withoutButtons().build(); + navigationToolbar.updateBreadcrumbPath(BreadcrumbUtils.getRiskBreadCrumbs(risk)); + actionsToolbar.setLabel(messages.showRiskTitle()); + actionsToolbar.setObjectAndBuild(risk, null, actionCallback); + title.setText(StringUtils.isNotBlank(risk.getName()) ? risk.getName() : risk.getId()); } - public static ShowRisk getInstance() { - if (instance == null) { - instance = new ShowRisk(); - } - return instance; - } + private static void resolveShowRisk(List historyTokens, final AsyncCallback callback) { - void resolve(List historyTokens, final AsyncCallback callback) { + if (historyTokens.size() != 1) { + HistoryUtils.newHistory(RiskRegister.RESOLVER); + callback.onSuccess(null); + return; + } - if (historyTokens.size() == 1) { - Services services = new Services("Retrieve indexed risk", "get"); - SidebarUtils.showSidebar(contentFlowPanel, sidebarFlowPanel); + Services services = new Services("Retrieve indexed risk", "get"); + String riskUUID = historyTokens.get(0); - services - .rodaEntityRestService(s -> s.findByUuid(historyTokens.get(0), LocaleInfo.getCurrentLocale().getLocaleName()), - IndexedRisk.class) - .thenCompose(indexedRisk -> services.riskResource(s -> s.retrieveRiskVersions(historyTokens.get(0))) + services + .rodaEntityRestService(s -> s.findByUuid(riskUUID, LocaleInfo.getCurrentLocale().getLocaleName()), + IndexedRisk.class) + .thenCompose(indexedRisk -> services.riskResource(s -> s.retrieveRiskVersions(riskUUID)) .whenComplete((result, throwable) -> { if (throwable == null) { indexedRisk.setHasVersions(!result.getVersions().isEmpty()); - instance = new ShowRisk(indexedRisk); - RiskActions riskActions = RiskActions.get(); - if (indexedRisk.hasVersions()) { - riskActions = RiskActions.getWithHistory(); - } - SidebarUtils.toggleSidebar(contentFlowPanel, sidebarFlowPanel, riskActions.hasAnyRoles()); - instance.actionsSidebar.setWidget(new ActionableWidgetBuilder<>(riskActions).withBackButton() - .withActionCallback(actionCallback).buildListWithObjects(new ActionableObject<>(indexedRisk))); - callback.onSuccess(instance); + callback.onSuccess(new ShowRisk(indexedRisk)); } else { callback.onFailure(throwable); } })); - } else { - HistoryUtils.newHistory(RiskRegister.RESOLVER); - callback.onSuccess(null); - } + } + + interface MyUiBinder extends UiBinder { } } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/ShowRisk.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/ShowRisk.ui.xml index 1347a69ca3..afc42b4b54 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/ShowRisk.ui.xml +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/planning/ShowRisk.ui.xml @@ -1,24 +1,20 @@ + xmlns:tab="urn:import:org.roda.wui.client.browse.tabs" xmlns:common="urn:import:org.roda.wui.client.common" +> - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + From 9061fafd9a43df6adc32bbe5f15f4c71f7c37452 Mon Sep 17 00:00:00 2001 From: Luis Faria Date: Mon, 27 Apr 2026 16:26:08 +0100 Subject: [PATCH 06/37] Add .claude/ to .gitignore Co-Authored-By: Claude Sonnet 4.6 --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5b807a3430..a166ccbfba 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ roda-installer/roda-installer_v* **/.DS_Store roda-core/roda-core/velocity.log.1 .idea/ +.claude/ *.iml independent_plugin_script.sh compile_plugin.sh From 130598832f77c208fb4f34be45d9b80085633da6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Guimar=C3=A3es?= Date: Tue, 21 Apr 2026 16:02:33 +0100 Subject: [PATCH 07/37] Fix BreadcrumbUtils imports --- .../roda/wui/client/main/BreadcrumbUtils.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java index b0ff6341ef..f19c170c03 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java @@ -19,12 +19,15 @@ import org.roda.core.data.v2.ip.IndexedFile; import org.roda.core.data.v2.ip.IndexedRepresentation; import org.roda.core.data.v2.ip.TransferredResource; +import org.roda.core.data.v2.notifications.Notification; import org.roda.core.data.v2.user.RODAMember; import org.roda.core.data.v2.log.LogEntry; import org.roda.core.data.v2.ri.RepresentationInformation; import org.roda.core.data.v2.risks.IndexedRisk; import org.roda.wui.client.browse.BrowseTop; +import org.roda.wui.client.management.NotificationRegister; import org.roda.wui.client.management.ShowLogEntry; +import org.roda.wui.client.management.ShowNotification; import org.roda.wui.client.management.UserLog; import org.roda.wui.client.browse.PreservationEvents; import org.roda.wui.client.disposal.DisposalDestroyedRecords; @@ -367,8 +370,8 @@ public static List getTransferredResourceBreadcrumbs(Transferred public static List getLogEntryBreadcrumbs(LogEntry logEntry) { List ret = new ArrayList<>(); - ret.add(new BreadcrumbItem(SafeHtmlUtils.fromSafeConstant(messages.activityLogTitle()), - messages.activityLogTitle(), UserLog.RESOLVER.getHistoryPath())); + ret.add(new BreadcrumbItem(SafeHtmlUtils.fromSafeConstant(messages.activityLogTitle()), messages.activityLogTitle(), + UserLog.RESOLVER.getHistoryPath())); if (logEntry != null) { List path = new ArrayList<>(ShowLogEntry.RESOLVER.getHistoryPath()); @@ -395,12 +398,10 @@ public static List getNotificationBreadcrumbs(Notification notif return ret; } - public static List getRepresentationInformationBreadCrumbs(RepresentationInformation ri){ + public static List getRepresentationInformationBreadCrumbs(RepresentationInformation ri) { List ret = new ArrayList<>(); - ret.add(new BreadcrumbItem( - SafeHtmlUtils.fromSafeConstant(messages.representationInformationTitle()), - messages.representationInformationTitle(), - RepresentationInformationNetwork.RESOLVER.getHistoryPath())); + ret.add(new BreadcrumbItem(SafeHtmlUtils.fromSafeConstant(messages.representationInformationTitle()), + messages.representationInformationTitle(), RepresentationInformationNetwork.RESOLVER.getHistoryPath())); if (ri != null) { List path = new ArrayList<>(ShowRepresentationInformation.RESOLVER.getHistoryPath()); @@ -513,8 +514,8 @@ private static SafeHtml getBreadcrumbLabel(IndexedAIP aip) { public static List getRODAMemberBreadcrumbs(RODAMember user) { List ret = new ArrayList<>(); - ret.add(new BreadcrumbItem(SafeHtmlUtils.fromSafeConstant(messages.usersAndGroupsTitle()), messages.usersAndGroupsTitle(), - MemberManagement.RESOLVER.getHistoryPath())); + ret.add(new BreadcrumbItem(SafeHtmlUtils.fromSafeConstant(messages.usersAndGroupsTitle()), + messages.usersAndGroupsTitle(), MemberManagement.RESOLVER.getHistoryPath())); if (user != null) { List path = new ArrayList<>(ShowMember.RESOLVER.getHistoryPath()); From bc18fc46d16467485272f6e696c82e0f00b68c7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Guimar=C3=A3es?= Date: Tue, 21 Apr 2026 15:42:11 +0100 Subject: [PATCH 08/37] Apply new layout to disposal policy page (schedule, holds and rules) #3372 #3370 --- .../roda/core/data/common/RodaConstants.java | 37 +- .../core/data/v2/common/RODAObjectList.java | 6 +- .../data/v2/disposal/hold/DisposalHold.java | 46 ++- .../v2/disposal/hold/DisposalHoldState.java | 6 +- .../v2/disposal/rule/ChangeOrderRequest.java | 53 +++ .../data/v2/disposal/rule/ConditionType.java | 6 +- .../data/v2/disposal/rule/DisposalRule.java | 41 ++- .../data/v2/disposal/rule/DisposalRules.java | 3 + .../data/v2/disposal/rule/OrderPositions.java | 9 + .../disposal/schedule/DisposalSchedule.java | 55 ++- .../schedule/DisposalScheduleState.java | 6 +- .../schedule/IndexedDisposalSchedule.java | 47 +++ .../schedule/RetentionPeriodIntervalCode.java | 6 +- .../v2/index/select/SelectedItemsList.java | 2 - .../v2/representation/ChangeTypeRequest.java | 4 - .../roda/core/index/IndexModelObserver.java | 32 ++ .../org/roda/core/index/IndexService.java | 33 ++ .../index/schema/SolrCollectionRegistry.java | 26 +- .../collections/DisposalHoldCollection.java | 108 ++++++ .../collections/DisposalRuleCollection.java | 114 ++++++ .../DisposalScheduleCollection.java | 132 +++++++ .../roda/core/model/DefaultModelService.java | 45 ++- .../DefaultTransactionalModelService.java | 32 +- .../core/model/LiteRODAObjectFactory.java | 6 + .../org/roda/core/model/ModelObservable.java | 139 ++++---- .../org/roda/core/model/ModelObserver.java | 15 + .../org/roda/core/model/utils/ModelUtils.java | 15 + .../core/model/utils/ResourceParseUtils.java | 12 +- .../org/roda/core/plugins/PluginHelper.java | 15 + .../disposal/hold/LiftDisposalHoldPlugin.java | 4 +- .../reindex/ReindexDisposalHoldPlugin.java | 47 +++ .../reindex/ReindexDisposalRulePlugin.java | 47 +++ .../ReindexDisposalSchedulePlugin.java | 47 +++ .../resources/config/roda-roles.properties | 10 +- .../config/i18n/client/ClientMessages.java | 28 +- .../v2/controller/DisposalHoldController.java | 41 +++ .../v2/controller/DisposalRuleController.java | 69 +++- .../DisposalScheduleController.java | 61 +++- .../api/v2/services/DisposalRuleService.java | 75 ++++ .../v2/services/DisposalScheduleService.java | 2 +- .../org/roda/wui/client/GenericDataPanel.java | 12 + .../client/browse/tabs/DisposalHoldTabs.java | 57 +++ .../browse/tabs/DisposalPolicyTabs.java | 70 ++++ .../client/browse/tabs/DisposalRuleTabs.java | 23 ++ .../browse/tabs/DisposalScheduleTabs.java | 57 +++ .../org/roda/wui/client/browse/tabs/Tabs.java | 62 +++- .../common/BrowseObjectActionsToolbar.java | 11 + ...ateOrUpdateDisposalHoldActionsToolbar.java | 77 ++++ ...ateOrUpdateDisposalRuleActionsToolbar.java | 78 +++++ ...rUpdateDisposalScheduleActionsToolbar.java | 78 +++++ .../common/DisposalHoldActionsToolbar.java | 38 ++ .../common/DisposalRuleActionsToolbar.java | 38 ++ .../DisposalScheduleActionsToolbar.java | 44 +++ .../wui/client/common/NavigationToolbar.java | 11 +- .../client/common/NavigationToolbar.ui.xml | 58 ++- .../common/actions/ActionableInterceptor.java | 78 +++++ .../common/actions/DisposalHoldAction.java | 32 ++ .../common/actions/DisposalHoldActions.java | 236 +++---------- .../DisposalHoldSearchWrapperActions.java | 133 +++++++ .../actions/DisposalHoldToolbarActions.java | 152 ++++++++ .../common/actions/DisposalRuleAction.java | 31 ++ .../common/actions/DisposalRuleActions.java | 80 +++++ .../DisposalRuleSearchWrapperActions.java | 181 ++++++++++ .../actions/DisposalRuleToolbarActions.java | 98 ++++++ .../actions/DisposalScheduleAction.java | 31 ++ .../actions/DisposalScheduleActions.java | 217 +++--------- .../DisposalScheduleSearchWrapperActions.java | 129 +++++++ .../DisposalScheduleToolbarActions.java | 148 ++++++++ ...edAIPDisposalHoldSearchWrapperActions.java | 198 +++++++++++ ...PDisposalScheduleSearchWrapperActions.java | 170 +++++++++ .../actions/RODAMemberToolbarActions.java | 4 - .../client/common/actions/RiskActions.java | 1 - .../widgets/ActionableWidgetBuilder.java | 34 +- .../common/dialogs/DisposalDialogs.java | 101 +++++- .../roda/wui/client/common/labels/Tag.java | 4 +- .../client/common/lists/DisposalHoldList.java | 105 ++++++ .../client/common/lists/DisposalRuleList.java | 106 ++++++ .../common/lists/DisposalScheduleList.java | 124 +++++++ .../pagination/ListSelectionStateMappers.java | 87 +++-- .../lists/utils/AsyncTableCellOptions.java | 1 - .../roda/wui/client/common/resources/main.gss | 47 +-- .../client/common/utils/FormUtilities.java | 17 + .../client/common/utils/HtmlSnippetUtils.java | 18 +- .../disposal/hold/CreateDisposalHold.java | 82 +++-- .../disposal/hold/CreateDisposalHold.ui.xml | 35 +- .../disposal/hold/DisposalHoldDataPanel.java | 3 +- .../disposal/hold/EditDisposalHold.java | 96 +++-- .../disposal/hold/EditDisposalHold.ui.xml | 35 +- .../disposal/hold/ShowDisposalHold.java | 311 ++++------------- .../disposal/hold/ShowDisposalHold.ui.xml | 78 +---- .../hold/tabs/DisposalHoldDetailsPanel.java | 117 +++++++ .../hold/tabs/DisposalHoldDetailsPanel.ui.xml | 16 + .../disposal/policy/DisposalPolicy.java | 206 +---------- .../disposal/policy/DisposalPolicy.ui.xml | 38 +- .../client/disposal/rule/ChildOfPanel.java | 10 + .../disposal/rule/CreateDisposalRule.java | 133 +++---- .../disposal/rule/CreateDisposalRule.ui.xml | 44 +-- .../disposal/rule/DisposalRuleDataPanel.java | 9 +- .../rule/DisposalRuleDataPanel.ui.xml | 3 +- .../disposal/rule/EditDisposalRule.java | 131 +++---- .../disposal/rule/EditDisposalRule.ui.xml | 35 +- .../disposal/rule/ShowDisposalRule.java | 228 +++--------- .../disposal/rule/ShowDisposalRule.ui.xml | 74 +--- .../rule/tabs/DisposalRuleDetailsPanel.java | 130 +++++++ .../rule/tabs/DisposalRuleDetailsPanel.ui.xml | 16 + .../schedule/CreateDisposalSchedule.java | 87 ++--- .../schedule/CreateDisposalSchedule.ui.xml | 36 +- .../schedule/DisposalScheduleDataPanel.java | 5 +- .../schedule/EditDisposalSchedule.java | 92 ++--- .../schedule/EditDisposalSchedule.ui.xml | 35 +- .../schedule/ShowDisposalSchedule.java | 329 ++++-------------- .../schedule/ShowDisposalSchedule.ui.xml | 94 +---- .../tabs/DisposalScheduleDetailsPanel.java | 120 +++++++ .../tabs/DisposalScheduleDetailsPanel.ui.xml | 16 + .../roda/wui/client/main/BreadcrumbUtils.java | 143 ++++++++ .../management/members/MemberManagement.java | 5 +- .../services/DisposalHoldRestService.java | 3 +- .../services/DisposalRuleRestService.java | 12 +- .../services/DisposalScheduleRestService.java | 3 +- .../roda/wui/client/services/Services.java | 9 + .../wui/common/client/tools/HistoryUtils.java | 16 + .../config/i18n/ServerMessages.properties | 25 +- .../i18n/ServerMessages_pt_PT.properties | 24 ++ .../i18n/client/ClientMessages.properties | 35 +- .../client/ClientMessages_de_AT.properties | 6 +- .../client/ClientMessages_de_DE.properties | 6 +- .../i18n/client/ClientMessages_es.properties | 8 +- .../client/ClientMessages_pt_PT.properties | 29 +- .../main/resources/config/roda-wui.properties | 24 ++ 129 files changed, 5587 insertions(+), 2164 deletions(-) create mode 100644 roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/rule/ChangeOrderRequest.java create mode 100644 roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/rule/OrderPositions.java create mode 100644 roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/schedule/IndexedDisposalSchedule.java create mode 100644 roda-core/roda-core/src/main/java/org/roda/core/index/schema/collections/DisposalHoldCollection.java create mode 100644 roda-core/roda-core/src/main/java/org/roda/core/index/schema/collections/DisposalRuleCollection.java create mode 100644 roda-core/roda-core/src/main/java/org/roda/core/index/schema/collections/DisposalScheduleCollection.java create mode 100644 roda-core/roda-core/src/main/java/org/roda/core/plugins/base/maintenance/reindex/ReindexDisposalHoldPlugin.java create mode 100644 roda-core/roda-core/src/main/java/org/roda/core/plugins/base/maintenance/reindex/ReindexDisposalRulePlugin.java create mode 100644 roda-core/roda-core/src/main/java/org/roda/core/plugins/base/maintenance/reindex/ReindexDisposalSchedulePlugin.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/GenericDataPanel.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DisposalHoldTabs.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DisposalPolicyTabs.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DisposalRuleTabs.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DisposalScheduleTabs.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/CreateOrUpdateDisposalHoldActionsToolbar.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/CreateOrUpdateDisposalRuleActionsToolbar.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/CreateOrUpdateDisposalScheduleActionsToolbar.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/DisposalHoldActionsToolbar.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/DisposalRuleActionsToolbar.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/DisposalScheduleActionsToolbar.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/ActionableInterceptor.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalHoldAction.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalHoldSearchWrapperActions.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalHoldToolbarActions.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalRuleAction.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalRuleSearchWrapperActions.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalRuleToolbarActions.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalScheduleAction.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalScheduleSearchWrapperActions.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalScheduleToolbarActions.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/IndexedAIPDisposalHoldSearchWrapperActions.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/IndexedAIPDisposalScheduleSearchWrapperActions.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/DisposalHoldList.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/DisposalRuleList.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/DisposalScheduleList.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/tabs/DisposalHoldDetailsPanel.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/tabs/DisposalHoldDetailsPanel.ui.xml create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/tabs/DisposalRuleDetailsPanel.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/tabs/DisposalRuleDetailsPanel.ui.xml create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/tabs/DisposalScheduleDetailsPanel.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/tabs/DisposalScheduleDetailsPanel.ui.xml diff --git a/roda-common/roda-common-data/src/main/java/org/roda/core/data/common/RodaConstants.java b/roda-common/roda-common-data/src/main/java/org/roda/core/data/common/RodaConstants.java index 7f90cd73d4..1f59483aff 100644 --- a/roda-common/roda-common-data/src/main/java/org/roda/core/data/common/RodaConstants.java +++ b/roda-common/roda-common-data/src/main/java/org/roda/core/data/common/RodaConstants.java @@ -10,8 +10,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.List; +import org.roda.core.data.v2.disposal.schedule.DisposalActionCode; +import org.roda.core.data.v2.disposal.schedule.DisposalScheduleState; +import org.roda.core.data.v2.disposal.schedule.RetentionPeriodIntervalCode; import org.roda.core.data.v2.ip.Permissions.PermissionType; public final class RodaConstants { @@ -932,6 +936,9 @@ public enum OrchestratorType { public static final String INDEX_DIP_FILE = "DIPFile"; public static final String INDEX_REPRESENTATION_INFORMATION = "RepresentationInformation"; public static final String INDEX_DISPOSAL_CONFIRMATION = "DisposalConfirmation"; + public static final String INDEX_DISPOSAL_SCHEDULE = "DisposalSchedule"; + public static final String INDEX_DISPOSAL_HOLD = "DisposalHold"; + public static final String INDEX_DISPOSAL_RULE = "DisposalRule"; /* * INDEXED CLASSES @@ -946,7 +953,10 @@ public enum OrchestratorType { "org.roda.core.data.v2.risks.RiskIncidence", "org.roda.core.data.v2.ri.RepresentationInformation", "org.roda.core.data.v2.ip.TransferredResource", "org.roda.core.data.v2.user.User", "org.roda.core.data.v2.user.Group", "org.roda.core.data.v2.user.RODAMember", - "org.roda.core.data.v2.ip.disposal.DisposalConfirmation", "org.roda.core.data.v2.user.RodaPrincipal", + "org.roda.core.data.v2.disposal.hold.DisposalHold", + "org.roda.core.data.v2.disposal.schedule.DisposalSchedule", + "org.roda.core.data.v2.disposal.rule.DisposalRule", + "org.roda.core.data.v2.disposal.confirmation.DisposalConfirmation", "org.roda.core.data.v2.user.RodaPrincipal", "org.roda.core.data.v2.ip.AIP", "org.roda.core.data.v2.risks.Risk", "org.roda.core.events.pekko.CRDTWrapper", "org.roda.core.data.v2.ip.DIP", "org.roda.core.data.v2.ip.metadata.DescriptiveMetadata", "org.roda.core.data.v2.ip.disposal.DisposalConfirmationAIPEntry", @@ -1307,8 +1317,32 @@ public enum OrchestratorType { public static final String DISPOSAL_CONFIRMATION_EXTRA_INFO = "extraInformation"; public static final String DISPOSAL_CONFIRMATION_STORAGE_SIZE = "size"; + public static final String DISPOSAL_SCHEDULE_TITLE = "title"; + public static final String DISPOSAL_SCHEDULE_DESCRIPTION = "description"; + public static final String DISPOSAL_SCHEDULE_MANDATE = "mandate"; + public static final String DISPOSAL_SCHEDULE_SCOPE_NOTES = "scopeNotes"; + public static final String DISPOSAL_SCHEDULE_STATE = "state"; + public static final String DISPOSAL_SCHEDULE_ACTION = "action"; + public static final String DISPOSAL_SCHEDULE_RETENTION_PERIOD_INTERVAL_CODE = "retentionPeriodIntervalCode"; + public static final String DISPOSAL_SCHEDULE_RETENTION_PERIOD_DURATION = "retentionPeriodDuration"; + + public static final String DISPOSAL_HOLD_TITLE = "title"; + public static final String DISPOSAL_HOLD_DESCRIPTION = "description"; + public static final String DISPOSAL_HOLD_MANDATE = "mandate"; + public static final String DISPOSAL_HOLD_SCOPE_NOTES = "scopeNotes"; + public static final String DISPOSAL_HOLD_STATE = "state"; + + public static final String DISPOSAL_RULE_ORDER = "order"; + public static final String DISPOSAL_RULE_TITLE = "title"; + public static final String DISPOSAL_RULE_DESCRIPTION = "description"; + public static final String DISPOSAL_RULE_SELECTION_METHOD = "selectionMethod"; + public static final String DISPOSAL_RULE_CONDITION_KEY = "conditionKey"; + public static final String DISPOSAL_RULE_CONDITION_VALUE = "conditionvalue"; + public static final String DISPOSAL_RULE_SCHEDULE_ID = "scheduleId"; + /* Disposal related parameters */ public static final String DISPOSAL_HOLD_FILE_EXTENSION = ".json"; + public static final String DISPOSAL_RULE_FILE_EXTENSION = ".json"; /* Distributed related parameters */ public static final String DISTRIBUTED_INSTANCE_FILE_EXTENSION = ".json"; @@ -2169,6 +2203,7 @@ public enum RODA_TYPE { public static final String PERMISSION_METHOD_CREATE_DISPOSAL_RULE = "org.roda.wui.api.v2.controller.DisposalRuleController.createDisposalRule"; public static final String PERMISSION_METHOD_UPDATE_DISPOSAL_RULE = "org.roda.wui.api.v2.controller.DisposalRuleController.updateDisposalRule"; + public static final String PERMISSION_METHOD_DELETE_DISPOSAL_RULE = "org.roda.wui.api.v2.controller.DisposalRuleController.deleteDisposalRule"; public static final String PERMISSION_METHOD_CREATE_DISPOSAL_SCHEDULE = "org.roda.wui.api.v2.controller.DisposalScheduleController.createDisposalSchedule"; public static final String PERMISSION_METHOD_UPDATE_DISPOSAL_SCHEDULE = "org.roda.wui.api.v2.controller.DisposalScheduleController.updateDisposalSchedule"; diff --git a/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/common/RODAObjectList.java b/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/common/RODAObjectList.java index 72144f4da0..4263ac6169 100644 --- a/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/common/RODAObjectList.java +++ b/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/common/RODAObjectList.java @@ -14,9 +14,9 @@ public interface RODAObjectList extends Serializable { - public List getObjects(); + List getObjects(); - public void setObjects(List objects); + void setObjects(List objects); - public void addObject(T object); + void addObject(T object); } diff --git a/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/hold/DisposalHold.java b/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/hold/DisposalHold.java index 791823505f..063219f753 100644 --- a/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/hold/DisposalHold.java +++ b/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/hold/DisposalHold.java @@ -7,7 +7,12 @@ */ package org.roda.core.data.v2.disposal.hold; +import java.io.Serial; +import java.util.Arrays; import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Objects; import org.roda.core.data.common.RodaConstants; @@ -15,6 +20,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; +import org.roda.core.data.v2.index.IsIndexed; +import org.roda.core.data.v2.ip.HasId; /** * @author Tiago Fraga @@ -22,8 +29,9 @@ @jakarta.xml.bind.annotation.XmlRootElement(name = RodaConstants.RODA_OBJECT_DISPOSAL_HOLD) @JsonInclude(JsonInclude.Include.NON_NULL) -public class DisposalHold implements IsModelObject { +public class DisposalHold implements IsIndexed, IsModelObject, HasId { + @Serial private static final long serialVersionUID = 8291490773422089586L; private String id; @@ -57,6 +65,8 @@ public class DisposalHold implements IsModelObject { private DisposalHoldState state; + private Map fields = new HashMap<>(); + public DisposalHold() { super(); this.state = DisposalHoldState.ACTIVE; @@ -242,4 +252,38 @@ public String toString() { + ", liftedBy='" + liftedBy + '\'' + ", firstTimeUsed=" + firstTimeUsed + ", aipCounter=" + aipCounter + ", state=" + state + '}'; } + + @JsonIgnore + @Override + public String getUUID() { + return getId(); + } + + @Override + public List toCsvHeaders() { + return Arrays.asList("id", "title", "description", "mandate", "scopeNotes", "createOn", "createBy", "updatedOn", + "updatedBy", "originatedOn", "originatedBy", "liftedOn", "liftedBy", "firstTimeUsed", "state"); + } + + @Override + public List toCsvValues() { + return Arrays.asList(id, title, description, mandate, scopeNotes, createdOn, createdBy, updatedOn, updatedBy, + originatedOn, originatedBy, liftedOn, liftedBy, firstTimeUsed, state); + } + + @Override + public List liteFields() { + return List.of(); + } + + @Override + public Map getFields() { + return this.fields; + } + + @Override + public void setFields(Map fields) { + fields.entrySet().stream().filter(p -> p.getValue() != null) + .forEach(e -> this.fields.put(e.getKey(), e.getValue())); + } } diff --git a/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/hold/DisposalHoldState.java b/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/hold/DisposalHoldState.java index 3227e7854b..c48af2c710 100644 --- a/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/hold/DisposalHoldState.java +++ b/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/hold/DisposalHoldState.java @@ -11,5 +11,9 @@ * @author Tiago Fraga */ public enum DisposalHoldState { - ACTIVE, LIFTED + ACTIVE, LIFTED; + + public static DisposalHoldState getDefault() { + return ACTIVE; + } } diff --git a/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/rule/ChangeOrderRequest.java b/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/rule/ChangeOrderRequest.java new file mode 100644 index 0000000000..dea7ab9447 --- /dev/null +++ b/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/rule/ChangeOrderRequest.java @@ -0,0 +1,53 @@ +package org.roda.core.data.v2.disposal.rule; + +import org.roda.core.data.v2.generics.select.SelectedItemsRequest; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @author Miguel Guimarães + */ + +public class ChangeOrderRequest implements Serializable { + @Serial + private static final long serialVersionUID = -1604170151297177958L; + + private SelectedItemsRequest items; + private OrderPositions position; + private int newOrder; + + public ChangeOrderRequest() { + position = OrderPositions.TOP; + } + + public ChangeOrderRequest(SelectedItemsRequest items, OrderPositions position, int newOrder) { + this.items = items; + this.position = position; + this.newOrder = newOrder; + } + + public SelectedItemsRequest getItems() { + return items; + } + + public void setItems(SelectedItemsRequest items) { + this.items = items; + } + + public int getNewOrder() { + return newOrder; + } + + public void setNewOrder(int newOrder) { + this.newOrder = newOrder; + } + + public OrderPositions getPosition() { + return position; + } + + public void setPosition(OrderPositions position) { + this.position = position; + } +} diff --git a/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/rule/ConditionType.java b/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/rule/ConditionType.java index df4f3b8147..291fd58f9a 100644 --- a/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/rule/ConditionType.java +++ b/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/rule/ConditionType.java @@ -11,5 +11,9 @@ * @author Tiago Fraga */ public enum ConditionType { - IS_CHILD_OF, METADATA_FIELD + IS_CHILD_OF, METADATA_FIELD; + + public static ConditionType getDefault() { + return IS_CHILD_OF; + } } diff --git a/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/rule/DisposalRule.java b/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/rule/DisposalRule.java index 9ff812252e..1d7ade93f0 100644 --- a/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/rule/DisposalRule.java +++ b/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/rule/DisposalRule.java @@ -8,11 +8,15 @@ package org.roda.core.data.v2.disposal.rule; import java.io.Serial; +import java.util.Arrays; import java.util.Date; +import java.util.List; +import java.util.Map; import java.util.Objects; import org.roda.core.data.common.RodaConstants; import org.roda.core.data.v2.IsModelObject; +import org.roda.core.data.v2.index.IsIndexed; import org.roda.core.data.v2.ip.HasId; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -23,7 +27,7 @@ */ @jakarta.xml.bind.annotation.XmlRootElement(name = RodaConstants.RODA_OBJECT_DISPOSAL_RULE) @JsonInclude(JsonInclude.Include.NON_NULL) -public class DisposalRule implements IsModelObject, HasId, Comparable { +public class DisposalRule implements IsModelObject, IsIndexed, HasId, Comparable { private static final int VERSION = 1; @Serial @@ -50,6 +54,8 @@ public class DisposalRule implements IsModelObject, HasId, Comparable fields; + public DisposalRule() { super(); } @@ -201,4 +207,37 @@ public int getClassVersion() { public int compareTo(DisposalRule otherRule) { return Integer.compare(this.getOrder(), otherRule.getOrder()); } + + @JsonIgnore + @Override + public String getUUID() { + return getId(); + } + + @Override + public List toCsvHeaders() { + return Arrays.asList("id", "title", "description", "type", "conditionKey", "conditionValue", "disposalScheduleId", + "disposalScheduleName", "order", "createdOn", "createdBy", "updatedOn", "updatedBy"); + } + + @Override + public List toCsvValues() { + return Arrays.asList(id, title, description, type, conditionKey, conditionValue, disposalScheduleId, + disposalScheduleName, order, createdOn, createdBy, updatedOn, updatedBy); + } + + @Override + public List liteFields() { + return List.of(); + } + + @Override + public Map getFields() { + return this.fields; + } + + @Override + public void setFields(Map fields) { + this.fields = fields; + } } diff --git a/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/rule/DisposalRules.java b/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/rule/DisposalRules.java index a98fb9e61c..7256972b88 100644 --- a/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/rule/DisposalRules.java +++ b/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/rule/DisposalRules.java @@ -7,6 +7,7 @@ */ package org.roda.core.data.v2.disposal.rule; +import java.io.Serial; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -23,6 +24,8 @@ @JsonInclude(JsonInclude.Include.NON_NULL) public class DisposalRules implements RODAObjectList { + @Serial + private static final long serialVersionUID = 5273072223178069428L; private List disposalRuleList; public DisposalRules() { diff --git a/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/rule/OrderPositions.java b/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/rule/OrderPositions.java new file mode 100644 index 0000000000..fa245a8f5f --- /dev/null +++ b/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/rule/OrderPositions.java @@ -0,0 +1,9 @@ +package org.roda.core.data.v2.disposal.rule; + +/** + * @author Miguel Guimarães + */ + +public enum OrderPositions { + TOP, BOTTOM, POSITION +} diff --git a/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/schedule/DisposalSchedule.java b/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/schedule/DisposalSchedule.java index 45aa44af63..2d6bf63812 100644 --- a/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/schedule/DisposalSchedule.java +++ b/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/schedule/DisposalSchedule.java @@ -8,10 +8,16 @@ package org.roda.core.data.v2.disposal.schedule; import java.io.Serial; +import java.util.Arrays; import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Objects; +import org.roda.core.data.common.RodaConstants; import org.roda.core.data.v2.IsModelObject; +import org.roda.core.data.v2.index.IsIndexed; import org.roda.core.data.v2.ip.HasId; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -21,7 +27,7 @@ * @author Miguel Guimarães */ @JsonInclude(JsonInclude.Include.NON_NULL) -public class DisposalSchedule implements IsModelObject, HasId { +public class DisposalSchedule implements IsModelObject, IsIndexed, HasId { @Serial private static final long serialVersionUID = -2870778207871507847L; private static final int VERSION = 1; @@ -54,6 +60,10 @@ public class DisposalSchedule implements IsModelObject, HasId { private DisposalScheduleState state; + private Map fields = new HashMap<>(); + + private boolean usedInDisposalRule = false; + public DisposalSchedule() { super(); this.state = DisposalScheduleState.ACTIVE; @@ -221,6 +231,14 @@ public void setState(DisposalScheduleState state) { this.state = state; } + public boolean isUsedInDisposalRule() { + return usedInDisposalRule; + } + + public void setUsedInDisposalRule(boolean usedInDisposalRule) { + this.usedInDisposalRule = usedInDisposalRule; + } + @Override public boolean equals(Object o) { if (this == o) @@ -255,4 +273,39 @@ public String toString() { + firstTimeUsed + ", numberOfAIPUnder=" + apiCounter + ", createdOn=" + createdOn + ", createdBy='" + createdBy + '\'' + ", updatedOn=" + updatedOn + ", updatedBy='" + updatedBy + '\'' + ", state=" + state + '}'; } + + @JsonIgnore + @Override + public String getUUID() { + return getId(); + } + + @Override + public List toCsvHeaders() { + return Arrays.asList("id", "title", "description", "mandate", "scopeNotes", "actionCode", + "retentionTriggerElementId", "retentionPeriodIntervalCode", "retentionPeriodDuration", "createdOn", "createdBy", + "updatedOn", "updatedBy", "state"); + } + + @Override + public List toCsvValues() { + return Arrays.asList(id, title, description, mandate, scopeNotes, actionCode, retentionTriggerElementId, + retentionPeriodIntervalCode, retentionPeriodDuration, createdOn, createdBy, updatedOn, updatedBy, state); + } + + @Override + public List liteFields() { + return Arrays.asList(RodaConstants.INDEX_UUID); + } + + @Override + public Map getFields() { + return fields; + } + + @Override + public void setFields(Map fields) { + fields.entrySet().stream().filter(p -> p.getValue() != null) + .forEach(e -> this.fields.put(e.getKey(), e.getValue())); + } } diff --git a/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/schedule/DisposalScheduleState.java b/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/schedule/DisposalScheduleState.java index 53f54b2098..a499448443 100644 --- a/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/schedule/DisposalScheduleState.java +++ b/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/schedule/DisposalScheduleState.java @@ -11,5 +11,9 @@ * @author Miguel Guimarães */ public enum DisposalScheduleState { - ACTIVE, INACTIVE + ACTIVE, INACTIVE; + + public static DisposalScheduleState getDefault() { + return ACTIVE; + } } diff --git a/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/schedule/IndexedDisposalSchedule.java b/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/schedule/IndexedDisposalSchedule.java new file mode 100644 index 0000000000..b5d0ebcbbb --- /dev/null +++ b/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/schedule/IndexedDisposalSchedule.java @@ -0,0 +1,47 @@ +package org.roda.core.data.v2.disposal.schedule; + +import org.roda.core.data.v2.index.IsIndexed; + +import java.util.List; +import java.util.Map; + +/** + * @author Miguel Guimarães + */ + +public class IndexedDisposalSchedule implements IsIndexed { + @Override + public String getUUID() { + return ""; + } + + @Override + public List toCsvHeaders() { + return List.of(); + } + + @Override + public List toCsvValues() { + return List.of(); + } + + @Override + public List liteFields() { + return List.of(); + } + + @Override + public Map getFields() { + return Map.of(); + } + + @Override + public void setFields(Map fields) { + + } + + @Override + public String getId() { + return ""; + } +} diff --git a/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/schedule/RetentionPeriodIntervalCode.java b/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/schedule/RetentionPeriodIntervalCode.java index 84079d53d8..83cc2a18b6 100644 --- a/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/schedule/RetentionPeriodIntervalCode.java +++ b/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/disposal/schedule/RetentionPeriodIntervalCode.java @@ -11,5 +11,9 @@ * @author Miguel Guimarães */ public enum RetentionPeriodIntervalCode { - NO_RETENTION_PERIOD, DAYS, WEEKS, MONTHS, YEARS + NO_RETENTION_PERIOD, DAYS, WEEKS, MONTHS, YEARS; + + public static RetentionPeriodIntervalCode getDefault() { + return NO_RETENTION_PERIOD; + } } diff --git a/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/index/select/SelectedItemsList.java b/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/index/select/SelectedItemsList.java index 534349b270..696a73a47d 100644 --- a/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/index/select/SelectedItemsList.java +++ b/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/index/select/SelectedItemsList.java @@ -14,8 +14,6 @@ import org.roda.core.data.v2.IsRODAObject; -import com.fasterxml.jackson.annotation.JsonTypeName; - public class SelectedItemsList implements SelectedItems { @Serial diff --git a/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/representation/ChangeTypeRequest.java b/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/representation/ChangeTypeRequest.java index aeb807336d..5e0f78e950 100644 --- a/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/representation/ChangeTypeRequest.java +++ b/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/representation/ChangeTypeRequest.java @@ -10,10 +10,6 @@ import java.io.Serial; import java.io.Serializable; -import io.swagger.v3.oas.annotations.media.DiscriminatorMapping; -import io.swagger.v3.oas.annotations.media.Schema; -import org.roda.core.data.v2.generics.select.SelectedItemsFilterRequest; -import org.roda.core.data.v2.generics.select.SelectedItemsListRequest; import org.roda.core.data.v2.generics.select.SelectedItemsRequest; /** diff --git a/roda-core/roda-core/src/main/java/org/roda/core/index/IndexModelObserver.java b/roda-core/roda-core/src/main/java/org/roda/core/index/IndexModelObserver.java index 0647dba256..2c7d68af30 100644 --- a/roda-core/roda-core/src/main/java/org/roda/core/index/IndexModelObserver.java +++ b/roda-core/roda-core/src/main/java/org/roda/core/index/IndexModelObserver.java @@ -37,6 +37,8 @@ import org.roda.core.data.v2.LiteRODAObject; import org.roda.core.data.v2.common.OptionalWithCause; import org.roda.core.data.v2.disposal.confirmation.DisposalConfirmation; +import org.roda.core.data.v2.disposal.hold.DisposalHold; +import org.roda.core.data.v2.disposal.rule.DisposalRule; import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; import org.roda.core.data.v2.index.IsIndexed; import org.roda.core.data.v2.index.filter.Filter; @@ -1509,6 +1511,36 @@ public ReturnWithExceptions disposalConfirmationDeleted(Str return deleteDocumentFromIndex(DisposalConfirmation.class, confirmationId); } + @Override + public ReturnWithExceptions disposalScheduleCreatedOrUpdated(DisposalSchedule schedule) { + return SolrUtils.create2(index, model, this, DisposalSchedule.class, schedule); + } + + @Override + public ReturnWithExceptions disposalScheduleDeleted(String scheduleId, boolean commit) { + return deleteDocumentFromIndex(DisposalSchedule.class, scheduleId); + } + + @Override + public ReturnWithExceptions disposalHoldCreatedOrUpdated(DisposalHold hold) { + return SolrUtils.create2(index, model, this, DisposalHold.class, hold); + } + + @Override + public ReturnWithExceptions disposalHoldDeleted(String holdId, boolean commit) { + return deleteDocumentFromIndex(DisposalHold.class, holdId); + } + + @Override + public ReturnWithExceptions disposalRuleCreatedOrUpdated(DisposalRule rule) { + return SolrUtils.create2(index, model, this, DisposalRule.class, rule); + } + + @Override + public ReturnWithExceptions disposalRuleDeleted(String ruleId, boolean commit) { + return deleteDocumentFromIndex(DisposalRule.class, ruleId); + } + public ReturnWithExceptions liteRODAObjectCreated(LiteRODAObject liteRODAObject) { ReturnWithExceptions ret = new ReturnWithExceptions<>(this); OptionalWithCause liteObject = LiteRODAObjectFactory.get(model, liteRODAObject); diff --git a/roda-core/roda-core/src/main/java/org/roda/core/index/IndexService.java b/roda-core/roda-core/src/main/java/org/roda/core/index/IndexService.java index 6a109866aa..6864437de0 100644 --- a/roda-core/roda-core/src/main/java/org/roda/core/index/IndexService.java +++ b/roda-core/roda-core/src/main/java/org/roda/core/index/IndexService.java @@ -40,6 +40,9 @@ import org.roda.core.data.v2.IsRODAObject; import org.roda.core.data.v2.common.OptionalWithCause; import org.roda.core.data.v2.disposal.confirmation.DisposalConfirmation; +import org.roda.core.data.v2.disposal.hold.DisposalHold; +import org.roda.core.data.v2.disposal.rule.DisposalRule; +import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; import org.roda.core.data.v2.index.FindRequest; import org.roda.core.data.v2.index.IndexResult; import org.roda.core.data.v2.index.IndexRunnable; @@ -322,6 +325,30 @@ public ReturnWithExceptions reindexDisposalConfirmation(Dis return ret; } + public ReturnWithExceptions reindexDisposalSchedule(DisposalSchedule schedule) { + ReturnWithExceptions ret = RodaCoreFactory.checkIfWriteIsAllowedAndIfFalseReturn(nodeType); + if (ret.isEmpty()) { + ret = observer.disposalScheduleCreatedOrUpdated(schedule); + } + return ret; + } + + public ReturnWithExceptions reindexDisposalHold(DisposalHold hold) { + ReturnWithExceptions ret = RodaCoreFactory.checkIfWriteIsAllowedAndIfFalseReturn(nodeType); + if (ret.isEmpty()) { + ret = observer.disposalHoldCreatedOrUpdated(hold); + } + return ret; + } + + public ReturnWithExceptions reindexDisposalRule(DisposalRule rule) { + ReturnWithExceptions ret = RodaCoreFactory.checkIfWriteIsAllowedAndIfFalseReturn(nodeType); + if (ret.isEmpty()) { + ret = observer.disposalRuleCreatedOrUpdated(rule); + } + return ret; + } + public ReturnWithExceptions reindexAIPPreservationEvents(AIP aip) { ReturnWithExceptions ret = RodaCoreFactory.checkIfWriteIsAllowedAndIfFalseReturn(nodeType); if (ret.isEmpty()) { @@ -519,6 +546,12 @@ public ReturnWithExceptions reinde return reindexDIPFile(DIPFile.class.cast(object)); } else if (DisposalConfirmation.class.equals(objectClass)) { return reindexDisposalConfirmation(DisposalConfirmation.class.cast(object)); + } else if (DisposalSchedule.class.equals(objectClass)) { + return reindexDisposalSchedule(DisposalSchedule.class.cast(object)); + } else if (DisposalHold.class.equals(objectClass)) { + return reindexDisposalHold(DisposalHold.class.cast(object)); + } else if (DisposalRule.class.equals(objectClass)) { + return reindexDisposalRule(DisposalRule.class.cast(object)); } else { LOGGER.error("Error trying to reindex an unconfigured object class: {}", objectClass.getName()); ReturnWithExceptions exceptions = new ReturnWithExceptions<>(); diff --git a/roda-core/roda-core/src/main/java/org/roda/core/index/schema/SolrCollectionRegistry.java b/roda-core/roda-core/src/main/java/org/roda/core/index/schema/SolrCollectionRegistry.java index ebd2acba4d..a2262239cd 100644 --- a/roda-core/roda-core/src/main/java/org/roda/core/index/schema/SolrCollectionRegistry.java +++ b/roda-core/roda-core/src/main/java/org/roda/core/index/schema/SolrCollectionRegistry.java @@ -29,6 +29,9 @@ import org.roda.core.index.schema.collections.DIPCollection; import org.roda.core.index.schema.collections.DIPFileCollection; import org.roda.core.index.schema.collections.DisposalConfirmationCollection; +import org.roda.core.index.schema.collections.DisposalHoldCollection; +import org.roda.core.index.schema.collections.DisposalRuleCollection; +import org.roda.core.index.schema.collections.DisposalScheduleCollection; import org.roda.core.index.schema.collections.FileCollection; import org.roda.core.index.schema.collections.JobCollection; import org.roda.core.index.schema.collections.JobReportCollection; @@ -46,10 +49,6 @@ public final class SolrCollectionRegistry { - private SolrCollectionRegistry() { - - } - private static final Map, SolrCollection> REGISTRY = new HashMap<>(); private static final Map, Class> MODEL_TO_INDEX = new HashMap<>(); @@ -79,9 +78,16 @@ private SolrCollectionRegistry() { register(new RepresentationInformationCollection()); + register(new DisposalScheduleCollection()); + register(new DisposalHoldCollection()); + register(new DisposalRuleCollection()); register(new DisposalConfirmationCollection()); } + private SolrCollectionRegistry() { + + } + public static void register(SolrCollection collection) { REGISTRY.put(collection.getIndexClass(), collection); MODEL_TO_INDEX.put(collection.getModelClass(), collection.getIndexClass()); @@ -138,9 +144,9 @@ public static I fromSolrDocument(Class indexClass, Solr return fromSolrDocument(indexClass, doc, Collections.emptyList()); } - public static SolrInputDocument toSolrDocument(Class indexClass, ModelService model, - M object, IndexingAdditionalInfo utils) throws GenericException, NotSupportedException, RequestNotValidException, - NotFoundException, AuthorizationDeniedException { + public static SolrInputDocument toSolrDocument(Class indexClass, + ModelService model, M object, IndexingAdditionalInfo utils) throws GenericException, NotSupportedException, + RequestNotValidException, NotFoundException, AuthorizationDeniedException { SolrCollection solrCollection = get(indexClass); if (solrCollection != null) { return solrCollection.toSolrDocument(model, object, utils); @@ -150,9 +156,9 @@ public static SolrInputDocument t } } - public static SolrInputDocument toSolrDocument(Class indexClass, ModelService model, - M object) throws GenericException, NotSupportedException, RequestNotValidException, NotFoundException, - AuthorizationDeniedException { + public static SolrInputDocument toSolrDocument(Class indexClass, + ModelService model, M object) throws GenericException, NotSupportedException, RequestNotValidException, + NotFoundException, AuthorizationDeniedException { return toSolrDocument(indexClass, model, object, IndexingAdditionalInfo.empty()); } diff --git a/roda-core/roda-core/src/main/java/org/roda/core/index/schema/collections/DisposalHoldCollection.java b/roda-core/roda-core/src/main/java/org/roda/core/index/schema/collections/DisposalHoldCollection.java new file mode 100644 index 0000000000..f2b3457c61 --- /dev/null +++ b/roda-core/roda-core/src/main/java/org/roda/core/index/schema/collections/DisposalHoldCollection.java @@ -0,0 +1,108 @@ +package org.roda.core.index.schema.collections; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.apache.solr.common.SolrDocument; +import org.apache.solr.common.SolrInputDocument; +import org.roda.core.data.common.RodaConstants; +import org.roda.core.data.exceptions.AuthorizationDeniedException; +import org.roda.core.data.exceptions.GenericException; +import org.roda.core.data.exceptions.NotFoundException; +import org.roda.core.data.exceptions.RequestNotValidException; +import org.roda.core.data.v2.disposal.hold.DisposalHold; +import org.roda.core.data.v2.disposal.hold.DisposalHoldState; +import org.roda.core.index.IndexingAdditionalInfo; +import org.roda.core.index.schema.AbstractSolrCollection; +import org.roda.core.index.schema.CopyField; +import org.roda.core.index.schema.Field; +import org.roda.core.index.schema.SolrCollection; +import org.roda.core.index.utils.SolrUtils; +import org.roda.core.model.ModelService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Miguel Guimarães + */ + +public class DisposalHoldCollection extends AbstractSolrCollection { + + private static final Logger LOGGER = LoggerFactory.getLogger(DisposalHoldCollection.class); + + @Override + public Class getIndexClass() { + return DisposalHold.class; + } + + @Override + public Class getModelClass() { + return DisposalHold.class; + } + + @Override + public String getIndexName() { + return RodaConstants.INDEX_DISPOSAL_HOLD; + } + + @Override + public List getCommitIndexNames() { + return Arrays.asList(RodaConstants.INDEX_DISPOSAL_HOLD); + } + + @Override + public String getUniqueId(DisposalHold modelObject) { + return modelObject.getUUID(); + } + + @Override + public List getCopyFields() { + return Collections.singletonList(SolrCollection.getCopyAllToSearchField()); + } + + @Override + public List getFields() { + List fields = new ArrayList<>(super.getFields()); + + fields.add(new Field(RodaConstants.DISPOSAL_HOLD_TITLE, Field.TYPE_STRING)); + fields.add(new Field(RodaConstants.DISPOSAL_HOLD_DESCRIPTION, Field.TYPE_STRING)); + fields.add(new Field(RodaConstants.DISPOSAL_HOLD_MANDATE, Field.TYPE_STRING)); + fields.add(new Field(RodaConstants.DISPOSAL_HOLD_SCOPE_NOTES, Field.TYPE_STRING)); + fields.add(new Field(RodaConstants.DISPOSAL_HOLD_STATE, Field.TYPE_STRING)); + + return fields; + } + + @Override + public SolrInputDocument toSolrDocument(ModelService model, DisposalHold hold, IndexingAdditionalInfo info) + throws RequestNotValidException, GenericException, NotFoundException, AuthorizationDeniedException { + + SolrInputDocument doc = super.toSolrDocument(model, hold, info); + + doc.addField(RodaConstants.DISPOSAL_HOLD_TITLE, hold.getTitle()); + doc.addField(RodaConstants.DISPOSAL_HOLD_DESCRIPTION, hold.getDescription()); + doc.addField(RodaConstants.DISPOSAL_HOLD_MANDATE, hold.getMandate()); + doc.addField(RodaConstants.DISPOSAL_HOLD_SCOPE_NOTES, hold.getScopeNotes()); + doc.addField(RodaConstants.DISPOSAL_HOLD_STATE, hold.getState().toString()); + + return doc; + } + + @Override + public DisposalHold fromSolrDocument(SolrDocument doc, List fieldsToReturn) throws GenericException { + + final DisposalHold hold = super.fromSolrDocument(doc, fieldsToReturn); + + hold.setTitle(SolrUtils.objectToString(doc.get(RodaConstants.DISPOSAL_HOLD_TITLE), null)); + hold.setDescription(SolrUtils.objectToString(doc.get(RodaConstants.DISPOSAL_HOLD_DESCRIPTION), null)); + hold.setMandate(SolrUtils.objectToString(doc.get(RodaConstants.DISPOSAL_HOLD_MANDATE), null)); + hold.setScopeNotes(SolrUtils.objectToString(doc.get(RodaConstants.DISPOSAL_HOLD_SCOPE_NOTES), null)); + DisposalHoldState disposalHoldState = SolrUtils.objectToEnum(doc.get(RodaConstants.DISPOSAL_HOLD_STATE), + DisposalHoldState.class, DisposalHoldState.getDefault()); + hold.setState(disposalHoldState); + + return hold; + } +} diff --git a/roda-core/roda-core/src/main/java/org/roda/core/index/schema/collections/DisposalRuleCollection.java b/roda-core/roda-core/src/main/java/org/roda/core/index/schema/collections/DisposalRuleCollection.java new file mode 100644 index 0000000000..c1344ef71d --- /dev/null +++ b/roda-core/roda-core/src/main/java/org/roda/core/index/schema/collections/DisposalRuleCollection.java @@ -0,0 +1,114 @@ +package org.roda.core.index.schema.collections; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.apache.solr.common.SolrDocument; +import org.apache.solr.common.SolrInputDocument; +import org.roda.core.data.common.RodaConstants; +import org.roda.core.data.exceptions.AuthorizationDeniedException; +import org.roda.core.data.exceptions.GenericException; +import org.roda.core.data.exceptions.NotFoundException; +import org.roda.core.data.exceptions.RequestNotValidException; +import org.roda.core.data.v2.disposal.rule.ConditionType; +import org.roda.core.data.v2.disposal.rule.DisposalRule; +import org.roda.core.index.IndexingAdditionalInfo; +import org.roda.core.index.schema.AbstractSolrCollection; +import org.roda.core.index.schema.CopyField; +import org.roda.core.index.schema.Field; +import org.roda.core.index.schema.SolrCollection; +import org.roda.core.index.utils.SolrUtils; +import org.roda.core.model.ModelService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Miguel Guimarães + */ + +public class DisposalRuleCollection extends AbstractSolrCollection { + + private static final Logger LOGGER = LoggerFactory.getLogger(DisposalRuleCollection.class); + + @Override + public Class getIndexClass() { + return DisposalRule.class; + } + + @Override + public Class getModelClass() { + return DisposalRule.class; + } + + @Override + public String getIndexName() { + return RodaConstants.INDEX_DISPOSAL_RULE; + } + + @Override + public List getCommitIndexNames() { + return Arrays.asList(RodaConstants.INDEX_DISPOSAL_RULE); + } + + @Override + public String getUniqueId(DisposalRule modelObject) { + return modelObject.getUUID(); + } + + @Override + public List getCopyFields() { + return Collections.singletonList(SolrCollection.getCopyAllToSearchField()); + } + + @Override + public List getFields() { + List fields = new ArrayList<>(super.getFields()); + + fields.add(new Field(RodaConstants.DISPOSAL_RULE_ORDER, Field.TYPE_INT)); + fields.add(new Field(RodaConstants.DISPOSAL_RULE_TITLE, Field.TYPE_STRING)); + fields.add(new Field(RodaConstants.DISPOSAL_RULE_DESCRIPTION, Field.TYPE_STRING)); + fields.add(new Field(RodaConstants.DISPOSAL_RULE_SELECTION_METHOD, Field.TYPE_STRING)); + fields.add(new Field(RodaConstants.DISPOSAL_RULE_CONDITION_KEY, Field.TYPE_STRING)); + fields.add(new Field(RodaConstants.DISPOSAL_RULE_CONDITION_VALUE, Field.TYPE_STRING)); + fields.add(new Field(RodaConstants.DISPOSAL_RULE_SCHEDULE_ID, Field.TYPE_STRING)); + + return fields; + } + + @Override + public SolrInputDocument toSolrDocument(ModelService model, DisposalRule rule, IndexingAdditionalInfo info) + throws RequestNotValidException, GenericException, NotFoundException, AuthorizationDeniedException { + + SolrInputDocument doc = super.toSolrDocument(model, rule, info); + + doc.addField(RodaConstants.DISPOSAL_RULE_ORDER, rule.getOrder()); + doc.addField(RodaConstants.DISPOSAL_RULE_TITLE, rule.getTitle()); + doc.addField(RodaConstants.DISPOSAL_RULE_DESCRIPTION, rule.getDescription()); + doc.addField(RodaConstants.DISPOSAL_RULE_SELECTION_METHOD, rule.getType().toString()); + doc.addField(RodaConstants.DISPOSAL_RULE_CONDITION_KEY, rule.getConditionKey()); + doc.addField(RodaConstants.DISPOSAL_RULE_CONDITION_VALUE, rule.getConditionValue()); + doc.addField(RodaConstants.DISPOSAL_RULE_SCHEDULE_ID, rule.getDisposalScheduleId()); + + return doc; + } + + @Override + public DisposalRule fromSolrDocument(SolrDocument doc, List fieldsToReturn) throws GenericException { + + final DisposalRule rule = super.fromSolrDocument(doc, fieldsToReturn); + + rule.setOrder(SolrUtils.objectToInteger(doc.get(RodaConstants.DISPOSAL_RULE_ORDER), null)); + rule.setTitle(SolrUtils.objectToString(doc.get(RodaConstants.DISPOSAL_RULE_TITLE), null)); + rule.setDescription(SolrUtils.objectToString(doc.get(RodaConstants.DISPOSAL_RULE_DESCRIPTION), null)); + rule.setConditionKey(SolrUtils.objectToString(doc.get(RodaConstants.DISPOSAL_RULE_CONDITION_KEY), null)); + rule.setConditionValue(SolrUtils.objectToString(doc.get(RodaConstants.DISPOSAL_RULE_CONDITION_VALUE), null)); + rule.setDisposalScheduleId(SolrUtils.objectToString(doc.get(RodaConstants.DISPOSAL_RULE_SCHEDULE_ID), null)); + ConditionType conditionType = SolrUtils.objectToEnum(doc.get(RodaConstants.DISPOSAL_RULE_SELECTION_METHOD), + ConditionType.class, ConditionType.getDefault()); + rule.setType(conditionType); + + return rule; + } +} diff --git a/roda-core/roda-core/src/main/java/org/roda/core/index/schema/collections/DisposalScheduleCollection.java b/roda-core/roda-core/src/main/java/org/roda/core/index/schema/collections/DisposalScheduleCollection.java new file mode 100644 index 0000000000..462e356f22 --- /dev/null +++ b/roda-core/roda-core/src/main/java/org/roda/core/index/schema/collections/DisposalScheduleCollection.java @@ -0,0 +1,132 @@ +package org.roda.core.index.schema.collections; + +import org.apache.solr.common.SolrDocument; +import org.apache.solr.common.SolrInputDocument; +import org.roda.core.data.common.RodaConstants; +import org.roda.core.data.exceptions.AuthorizationDeniedException; +import org.roda.core.data.exceptions.GenericException; +import org.roda.core.data.exceptions.NotFoundException; +import org.roda.core.data.exceptions.RequestNotValidException; +import org.roda.core.data.v2.disposal.schedule.DisposalActionCode; +import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; +import org.roda.core.data.v2.disposal.schedule.DisposalScheduleState; +import org.roda.core.data.v2.disposal.schedule.RetentionPeriodIntervalCode; +import org.roda.core.index.IndexingAdditionalInfo; +import org.roda.core.index.schema.AbstractSolrCollection; +import org.roda.core.index.schema.CopyField; +import org.roda.core.index.schema.Field; +import org.roda.core.index.schema.SolrCollection; +import org.roda.core.index.utils.SolrUtils; +import org.roda.core.model.ModelService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * @author Miguel Guimarães + */ + +public class DisposalScheduleCollection extends AbstractSolrCollection { + + private static final Logger LOGGER = LoggerFactory.getLogger(DisposalScheduleCollection.class); + + @Override + public Class getIndexClass() { + return DisposalSchedule.class; + } + + @Override + public Class getModelClass() { + return DisposalSchedule.class; + } + + @Override + public String getIndexName() { + return RodaConstants.INDEX_DISPOSAL_SCHEDULE; + } + + @Override + public List getCommitIndexNames() { + return Arrays.asList(RodaConstants.INDEX_DISPOSAL_SCHEDULE); + } + + @Override + public String getUniqueId(DisposalSchedule modelObject) { + return modelObject.getUUID(); + } + + @Override + public List getCopyFields() { + return Collections.singletonList(SolrCollection.getCopyAllToSearchField()); + } + + @Override + public List getFields() { + List fields = new ArrayList<>(super.getFields()); + + fields.add(new Field(RodaConstants.DISPOSAL_SCHEDULE_TITLE, Field.TYPE_STRING)); + fields.add(new Field(RodaConstants.DISPOSAL_SCHEDULE_DESCRIPTION, Field.TYPE_STRING)); + fields.add(new Field(RodaConstants.DISPOSAL_SCHEDULE_MANDATE, Field.TYPE_STRING)); + fields.add(new Field(RodaConstants.DISPOSAL_SCHEDULE_SCOPE_NOTES, Field.TYPE_STRING)); + fields.add(new Field(RodaConstants.DISPOSAL_SCHEDULE_STATE, Field.TYPE_STRING)); + fields.add(new Field(RodaConstants.DISPOSAL_SCHEDULE_ACTION, Field.TYPE_STRING)); + fields.add(new Field(RodaConstants.DISPOSAL_SCHEDULE_RETENTION_PERIOD_INTERVAL_CODE, Field.TYPE_STRING)); + fields.add(new Field(RodaConstants.DISPOSAL_SCHEDULE_RETENTION_PERIOD_DURATION, Field.TYPE_INT)); + + return fields; + } + + @Override + public SolrInputDocument toSolrDocument(ModelService model, DisposalSchedule disposalSchedule, + IndexingAdditionalInfo info) + throws RequestNotValidException, GenericException, NotFoundException, AuthorizationDeniedException { + + SolrInputDocument doc = super.toSolrDocument(model, disposalSchedule, info); + + doc.addField(RodaConstants.DISPOSAL_SCHEDULE_TITLE, disposalSchedule.getTitle()); + doc.addField(RodaConstants.DISPOSAL_SCHEDULE_DESCRIPTION, disposalSchedule.getDescription()); + doc.addField(RodaConstants.DISPOSAL_SCHEDULE_MANDATE, disposalSchedule.getMandate()); + doc.addField(RodaConstants.DISPOSAL_SCHEDULE_SCOPE_NOTES, disposalSchedule.getScopeNotes()); + if (!DisposalActionCode.RETAIN_PERMANENTLY.equals(disposalSchedule.getActionCode())) { + doc.addField(RodaConstants.DISPOSAL_SCHEDULE_RETENTION_PERIOD_INTERVAL_CODE, + disposalSchedule.getRetentionPeriodIntervalCode().toString()); + doc.addField(RodaConstants.DISPOSAL_SCHEDULE_RETENTION_PERIOD_DURATION, + disposalSchedule.getRetentionPeriodDuration()); + } + doc.addField(RodaConstants.DISPOSAL_SCHEDULE_STATE, disposalSchedule.getState().toString()); + doc.addField(RodaConstants.DISPOSAL_SCHEDULE_ACTION, disposalSchedule.getActionCode().toString()); + + return doc; + } + + @Override + public DisposalSchedule fromSolrDocument(SolrDocument doc, List fieldsToReturn) throws GenericException { + + final DisposalSchedule disposalSchedule = super.fromSolrDocument(doc, fieldsToReturn); + + disposalSchedule.setTitle(SolrUtils.objectToString(doc.get(RodaConstants.DISPOSAL_SCHEDULE_TITLE), null)); + disposalSchedule + .setDescription(SolrUtils.objectToString(doc.get(RodaConstants.DISPOSAL_SCHEDULE_DESCRIPTION), null)); + disposalSchedule.setMandate(SolrUtils.objectToString(doc.get(RodaConstants.DISPOSAL_SCHEDULE_MANDATE), null)); + disposalSchedule + .setScopeNotes(SolrUtils.objectToString(doc.get(RodaConstants.DISPOSAL_SCHEDULE_SCOPE_NOTES), null)); + DisposalScheduleState disposalScheduleState = SolrUtils.objectToEnum(doc.get(RodaConstants.DISPOSAL_SCHEDULE_STATE), + DisposalScheduleState.class, DisposalScheduleState.getDefault()); + disposalSchedule.setState(disposalScheduleState); + disposalSchedule.setRetentionPeriodDuration( + SolrUtils.objectToInteger(doc.get(RodaConstants.DISPOSAL_SCHEDULE_RETENTION_PERIOD_DURATION), null)); + RetentionPeriodIntervalCode retentionPeriodIntervalCode = SolrUtils.objectToEnum( + doc.get(RodaConstants.DISPOSAL_SCHEDULE_RETENTION_PERIOD_INTERVAL_CODE), RetentionPeriodIntervalCode.class, + RetentionPeriodIntervalCode.getDefault()); + disposalSchedule.setRetentionPeriodIntervalCode(retentionPeriodIntervalCode); + DisposalActionCode actionCode = SolrUtils.objectToEnum(doc.get(RodaConstants.DISPOSAL_SCHEDULE_ACTION), + DisposalActionCode.class, null); + disposalSchedule.setActionCode(actionCode); + + return disposalSchedule; + } +} diff --git a/roda-core/roda-core/src/main/java/org/roda/core/model/DefaultModelService.java b/roda-core/roda-core/src/main/java/org/roda/core/model/DefaultModelService.java index 44b29806e4..20116411ad 100644 --- a/roda-core/roda-core/src/main/java/org/roda/core/model/DefaultModelService.java +++ b/roda-core/roda-core/src/main/java/org/roda/core/model/DefaultModelService.java @@ -2810,7 +2810,7 @@ public Group updateGroup(final Group group, boolean notify, boolean isHandlingEv @Override public Group updateGroupMembers(String id, Set members, boolean notify, boolean isHandlingEvent) - throws AuthorizationDeniedException, NotFoundException, GenericException { + throws AuthorizationDeniedException, NotFoundException, GenericException { boolean writeIsAllowed = RodaCoreFactory.checkIfWriteIsAllowed(nodeType); if (!writeIsAllowed && !isHandlingEvent) { @@ -2819,7 +2819,7 @@ public Group updateGroupMembers(String id, Set members, boolean notify, Group group = UserUtility.getLdapUtility().getGroup(id); group.getUsers().addAll(members); - for (String member: members) { + for (String member : members) { User user = UserUtility.getLdapUtility().getUser(member); user.getGroups().add(id); notifyUserUpdated(user).failOnError(); @@ -4510,6 +4510,7 @@ public DisposalHold createDisposalHold(DisposalHold disposalHold, String created String disposalHoldAsJson = JsonUtils.getJsonFromObject(newDisposalHold); StoragePath disposalHoldPath = ModelUtils.getDisposalHoldStoragePath(newDisposalHold.getId()); storage.createBinary(disposalHoldPath, new StringContentPayload(disposalHoldAsJson), false); + notifyDisposalHoldCreatedOrUpdated(newDisposalHold).failOnError(); return newDisposalHold; } @@ -4560,6 +4561,7 @@ public DisposalHold updateDisposalHold(DisposalHold disposalHold, String updated storage.updateBinaryContent(disposalHoldPath, new StringContentPayload(disposalHoldAsJson), false, true, false, null); + notifyDisposalHoldCreatedOrUpdated(currentDisposalHold).failOnError(); createRepositoryEvent(PreservationEventType.UPDATE, "Update disposal hold", PluginState.SUCCESS, "", details, "", true, null); @@ -4577,6 +4579,7 @@ public void deleteDisposalHold(String disposalHoldId) throws RequestNotValidExce if (disposalHold.getFirstTimeUsed() == null) { StoragePath disposalHoldPath = ModelUtils.getDisposalHoldStoragePath(disposalHold.getId()); storage.deleteResource(disposalHoldPath); + notifyDisposalHoldDeleted(disposalHoldId, false); } else { throw new IllegalOperationException("Error deleting disposal hold: " + disposalHold.getId() + ". Reason: One or more AIPs where associated under this disposal hold"); @@ -4709,6 +4712,7 @@ public DisposalSchedule createDisposalSchedule(DisposalSchedule disposalSchedule String disposalScheduleAsJson = JsonUtils.getJsonFromObject(disposalSchedule); StoragePath disposalSchedulePath = ModelUtils.getDisposalScheduleStoragePath(disposalSchedule.getId()); storage.createBinary(disposalSchedulePath, new StringContentPayload(disposalScheduleAsJson), false); + notifyDisposalScheduleCreatedOrUpdated(disposalSchedule).failOnError(); return disposalSchedule; } @@ -4754,6 +4758,7 @@ public DisposalSchedule updateDisposalSchedule(DisposalSchedule disposalSchedule StoragePath disposalSchedulePath = ModelUtils.getDisposalScheduleStoragePath(currentDisposalSchedule.getId()); storage.updateBinaryContent(disposalSchedulePath, new StringContentPayload(disposalScheduleAsJson), false, false, false, null); + notifyDisposalScheduleCreatedOrUpdated(currentDisposalSchedule).failOnError(); return currentDisposalSchedule; } @@ -4813,7 +4818,7 @@ public void deleteDisposalSchedule(String disposalScheduleId) throws NotFoundExc // if so, block the action and keep the disposal schedule if (retrieveDisposalSchedule(disposalScheduleId).getFirstTimeUsed() != null) { throw new IllegalOperationException("Error deleting disposal schedule: " + disposalScheduleId - + ". Reason: One or more AIPs where destroyed under this disposal schedule"); + + ". Reason: This disposal schedule was already associated to an AIP or used to destroy on ore more AIP"); } // check if the disposal schedule is being used in a disposal rule @@ -4831,6 +4836,7 @@ public void deleteDisposalSchedule(String disposalScheduleId) throws NotFoundExc } storage.deleteResource(disposalSchedulePath); + notifyDisposalScheduleDeleted(disposalScheduleId, false).failOnError(); } /********************************** @@ -5079,6 +5085,7 @@ public DisposalRule createDisposalRule(DisposalRule disposalRule, String created String disposalRuleAsJson = JsonUtils.getJsonFromObject(disposalRule); StoragePath disposalRulePath = ModelUtils.getDisposalRuleStoragePath(disposalRule.getId()); storage.createBinary(disposalRulePath, new StringContentPayload(disposalRuleAsJson), false); + notifyDisposalRuleCreatedOrUpdated(disposalRule).failOnError(); return disposalRule; } @@ -5095,6 +5102,7 @@ public DisposalRule updateDisposalRule(DisposalRule disposalRule, String updated StoragePath disposalRulePath = ModelUtils.getDisposalRuleStoragePath(disposalRule.getId()); storage.updateBinaryContent(disposalRulePath, new StringContentPayload(disposalRuleAsJson), false, false, false, null); + notifyDisposalRuleCreatedOrUpdated(disposalRule).failOnError(); return disposalRule; } @@ -5106,6 +5114,7 @@ public void deleteDisposalRule(String disposalRuleId, String updatedBy) StoragePath disposalRulePath = ModelUtils.getDisposalRuleStoragePath(disposalRuleId); storage.deleteResource(disposalRulePath); + notifyDisposalRuleDeleted(disposalRuleId, false); DisposalRules disposalRules = listDisposalRules(); int index = 0; @@ -5763,6 +5772,36 @@ public ReturnWithExceptionsWrapper notifyDisposalConfirmationDeleted(String disp return notifyObserversSafely(observer -> observer.disposalConfirmationDeleted(disposalConfirmationId, commit)); } + @Override + public ReturnWithExceptionsWrapper notifyDisposalScheduleCreatedOrUpdated(DisposalSchedule schedule) { + return notifyObserversSafely(observer -> observer.disposalScheduleCreatedOrUpdated(schedule)); + } + + @Override + public ReturnWithExceptionsWrapper notifyDisposalScheduleDeleted(String disposalScheduleId, boolean commit) { + return notifyObserversSafely(observer -> observer.disposalScheduleDeleted(disposalScheduleId, commit)); + } + + @Override + public ReturnWithExceptionsWrapper notifyDisposalHoldCreatedOrUpdated(DisposalHold hold) { + return notifyObserversSafely(observer -> observer.disposalHoldCreatedOrUpdated(hold)); + } + + @Override + public ReturnWithExceptionsWrapper notifyDisposalHoldDeleted(String disposalHoldId, boolean commit) { + return notifyObserversSafely(observer -> observer.disposalHoldDeleted(disposalHoldId, commit)); + } + + @Override + public ReturnWithExceptionsWrapper notifyDisposalRuleCreatedOrUpdated(DisposalRule rule) { + return notifyObserversSafely(observer -> observer.disposalRuleCreatedOrUpdated(rule)); + } + + @Override + public ReturnWithExceptionsWrapper notifyDisposalRuleDeleted(String disposalRuleId, boolean commit) { + return notifyObserversSafely(observer -> observer.disposalRuleDeleted(disposalRuleId, commit)); + } + /************************************ * Storage Utils ************************************/ diff --git a/roda-core/roda-core/src/main/java/org/roda/core/model/DefaultTransactionalModelService.java b/roda-core/roda-core/src/main/java/org/roda/core/model/DefaultTransactionalModelService.java index 3b82df283a..32ab73b5e7 100644 --- a/roda-core/roda-core/src/main/java/org/roda/core/model/DefaultTransactionalModelService.java +++ b/roda-core/roda-core/src/main/java/org/roda/core/model/DefaultTransactionalModelService.java @@ -2041,7 +2041,7 @@ public Group updateGroup(Group group, boolean notify, boolean isHandlingEvent) @Override public Group updateGroupMembers(String id, Set members, boolean notify, boolean isHandlingEvent) - throws GenericException, NotFoundException, AuthorizationDeniedException { + throws GenericException, NotFoundException, AuthorizationDeniedException { return mainModelService.updateGroupMembers(id, members, notify, isHandlingEvent); } @@ -4490,6 +4490,36 @@ public ReturnWithExceptionsWrapper notifyDisposalConfirmationDeleted(String disp return getModelService().notifyDisposalConfirmationDeleted(disposalConfirmationId, commit); } + @Override + public ReturnWithExceptionsWrapper notifyDisposalScheduleCreatedOrUpdated(DisposalSchedule schedule) { + return getModelService().notifyDisposalScheduleCreatedOrUpdated(schedule); + } + + @Override + public ReturnWithExceptionsWrapper notifyDisposalScheduleDeleted(String disposalScheduleId, boolean commit) { + return getModelService().notifyDisposalScheduleDeleted(disposalScheduleId, commit); + } + + @Override + public ReturnWithExceptionsWrapper notifyDisposalHoldCreatedOrUpdated(DisposalHold hold) { + return getModelService().notifyDisposalHoldCreatedOrUpdated(hold); + } + + @Override + public ReturnWithExceptionsWrapper notifyDisposalHoldDeleted(String disposalHoldId, boolean commit) { + return getModelService().notifyDisposalHoldDeleted(disposalHoldId, commit); + } + + @Override + public ReturnWithExceptionsWrapper notifyDisposalRuleCreatedOrUpdated(DisposalRule rule) { + return getModelService().notifyDisposalRuleCreatedOrUpdated(rule); + } + + @Override + public ReturnWithExceptionsWrapper notifyDisposalRuleDeleted(String disposalRuleId, boolean commit) { + return getModelService().notifyDisposalRuleDeleted(disposalRuleId, commit); + } + @Override public void commit() throws RODATransactionException { for (TransactionalModelOperationLog modelOperation : transactionLogService diff --git a/roda-core/roda-core/src/main/java/org/roda/core/model/LiteRODAObjectFactory.java b/roda-core/roda-core/src/main/java/org/roda/core/model/LiteRODAObjectFactory.java index 906fe14ea8..c21b9b4a88 100644 --- a/roda-core/roda-core/src/main/java/org/roda/core/model/LiteRODAObjectFactory.java +++ b/roda-core/roda-core/src/main/java/org/roda/core/model/LiteRODAObjectFactory.java @@ -195,6 +195,12 @@ public static Optional get(T object) { ret = getIndexedPreservationAgent(object); } else if (object instanceof DisposalHold) { ret = get(DisposalHold.class, Arrays.asList(object.getId()), false); + } else if (object instanceof DisposalSchedule) { + ret = get(DisposalSchedule.class, Arrays.asList(object.getId()), false); + } else if (object instanceof DisposalHold) { + ret = get(DisposalHold.class, Arrays.asList(object.getId()), false); + } else if (object instanceof DisposalRule) { + ret = get(DisposalRule.class, Arrays.asList(object.getId()), false); } if (!ret.isPresent()) { diff --git a/roda-core/roda-core/src/main/java/org/roda/core/model/ModelObservable.java b/roda-core/roda-core/src/main/java/org/roda/core/model/ModelObservable.java index bbfb2f6823..5ac3ccf738 100644 --- a/roda-core/roda-core/src/main/java/org/roda/core/model/ModelObservable.java +++ b/roda-core/roda-core/src/main/java/org/roda/core/model/ModelObservable.java @@ -9,6 +9,9 @@ import org.roda.core.common.ReturnWithExceptionsWrapper; import org.roda.core.data.v2.disposal.confirmation.DisposalConfirmation; +import org.roda.core.data.v2.disposal.hold.DisposalHold; +import org.roda.core.data.v2.disposal.rule.DisposalRule; +import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; import org.roda.core.data.v2.ip.AIP; import org.roda.core.data.v2.ip.DIP; import org.roda.core.data.v2.ip.DIPFile; @@ -34,123 +37,133 @@ * @author Gabriel Barros */ public interface ModelObservable { - void addModelObserver(ModelObserver observer); + void addModelObserver(ModelObserver observer); - void removeModelObserver(ModelObserver observer); + void removeModelObserver(ModelObserver observer); - ReturnWithExceptionsWrapper notifyAipCreated(AIP aip); + ReturnWithExceptionsWrapper notifyAipCreated(AIP aip); - ReturnWithExceptionsWrapper notifyAipUpdated(AIP aip); + ReturnWithExceptionsWrapper notifyAipUpdated(AIP aip); - ReturnWithExceptionsWrapper notifyAipUpdatedOnChanged(AIP aip); + ReturnWithExceptionsWrapper notifyAipUpdatedOnChanged(AIP aip); - ReturnWithExceptionsWrapper notifyAipOnHoldStatusUpdated(AIP aip, boolean status); + ReturnWithExceptionsWrapper notifyAipOnHoldStatusUpdated(AIP aip, boolean status); - ReturnWithExceptionsWrapper notifyAipDestroyed(AIP aip); + ReturnWithExceptionsWrapper notifyAipDestroyed(AIP aip); - ReturnWithExceptionsWrapper notifyAipMoved(AIP aip, String oldParentId, String newParentId); + ReturnWithExceptionsWrapper notifyAipMoved(AIP aip, String oldParentId, String newParentId); - ReturnWithExceptionsWrapper notifyAipStateUpdated(AIP aip); + ReturnWithExceptionsWrapper notifyAipStateUpdated(AIP aip); - ReturnWithExceptionsWrapper notifyAipInstanceIdUpdated(AIP aip); + ReturnWithExceptionsWrapper notifyAipInstanceIdUpdated(AIP aip); - ReturnWithExceptionsWrapper notifyAipDeleted(String aipId); + ReturnWithExceptionsWrapper notifyAipDeleted(String aipId); - ReturnWithExceptionsWrapper notifyDescriptiveMetadataCreated(DescriptiveMetadata descriptiveMetadata); + ReturnWithExceptionsWrapper notifyDescriptiveMetadataCreated(DescriptiveMetadata descriptiveMetadata); - ReturnWithExceptionsWrapper notifyDescriptiveMetadataUpdated(DescriptiveMetadata descriptiveMetadata); + ReturnWithExceptionsWrapper notifyDescriptiveMetadataUpdated(DescriptiveMetadata descriptiveMetadata); - ReturnWithExceptionsWrapper notifyDescriptiveMetadataDeleted(String aipId, String representationId, - String descriptiveMetadataBinaryId); + ReturnWithExceptionsWrapper notifyDescriptiveMetadataDeleted(String aipId, String representationId, + String descriptiveMetadataBinaryId); - ReturnWithExceptionsWrapper notifyRepresentationCreated(Representation representation); + ReturnWithExceptionsWrapper notifyRepresentationCreated(Representation representation); - ReturnWithExceptionsWrapper notifyRepresentationUpdated(Representation representation); + ReturnWithExceptionsWrapper notifyRepresentationUpdated(Representation representation); - ReturnWithExceptionsWrapper notifyRepresentationDeleted(String aipId, String representationId); + ReturnWithExceptionsWrapper notifyRepresentationDeleted(String aipId, String representationId); - ReturnWithExceptionsWrapper notifyRepresentationUpdatedOnChanged(Representation representation); + ReturnWithExceptionsWrapper notifyRepresentationUpdatedOnChanged(Representation representation); - ReturnWithExceptionsWrapper notifyFileCreated(File file); + ReturnWithExceptionsWrapper notifyFileCreated(File file); - ReturnWithExceptionsWrapper notifyFileUpdated(File file); + ReturnWithExceptionsWrapper notifyFileUpdated(File file); - ReturnWithExceptionsWrapper notifyFileDeleted(String aipId, String representationId, - List fileDirectoryPath, String fileId); + ReturnWithExceptionsWrapper notifyFileDeleted(String aipId, String representationId, List fileDirectoryPath, + String fileId); - ReturnWithExceptionsWrapper notifyLogEntryCreated(LogEntry entry); + ReturnWithExceptionsWrapper notifyLogEntryCreated(LogEntry entry); - ReturnWithExceptionsWrapper notifyUserCreated(User user); + ReturnWithExceptionsWrapper notifyUserCreated(User user); - ReturnWithExceptionsWrapper notifyUserUpdated(User user); + ReturnWithExceptionsWrapper notifyUserUpdated(User user); - ReturnWithExceptionsWrapper notifyUserDeleted(String userID); + ReturnWithExceptionsWrapper notifyUserDeleted(String userID); - ReturnWithExceptionsWrapper notifyGroupCreated(Group group); + ReturnWithExceptionsWrapper notifyGroupCreated(Group group); - ReturnWithExceptionsWrapper notifyGroupUpdated(Group group); + ReturnWithExceptionsWrapper notifyGroupUpdated(Group group); - ReturnWithExceptionsWrapper notifyGroupDeleted(String groupID); + ReturnWithExceptionsWrapper notifyGroupDeleted(String groupID); - ReturnWithExceptionsWrapper notifyPreservationMetadataCreated( - PreservationMetadata preservationMetadataBinary); + ReturnWithExceptionsWrapper notifyPreservationMetadataCreated(PreservationMetadata preservationMetadataBinary); - ReturnWithExceptionsWrapper notifyPreservationMetadataUpdated( - PreservationMetadata preservationMetadataBinary); + ReturnWithExceptionsWrapper notifyPreservationMetadataUpdated(PreservationMetadata preservationMetadataBinary); - ReturnWithExceptionsWrapper notifyPreservationMetadataDeleted(PreservationMetadata pm); + ReturnWithExceptionsWrapper notifyPreservationMetadataDeleted(PreservationMetadata pm); - ReturnWithExceptionsWrapper notifyOtherMetadataCreated(OtherMetadata otherMetadataBinary); + ReturnWithExceptionsWrapper notifyOtherMetadataCreated(OtherMetadata otherMetadataBinary); - ReturnWithExceptionsWrapper notifyJobCreatedOrUpdated(Job job, boolean reindexJobReports); + ReturnWithExceptionsWrapper notifyJobCreatedOrUpdated(Job job, boolean reindexJobReports); - ReturnWithExceptionsWrapper notifyJobDeleted(String jobId); + ReturnWithExceptionsWrapper notifyJobDeleted(String jobId); - ReturnWithExceptionsWrapper notifyJobReportCreatedOrUpdated(Report jobReport, Job cachedJob); + ReturnWithExceptionsWrapper notifyJobReportCreatedOrUpdated(Report jobReport, Job cachedJob); - ReturnWithExceptionsWrapper notifyJobReportCreatedOrUpdated(Report jobReport, IndexedJob indexedJob); + ReturnWithExceptionsWrapper notifyJobReportCreatedOrUpdated(Report jobReport, IndexedJob indexedJob); - ReturnWithExceptionsWrapper notifyJobReportDeleted(String jobReportId); + ReturnWithExceptionsWrapper notifyJobReportDeleted(String jobReportId); - ReturnWithExceptionsWrapper notifyAipPermissionsUpdated(AIP aip); + ReturnWithExceptionsWrapper notifyAipPermissionsUpdated(AIP aip); - ReturnWithExceptionsWrapper notifyDipPermissionsUpdated(DIP dip); + ReturnWithExceptionsWrapper notifyDipPermissionsUpdated(DIP dip); - ReturnWithExceptionsWrapper notifyDipInstanceIdUpdated(DIP dip); + ReturnWithExceptionsWrapper notifyDipInstanceIdUpdated(DIP dip); - ReturnWithExceptionsWrapper notifyTransferredResourceDeleted(String transferredResourceID); + ReturnWithExceptionsWrapper notifyTransferredResourceDeleted(String transferredResourceID); - ReturnWithExceptionsWrapper notifyRiskCreatedOrUpdated(Risk risk, int incidences, boolean commit); + ReturnWithExceptionsWrapper notifyRiskCreatedOrUpdated(Risk risk, int incidences, boolean commit); - ReturnWithExceptionsWrapper notifyRiskDeleted(String riskId, boolean commit); + ReturnWithExceptionsWrapper notifyRiskDeleted(String riskId, boolean commit); - ReturnWithExceptionsWrapper notifyRiskIncidenceCreatedOrUpdated(RiskIncidence riskIncidence, boolean commit); + ReturnWithExceptionsWrapper notifyRiskIncidenceCreatedOrUpdated(RiskIncidence riskIncidence, boolean commit); - ReturnWithExceptionsWrapper notifyRiskIncidenceDeleted(String riskIncidenceId, boolean commit); + ReturnWithExceptionsWrapper notifyRiskIncidenceDeleted(String riskIncidenceId, boolean commit); - ReturnWithExceptionsWrapper notifyRepresentationInformationCreatedOrUpdated(RepresentationInformation ri, - boolean commit); + ReturnWithExceptionsWrapper notifyRepresentationInformationCreatedOrUpdated(RepresentationInformation ri, + boolean commit); - ReturnWithExceptionsWrapper notifyRepresentationInformationDeleted(String representationInformationId, - boolean commit); + ReturnWithExceptionsWrapper notifyRepresentationInformationDeleted(String representationInformationId, + boolean commit); - ReturnWithExceptionsWrapper notifyNotificationCreatedOrUpdated(Notification notification); + ReturnWithExceptionsWrapper notifyNotificationCreatedOrUpdated(Notification notification); - ReturnWithExceptionsWrapper notifyNotificationDeleted(String notificationId); + ReturnWithExceptionsWrapper notifyNotificationDeleted(String notificationId); - ReturnWithExceptionsWrapper notifyDIPCreated(DIP dip, boolean commit); + ReturnWithExceptionsWrapper notifyDIPCreated(DIP dip, boolean commit); - ReturnWithExceptionsWrapper notifyDIPUpdated(DIP dip, boolean commit); + ReturnWithExceptionsWrapper notifyDIPUpdated(DIP dip, boolean commit); - ReturnWithExceptionsWrapper notifyDIPDeleted(String dipId, boolean commit); + ReturnWithExceptionsWrapper notifyDIPDeleted(String dipId, boolean commit); - ReturnWithExceptionsWrapper notifyDIPFileCreated(DIPFile file); + ReturnWithExceptionsWrapper notifyDIPFileCreated(DIPFile file); - ReturnWithExceptionsWrapper notifyDIPFileUpdated(DIPFile file); + ReturnWithExceptionsWrapper notifyDIPFileUpdated(DIPFile file); - ReturnWithExceptionsWrapper notifyDIPFileDeleted(String dipId, List path, String fileId); + ReturnWithExceptionsWrapper notifyDIPFileDeleted(String dipId, List path, String fileId); - ReturnWithExceptionsWrapper notifyDisposalConfirmationCreatedOrUpdated(DisposalConfirmation confirmation); + ReturnWithExceptionsWrapper notifyDisposalConfirmationCreatedOrUpdated(DisposalConfirmation confirmation); - ReturnWithExceptionsWrapper notifyDisposalConfirmationDeleted(String disposalConfirmationId, boolean commit); + ReturnWithExceptionsWrapper notifyDisposalConfirmationDeleted(String disposalConfirmationId, boolean commit); + + ReturnWithExceptionsWrapper notifyDisposalScheduleCreatedOrUpdated(DisposalSchedule schedule); + + ReturnWithExceptionsWrapper notifyDisposalScheduleDeleted(String disposalScheduleId, boolean commit); + + ReturnWithExceptionsWrapper notifyDisposalHoldCreatedOrUpdated(DisposalHold hold); + + ReturnWithExceptionsWrapper notifyDisposalHoldDeleted(String disposalHoldId, boolean commit); + + ReturnWithExceptionsWrapper notifyDisposalRuleCreatedOrUpdated(DisposalRule rule); + + ReturnWithExceptionsWrapper notifyDisposalRuleDeleted(String disposalRuleId, boolean commit); } diff --git a/roda-core/roda-core/src/main/java/org/roda/core/model/ModelObserver.java b/roda-core/roda-core/src/main/java/org/roda/core/model/ModelObserver.java index bdd62307e3..9cb01ee8f7 100644 --- a/roda-core/roda-core/src/main/java/org/roda/core/model/ModelObserver.java +++ b/roda-core/roda-core/src/main/java/org/roda/core/model/ModelObserver.java @@ -12,6 +12,9 @@ import org.roda.core.data.exceptions.ReturnWithExceptions; import org.roda.core.data.v2.LiteRODAObject; import org.roda.core.data.v2.disposal.confirmation.DisposalConfirmation; +import org.roda.core.data.v2.disposal.hold.DisposalHold; +import org.roda.core.data.v2.disposal.rule.DisposalRule; +import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; import org.roda.core.data.v2.ip.AIP; import org.roda.core.data.v2.ip.DIP; import org.roda.core.data.v2.ip.DIPFile; @@ -155,6 +158,18 @@ public ReturnWithExceptions disposalConfirmationCreateOrUpd public ReturnWithExceptions disposalConfirmationDeleted(String confirmationId, boolean commit); + ReturnWithExceptions disposalScheduleCreatedOrUpdated(DisposalSchedule schedule); + + ReturnWithExceptions disposalScheduleDeleted(String scheduleId, boolean commit); + + ReturnWithExceptions disposalHoldCreatedOrUpdated(DisposalHold hold); + + ReturnWithExceptions disposalHoldDeleted(String holdId, boolean commit); + + ReturnWithExceptions disposalRuleCreatedOrUpdated(DisposalRule rule); + + ReturnWithExceptions disposalRuleDeleted(String ruleId, boolean commit); + public ReturnWithExceptions liteRODAObjectCreated(LiteRODAObject liteRODAObject); public ReturnWithExceptions liteRODAObjectUpdated(LiteRODAObject liteRODAObject); diff --git a/roda-core/roda-core/src/main/java/org/roda/core/model/utils/ModelUtils.java b/roda-core/roda-core/src/main/java/org/roda/core/model/utils/ModelUtils.java index 06652ca493..168d5bc67c 100644 --- a/roda-core/roda-core/src/main/java/org/roda/core/model/utils/ModelUtils.java +++ b/roda-core/roda-core/src/main/java/org/roda/core/model/utils/ModelUtils.java @@ -32,6 +32,7 @@ import org.roda.core.data.v2.common.OptionalWithCause; import org.roda.core.data.v2.disposal.confirmation.DisposalConfirmation; import org.roda.core.data.v2.disposal.hold.DisposalHold; +import org.roda.core.data.v2.disposal.rule.DisposalRule; import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; import org.roda.core.data.v2.index.select.SelectedItems; import org.roda.core.data.v2.index.select.SelectedItemsFilter; @@ -718,6 +719,18 @@ public static String getJobId(StoragePath jobPath) { return jobPath.getName().replace(RodaConstants.JOB_FILE_EXTENSION, ""); } + public static String getDisposalScheduleId(StoragePath disposalSchedulePath) { + return disposalSchedulePath.getName().replace(RodaConstants.DISPOSAL_SCHEDULE_FILE_EXTENSION, ""); + } + + public static String getDisposalHoldId(StoragePath disposalHoldPath) { + return disposalHoldPath.getName().replace(RodaConstants.DISPOSAL_HOLD_FILE_EXTENSION, ""); + } + + public static String getDisposalRuleId(StoragePath disposalRulePath) { + return disposalRulePath.getName().replace(RodaConstants.DISPOSAL_RULE_FILE_EXTENSION, ""); + } + public static StoragePath getJobReportContainerPath() throws RequestNotValidException { return DefaultStoragePath.parse(RodaConstants.STORAGE_CONTAINER_JOB_REPORT); } @@ -1051,6 +1064,8 @@ public static StoragePath getContainerPath(Class cla return getDisposalScheduleContainerPath(); } else if (clazz.equals(DisposalHold.class)) { return getDisposalHoldContainerPath(); + } else if (clazz.equals(DisposalRule.class)) { + return getDisposalRuleContainerPath(); } else if (clazz.equals(DisposalConfirmation.class)) { return getDisposalConfirmationContainerPath(); } else if (clazz.equals(DistributedInstance.class)) { diff --git a/roda-core/roda-core/src/main/java/org/roda/core/model/utils/ResourceParseUtils.java b/roda-core/roda-core/src/main/java/org/roda/core/model/utils/ResourceParseUtils.java index 4efe7bf847..f5949b0016 100644 --- a/roda-core/roda-core/src/main/java/org/roda/core/model/utils/ResourceParseUtils.java +++ b/roda-core/roda-core/src/main/java/org/roda/core/model/utils/ResourceParseUtils.java @@ -37,6 +37,9 @@ import org.roda.core.data.v2.LiteRODAObject; import org.roda.core.data.v2.common.OptionalWithCause; import org.roda.core.data.v2.disposal.confirmation.DisposalConfirmation; +import org.roda.core.data.v2.disposal.hold.DisposalHold; +import org.roda.core.data.v2.disposal.rule.DisposalRule; +import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; import org.roda.core.data.v2.ip.AIP; import org.roda.core.data.v2.ip.DIP; import org.roda.core.data.v2.ip.DIPFile; @@ -387,7 +390,14 @@ public static OptionalWithCause convert } ids.add(fileName.replace(RodaConstants.PREMIS_SUFFIX, "")); ret = OptionalWithCause.of(LiteRODAObjectFactory.get(classToReturn, ids)); - } else { + } else if (classToReturn.equals(DisposalSchedule.class)) { + ret = OptionalWithCause.of(LiteRODAObjectFactory.get(classToReturn, ModelUtils.getDisposalScheduleId(storagePath))); + } else if (classToReturn.equals(DisposalHold.class)) { + ret = OptionalWithCause.of(LiteRODAObjectFactory.get(classToReturn, ModelUtils.getDisposalHoldId(storagePath))); + } else if (classToReturn.equals(DisposalRule.class)) { + ret = OptionalWithCause.of(LiteRODAObjectFactory.get(classToReturn, ModelUtils.getDisposalRuleId(storagePath))); + } + else { ret = OptionalWithCause.of(LiteRODAObjectFactory.get(classToReturn, fileName)); } diff --git a/roda-core/roda-core/src/main/java/org/roda/core/plugins/PluginHelper.java b/roda-core/roda-core/src/main/java/org/roda/core/plugins/PluginHelper.java index a5826ab416..e07ca01c9c 100644 --- a/roda-core/roda-core/src/main/java/org/roda/core/plugins/PluginHelper.java +++ b/roda-core/roda-core/src/main/java/org/roda/core/plugins/PluginHelper.java @@ -54,6 +54,9 @@ import org.roda.core.data.v2.Void; import org.roda.core.data.v2.common.OptionalWithCause; import org.roda.core.data.v2.disposal.confirmation.DisposalConfirmation; +import org.roda.core.data.v2.disposal.hold.DisposalHold; +import org.roda.core.data.v2.disposal.rule.DisposalRule; +import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; import org.roda.core.data.v2.index.IndexResult; import org.roda.core.data.v2.index.facet.Facets; import org.roda.core.data.v2.index.filter.Filter; @@ -97,6 +100,9 @@ import org.roda.core.plugins.base.maintenance.reindex.ReindexActionLogPlugin; import org.roda.core.plugins.base.maintenance.reindex.ReindexDIPPlugin; import org.roda.core.plugins.base.maintenance.reindex.ReindexDisposalConfirmationPlugin; +import org.roda.core.plugins.base.maintenance.reindex.ReindexDisposalHoldPlugin; +import org.roda.core.plugins.base.maintenance.reindex.ReindexDisposalRulePlugin; +import org.roda.core.plugins.base.maintenance.reindex.ReindexDisposalSchedulePlugin; import org.roda.core.plugins.base.maintenance.reindex.ReindexIncidencePlugin; import org.roda.core.plugins.base.maintenance.reindex.ReindexJobPlugin; import org.roda.core.plugins.base.maintenance.reindex.ReindexNotificationPlugin; @@ -731,6 +737,9 @@ public static List> getReindexObjectClasses() { list.add(IndexedPreservationAgent.class); list.add(DIP.class); list.add(DisposalConfirmation.class); + list.add(DisposalSchedule.class); + list.add(DisposalHold.class); + list.add(DisposalRule.class); return list; } @@ -1605,6 +1614,12 @@ public static String getReindexPluginName(Class reindexClass) throws NotFound return ReindexDIPPlugin.class.getName(); } else if (reindexClass.equals(DisposalConfirmation.class)) { return ReindexDisposalConfirmationPlugin.class.getName(); + } else if (reindexClass.equals(DisposalSchedule.class)) { + return ReindexDisposalSchedulePlugin.class.getName(); + } else if (reindexClass.equals(DisposalHold.class)) { + return ReindexDisposalHoldPlugin.class.getName(); + } else if (reindexClass.equals(DisposalRule.class)) { + return ReindexDisposalRulePlugin.class.getName(); } else { throw new NotFoundException("No reindex plugin available"); } diff --git a/roda-core/roda-core/src/main/java/org/roda/core/plugins/base/disposal/hold/LiftDisposalHoldPlugin.java b/roda-core/roda-core/src/main/java/org/roda/core/plugins/base/disposal/hold/LiftDisposalHoldPlugin.java index 20d7d98840..ad793519f5 100644 --- a/roda-core/roda-core/src/main/java/org/roda/core/plugins/base/disposal/hold/LiftDisposalHoldPlugin.java +++ b/roda-core/roda-core/src/main/java/org/roda/core/plugins/base/disposal/hold/LiftDisposalHoldPlugin.java @@ -25,7 +25,6 @@ import org.roda.core.data.exceptions.RequestNotValidException; import org.roda.core.data.v2.LiteOptionalWithCause; import org.roda.core.data.v2.Void; -import org.roda.core.data.v2.common.Pair; import org.roda.core.data.v2.disposal.hold.DisposalHold; import org.roda.core.data.v2.disposal.hold.DisposalHoldState; import org.roda.core.data.v2.index.filter.Filter; @@ -195,7 +194,8 @@ private void liftDisposalHold(IndexService index, ModelService model, Report rep try { AIP aip = model.retrieveAIP(indexedAIP.getId()); - long children = processTransitiveAIP(model, index, cachedJob, aip.getId(), disposalHoldId, jobPluginInfo, report); + long children = processTransitiveAIP(model, index, cachedJob, aip.getId(), disposalHoldId, jobPluginInfo, + report); model.updateAIPOnHoldStatus(aip, model.onDisposalHold(aip.getId())); jobPluginInfo.incrementObjectsProcessedWithSuccess(); reportItem.setPluginState(state); diff --git a/roda-core/roda-core/src/main/java/org/roda/core/plugins/base/maintenance/reindex/ReindexDisposalHoldPlugin.java b/roda-core/roda-core/src/main/java/org/roda/core/plugins/base/maintenance/reindex/ReindexDisposalHoldPlugin.java new file mode 100644 index 0000000000..6152088658 --- /dev/null +++ b/roda-core/roda-core/src/main/java/org/roda/core/plugins/base/maintenance/reindex/ReindexDisposalHoldPlugin.java @@ -0,0 +1,47 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE file at the root of the source + * tree and available online at + * + * https://github.com/keeps/roda + */ +package org.roda.core.plugins.base.maintenance.reindex; + +import java.util.Collections; +import java.util.List; + +import org.roda.core.data.exceptions.GenericException; +import org.roda.core.data.exceptions.RequestNotValidException; +import org.roda.core.data.v2.disposal.hold.DisposalHold; +import org.roda.core.index.IndexService; +import org.roda.core.plugins.Plugin; + +public class ReindexDisposalHoldPlugin extends ReindexRodaEntityPlugin { + + @Override + public String getName() { + return "Rebuild disposal hold index"; + } + + @Override + public String getVersionImpl() { + return "1.0"; + } + + @Override + public Plugin cloneMe() { + return new ReindexDisposalHoldPlugin(); + } + + @Override + public List> getObjectClasses() { + return Collections.singletonList(DisposalHold.class); + } + + @Override + public void clearSpecificIndexes(IndexService index, List ids) + throws GenericException, RequestNotValidException { + // do nothing + } + +} diff --git a/roda-core/roda-core/src/main/java/org/roda/core/plugins/base/maintenance/reindex/ReindexDisposalRulePlugin.java b/roda-core/roda-core/src/main/java/org/roda/core/plugins/base/maintenance/reindex/ReindexDisposalRulePlugin.java new file mode 100644 index 0000000000..506546f37f --- /dev/null +++ b/roda-core/roda-core/src/main/java/org/roda/core/plugins/base/maintenance/reindex/ReindexDisposalRulePlugin.java @@ -0,0 +1,47 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE file at the root of the source + * tree and available online at + * + * https://github.com/keeps/roda + */ +package org.roda.core.plugins.base.maintenance.reindex; + +import java.util.Collections; +import java.util.List; + +import org.roda.core.data.exceptions.GenericException; +import org.roda.core.data.exceptions.RequestNotValidException; +import org.roda.core.data.v2.disposal.rule.DisposalRule; +import org.roda.core.index.IndexService; +import org.roda.core.plugins.Plugin; + +public class ReindexDisposalRulePlugin extends ReindexRodaEntityPlugin { + + @Override + public String getName() { + return "Rebuild disposal rule index"; + } + + @Override + public String getVersionImpl() { + return "1.0"; + } + + @Override + public Plugin cloneMe() { + return new ReindexDisposalRulePlugin(); + } + + @Override + public List> getObjectClasses() { + return Collections.singletonList(DisposalRule.class); + } + + @Override + public void clearSpecificIndexes(IndexService index, List ids) + throws GenericException, RequestNotValidException { + // do nothing + } + +} diff --git a/roda-core/roda-core/src/main/java/org/roda/core/plugins/base/maintenance/reindex/ReindexDisposalSchedulePlugin.java b/roda-core/roda-core/src/main/java/org/roda/core/plugins/base/maintenance/reindex/ReindexDisposalSchedulePlugin.java new file mode 100644 index 0000000000..5599bfda47 --- /dev/null +++ b/roda-core/roda-core/src/main/java/org/roda/core/plugins/base/maintenance/reindex/ReindexDisposalSchedulePlugin.java @@ -0,0 +1,47 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE file at the root of the source + * tree and available online at + * + * https://github.com/keeps/roda + */ +package org.roda.core.plugins.base.maintenance.reindex; + +import java.util.Collections; +import java.util.List; + +import org.roda.core.data.exceptions.GenericException; +import org.roda.core.data.exceptions.RequestNotValidException; +import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; +import org.roda.core.index.IndexService; +import org.roda.core.plugins.Plugin; + +public class ReindexDisposalSchedulePlugin extends ReindexRodaEntityPlugin { + + @Override + public String getName() { + return "Rebuild disposal schedule index"; + } + + @Override + public String getVersionImpl() { + return "1.0"; + } + + @Override + public Plugin cloneMe() { + return new ReindexDisposalSchedulePlugin(); + } + + @Override + public List> getObjectClasses() { + return Collections.singletonList(DisposalSchedule.class); + } + + @Override + public void clearSpecificIndexes(IndexService index, List ids) + throws GenericException, RequestNotValidException { + // do nothing + } + +} diff --git a/roda-core/roda-core/src/main/resources/config/roda-roles.properties b/roda-core/roda-core/src/main/resources/config/roda-roles.properties index e54f2efb76..519beb7efd 100644 --- a/roda-core/roda-core/src/main/resources/config/roda-roles.properties +++ b/roda-core/roda-core/src/main/resources/config/roda-roles.properties @@ -181,6 +181,7 @@ core.roles.org.roda.wui.api.v2.controller.DisposalRuleController.retrieveDisposa core.roles.org.roda.wui.api.v2.controller.DisposalRuleController.updateDisposalRule = disposal_hold.manage core.roles.org.roda.wui.api.v2.controller.DisposalRuleController.deleteDisposalRule = disposal_rule.manage core.roles.org.roda.wui.api.v2.controller.DisposalRuleController.applyDisposalRules = disposal_schedule.associate +core.roles.org.roda.wui.api.v2.controller.DisposalRuleController.changeDisposalRuleOrder = disposal_rule.manage # Representation information core.roles.org.roda.wui.api.v2.controller.RepresentationInformationController.retrieveRepresentationInformationFamily = ri.read @@ -284,7 +285,6 @@ core.roles.org.roda.wui.api.v2.services.IndexService.find(IndexedReport) = job. core.roles.org.roda.wui.api.v2.services.IndexService.find(IndexedPreservationEvent) = preservation_metadata.read core.roles.org.roda.wui.api.v2.services.IndexService.find(IndexedPreservationAgent) = preservation_metadata.read core.roles.org.roda.wui.api.v2.services.IndexService.find(LogEntry) = log_entry.read -core.roles.org.roda.wui.api.v2.services.IndexService.find(DisposalConfirmation) = disposal_confirmation.read core.roles.org.roda.wui.api.v2.services.IndexService.find(RepresentationInformation) = ri.read core.roles.org.roda.wui.api.v2.services.IndexService.find(RODAMember) = member.read core.roles.org.roda.wui.api.v2.services.IndexService.find(IndexedDIP) = aip.view @@ -292,6 +292,10 @@ core.roles.org.roda.wui.api.v2.services.IndexService.find(IndexedDIP) = aip.read core.roles.org.roda.wui.api.v2.services.IndexService.find(IndexedFile) = representation.read core.roles.org.roda.wui.api.v2.services.IndexService.find(IndexedRepresentation) = representation.read core.roles.org.roda.wui.api.v2.services.IndexService.find(DIPFile) = aip.read +core.roles.org.roda.wui.api.v2.services.IndexService.find(DisposalSchedule) = disposal_schedule.read +core.roles.org.roda.wui.api.v2.services.IndexService.find(DisposalRule) = disposal_rule.read +core.roles.org.roda.wui.api.v2.services.IndexService.find(DisposalHold) = disposal_hold.read +core.roles.org.roda.wui.api.v2.services.IndexService.find(DisposalConfirmation) = disposal_confirmation.read # Generic count roles core.roles.org.roda.wui.api.v2.services.IndexService.count(TransferredResource) = transfer.read @@ -317,6 +321,10 @@ core.roles.org.roda.wui.api.v2.services.IndexService.count(IndexedFile) = aip.vi core.roles.org.roda.wui.api.v2.services.IndexService.count(IndexedFile) = aip.read core.roles.org.roda.wui.api.v2.services.IndexService.count(DIPFile) = aip.read core.roles.org.roda.wui.api.v2.services.IndexService.count(IndexedReport) = job.read +core.roles.org.roda.wui.api.v2.services.IndexService.count(DisposalSchedule) = disposal_schedule.read +core.roles.org.roda.wui.api.v2.services.IndexService.count(DisposalRule) = disposal_rule.read +core.roles.org.roda.wui.api.v2.services.IndexService.count(DisposalHold) = disposal_hold.read +core.roles.org.roda.wui.api.v2.services.IndexService.count(DisposalConfirmation) = disposal_confirmation.read # Generic suggest roles core.roles.org.roda.wui.api.v2.services.IndexService.suggest(IndexedAIP) = aip.read diff --git a/roda-ui/roda-wui/src/main/java/config/i18n/client/ClientMessages.java b/roda-ui/roda-wui/src/main/java/config/i18n/client/ClientMessages.java index 52eb703443..ee0e07f230 100644 --- a/roda-ui/roda-wui/src/main/java/config/i18n/client/ClientMessages.java +++ b/roda-ui/roda-wui/src/main/java/config/i18n/client/ClientMessages.java @@ -1408,7 +1408,7 @@ SafeHtml searchPreFilterLongRangeFilterParameterGreaterThan(String searchPreFilt String permissionAssignedUsersEmpty(); - String listOfAIPs(); + String listOfAIPs(); String listOfRepresentations(); @@ -2105,6 +2105,12 @@ SafeHtml representationInformationAssociatedWithDescription(String field, String String disposalScheduleUsedInRule(); + String disposalScheduleRemoveConfirmDialogTitle(); + + String disposalScheduleRemoveConfirmDialogMessage(); + + String disposalScheduleRemovedWithSuccess(); + String disposalHoldIdentifier(); String disposalHoldTitle(); @@ -2665,6 +2671,8 @@ SafeHtml representationInformationAssociatedWithDescription(String field, String String reasonCantActOnGroup(); + String reasonHoldAlreadyLifted(); + // RODA Members - toolbar actions String deactivateUserTitle(); @@ -2747,4 +2755,22 @@ SafeHtml representationInformationAssociatedWithDescription(String field, String String emailViewerLoadImagesOnce(); String emailViewerAlwaysTrustSender(); + + String disposalHoldSuccessfullyCreated(); + + String disposalHoldSuccessfullyUpdated(); + + String updateDisposalRuleOrderSuccessMessage(); + + String moveToTop(); + + String moveToBottom(); + + String moveToPositionNumber(); + + String disposalRuleChangeOrderMessage(); + + String disposalRuleSuccessfullyCreated(); + + String disposalRuleSuccessfullyUpdated(); } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/controller/DisposalHoldController.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/controller/DisposalHoldController.java index 432de2aba1..1b74633d2c 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/controller/DisposalHoldController.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/controller/DisposalHoldController.java @@ -8,7 +8,10 @@ package org.roda.wui.api.v2.controller; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import jakarta.servlet.http.HttpServletRequest; import org.roda.core.data.common.RodaConstants; import org.roda.core.data.exceptions.RODAException; import org.roda.core.data.utils.JsonUtils; @@ -16,17 +19,25 @@ import org.roda.core.data.v2.disposal.hold.DisposalHolds; import org.roda.core.data.v2.disposal.metadata.DisposalHoldsAIPMetadata; import org.roda.core.data.v2.disposal.metadata.DisposalTransitiveHoldsAIPMetadata; +import org.roda.core.data.v2.generics.LongResponse; import org.roda.core.data.v2.generics.select.SelectedItemsRequest; +import org.roda.core.data.v2.index.CountRequest; +import org.roda.core.data.v2.index.FindRequest; +import org.roda.core.data.v2.index.IndexResult; +import org.roda.core.data.v2.index.SuggestRequest; import org.roda.core.data.v2.ip.IndexedAIP; import org.roda.core.data.v2.ip.disposalhold.DisassociateDisposalHoldRequest; import org.roda.core.data.v2.ip.disposalhold.UpdateDisposalHoldRequest; import org.roda.core.data.v2.jobs.Job; +import org.roda.core.model.utils.UserUtility; import org.roda.wui.api.v2.exceptions.RESTException; import org.roda.wui.api.v2.services.DisposalHoldService; +import org.roda.wui.api.v2.services.IndexService; import org.roda.wui.api.v2.utils.CommonServicesUtils; import org.roda.wui.client.services.DisposalHoldRestService; import org.roda.wui.common.RequestControllerAssistant; import org.roda.wui.common.model.RequestContext; +import org.roda.wui.common.utils.RequestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -40,6 +51,11 @@ @RestController @RequestMapping(path = "/api/v2/disposal/holds") public class DisposalHoldController implements DisposalHoldRestService { + @Autowired + HttpServletRequest request; + + @Autowired + IndexService indexService; @Autowired DisposalHoldService disposalHoldService; @@ -189,4 +205,29 @@ public DisposalHoldsAIPMetadata process(RequestContext requestContext, } }); } + + @Override + public DisposalHold findByUuid(String uuid, String localeString) { + return indexService.retrieve(DisposalHold.class, uuid, new ArrayList<>()); + } + + @Override + public IndexResult find(@RequestBody FindRequest findRequest, String localeString) { + return indexService.find(DisposalHold.class, findRequest, localeString); + } + + @Override + public LongResponse count(@RequestBody CountRequest countRequest) { + RequestContext requestContext = RequestUtils.parseHTTPRequest(request); + if (UserUtility.hasPermissions(requestContext.getUser(), RodaConstants.PERMISSION_METHOD_FIND_FILE)) { + return new LongResponse(indexService.count(DisposalHold.class, countRequest)); + } else { + return new LongResponse(-1L); + } + } + + @Override + public List suggest(@RequestBody SuggestRequest suggestRequest) { + return indexService.suggest(suggestRequest, DisposalHold.class); + } } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/controller/DisposalRuleController.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/controller/DisposalRuleController.java index b1cbffea16..9ac2beda40 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/controller/DisposalRuleController.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/controller/DisposalRuleController.java @@ -8,18 +8,32 @@ package org.roda.wui.api.v2.controller; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import jakarta.servlet.http.HttpServletRequest; import org.roda.core.data.common.RodaConstants; import org.roda.core.data.exceptions.RODAException; import org.roda.core.data.utils.JsonUtils; +import org.roda.core.data.v2.disposal.rule.ChangeOrderRequest; import org.roda.core.data.v2.disposal.rule.DisposalRule; import org.roda.core.data.v2.disposal.rule.DisposalRules; +import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; +import org.roda.core.data.v2.generics.LongResponse; +import org.roda.core.data.v2.index.CountRequest; +import org.roda.core.data.v2.index.FindRequest; +import org.roda.core.data.v2.index.IndexResult; +import org.roda.core.data.v2.index.SuggestRequest; import org.roda.core.data.v2.jobs.Job; +import org.roda.core.model.utils.UserUtility; import org.roda.wui.api.v2.exceptions.RESTException; import org.roda.wui.api.v2.services.DisposalRuleService; +import org.roda.wui.api.v2.services.DisposalScheduleService; +import org.roda.wui.api.v2.services.IndexService; import org.roda.wui.client.services.DisposalRuleRestService; import org.roda.wui.common.RequestControllerAssistant; import org.roda.wui.common.model.RequestContext; +import org.roda.wui.common.utils.RequestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -33,10 +47,18 @@ @RestController @RequestMapping(path = "/api/v2/disposal/rules") public class DisposalRuleController implements DisposalRuleRestService { + @Autowired + HttpServletRequest request; + + @Autowired + IndexService indexService; @Autowired DisposalRuleService disposalRuleService; + @Autowired + DisposalScheduleService disposalScheduleService; + @Autowired RequestHandler requestHandler; @@ -56,12 +78,17 @@ public DisposalRule retrieveDisposalRule(String id) { return requestHandler.processRequest(new RequestHandler.RequestProcessor() { @Override public DisposalRule process(RequestContext requestContext, RequestControllerAssistant controllerAssistant) - throws RODAException, RESTException, IOException { + throws RODAException, RESTException { // check user permissions controllerAssistant.setRelatedObjectId(id); controllerAssistant.setParameters(RodaConstants.DISPOSAL_RULE_ID, id); - return disposalRuleService.retrieveDisposalHold(id, requestContext.getModelService()); + DisposalRule disposalRule = disposalRuleService.retrieveDisposalHold(id, requestContext.getModelService()); + DisposalSchedule disposalSchedule = disposalScheduleService + .retrieveDisposalSchedule(requestContext.getModelService(), disposalRule.getDisposalScheduleId()); + disposalRule.setDisposalScheduleName(disposalSchedule.getTitle()); + + return disposalRule; } }); } @@ -138,4 +165,42 @@ public Job process(RequestContext requestContext, RequestControllerAssistant con } }); } + + @Override + public Void changeDisposalRuleOrder(@RequestBody ChangeOrderRequest request) { + return requestHandler.processRequestWithTransaction(new RequestHandler.RequestProcessor() { + @Override + public Void process(RequestContext requestContext, RequestControllerAssistant controllerAssistant) throws RODAException, RESTException, IOException { + + // delegate action to service + disposalRuleService.changeDisposalRuleOrder(requestContext, request); + return null; + } + }); + } + + @Override + public DisposalRule findByUuid(String uuid, String localeString) { + return indexService.retrieve(DisposalRule.class, uuid, new ArrayList<>()); + } + + @Override + public IndexResult find(@RequestBody FindRequest findRequest, String localeString) { + return indexService.find(DisposalRule.class, findRequest, localeString); + } + + @Override + public LongResponse count(@RequestBody CountRequest countRequest) { + RequestContext requestContext = RequestUtils.parseHTTPRequest(request); + if (UserUtility.hasPermissions(requestContext.getUser(), RodaConstants.PERMISSION_METHOD_FIND_FILE)) { + return new LongResponse(indexService.count(DisposalRule.class, countRequest)); + } else { + return new LongResponse(-1L); + } + } + + @Override + public List suggest(@RequestBody SuggestRequest suggestRequest) { + return indexService.suggest(suggestRequest, DisposalRule.class); + } } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/controller/DisposalScheduleController.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/controller/DisposalScheduleController.java index de7040cd22..fe7395d91c 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/controller/DisposalScheduleController.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/controller/DisposalScheduleController.java @@ -8,21 +8,35 @@ package org.roda.wui.api.v2.controller; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import jakarta.servlet.http.HttpServletRequest; import org.roda.core.data.common.RodaConstants; import org.roda.core.data.exceptions.RODAException; import org.roda.core.data.utils.JsonUtils; +import org.roda.core.data.v2.disposal.rule.DisposalRule; import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; import org.roda.core.data.v2.disposal.schedule.DisposalSchedules; +import org.roda.core.data.v2.generics.LongResponse; import org.roda.core.data.v2.generics.select.SelectedItemsRequest; +import org.roda.core.data.v2.index.CountRequest; +import org.roda.core.data.v2.index.FindRequest; +import org.roda.core.data.v2.index.IndexResult; +import org.roda.core.data.v2.index.SuggestRequest; +import org.roda.core.data.v2.index.filter.Filter; +import org.roda.core.data.v2.index.filter.SimpleFilterParameter; import org.roda.core.data.v2.ip.IndexedAIP; import org.roda.core.data.v2.jobs.Job; +import org.roda.core.model.utils.UserUtility; import org.roda.wui.api.v2.exceptions.RESTException; import org.roda.wui.api.v2.services.DisposalScheduleService; +import org.roda.wui.api.v2.services.IndexService; import org.roda.wui.api.v2.utils.CommonServicesUtils; import org.roda.wui.client.services.DisposalScheduleRestService; import org.roda.wui.common.RequestControllerAssistant; import org.roda.wui.common.model.RequestContext; +import org.roda.wui.common.utils.RequestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -37,6 +51,12 @@ @RequestMapping(path = "/api/v2/disposal/schedules") public class DisposalScheduleController implements DisposalScheduleRestService { + @Autowired + HttpServletRequest request; + + @Autowired + IndexService indexService; + @Autowired DisposalScheduleService disposalScheduleService; @@ -64,7 +84,21 @@ public DisposalSchedule process(RequestContext requestContext, RequestController controllerAssistant.setRelatedObjectId(id); // delegate action to service - return disposalScheduleService.retrieveDisposalSchedule(requestContext.getModelService(), id); + DisposalSchedule disposalSchedule = disposalScheduleService + .retrieveDisposalSchedule(requestContext.getModelService(), id); + + CountRequest countRequest = new CountRequest(); + SimpleFilterParameter filterParameter = new SimpleFilterParameter(RodaConstants.DISPOSAL_RULE_SCHEDULE_ID, id); + Filter filter = new Filter(); + filter.add(filterParameter); + + countRequest.setFilter(filter); + Long count = indexService.count(DisposalRule.class, countRequest); + if (count > 0) { + disposalSchedule.setUsedInDisposalRule(true); + } + + return disposalSchedule; } })); } @@ -157,4 +191,29 @@ public Job process(RequestContext requestContext, RequestControllerAssistant con } }); } + + @Override + public DisposalSchedule findByUuid(String uuid, String localeString) { + return indexService.retrieve(DisposalSchedule.class, uuid, new ArrayList<>()); + } + + @Override + public IndexResult find(@RequestBody FindRequest findRequest, String localeString) { + return indexService.find(DisposalSchedule.class, findRequest, localeString); + } + + @Override + public LongResponse count(@RequestBody CountRequest countRequest) { + RequestContext requestContext = RequestUtils.parseHTTPRequest(request); + if (UserUtility.hasPermissions(requestContext.getUser(), RodaConstants.PERMISSION_METHOD_FIND_FILE)) { + return new LongResponse(indexService.count(DisposalSchedule.class, countRequest)); + } else { + return new LongResponse(-1L); + } + } + + @Override + public List suggest(@RequestBody SuggestRequest suggestRequest) { + return indexService.suggest(suggestRequest, DisposalSchedule.class); + } } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/services/DisposalRuleService.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/services/DisposalRuleService.java index 42daad85df..6a66425597 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/services/DisposalRuleService.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/services/DisposalRuleService.java @@ -8,8 +8,13 @@ package org.roda.wui.api.v2.services; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import org.roda.core.data.common.RodaConstants; import org.roda.core.data.exceptions.AlreadyExistsException; @@ -18,6 +23,7 @@ import org.roda.core.data.exceptions.GenericException; import org.roda.core.data.exceptions.NotFoundException; import org.roda.core.data.exceptions.RequestNotValidException; +import org.roda.core.data.v2.disposal.rule.ChangeOrderRequest; import org.roda.core.data.v2.disposal.rule.ConditionType; import org.roda.core.data.v2.disposal.rule.DisposalRule; import org.roda.core.data.v2.disposal.rule.DisposalRules; @@ -26,7 +32,9 @@ import org.roda.core.data.v2.disposal.schedule.DisposalSchedules; import org.roda.core.data.v2.index.filter.Filter; import org.roda.core.data.v2.index.filter.SimpleFilterParameter; +import org.roda.core.data.v2.index.select.SelectedItems; import org.roda.core.data.v2.index.select.SelectedItemsFilter; +import org.roda.core.data.v2.index.select.SelectedItemsList; import org.roda.core.data.v2.ip.AIPState; import org.roda.core.data.v2.ip.IndexedAIP; import org.roda.core.data.v2.jobs.Job; @@ -81,6 +89,73 @@ public Job applyDisposalRules(User user, boolean overrideManualAssociations) ApplyDisposalRulesPlugin.class, user, pluginParameters, "Could not execute apply disposal rules to repository"); } + public void changeDisposalRuleOrder(RequestContext context, ChangeOrderRequest request) + throws AuthorizationDeniedException, RequestNotValidException, IOException, GenericException, NotFoundException { + ModelService modelService = context.getModelService(); + DisposalRules disposalRules = modelService.listDisposalRules(); + + SelectedItems disposalRuleSelectedItems = CommonServicesUtils.convertSelectedItems(request.getItems(), + DisposalRule.class); + if (disposalRuleSelectedItems instanceof SelectedItemsList itemsList) { + Set selectedIds = new HashSet<>(itemsList.getIds()); + + List selectedRules = new ArrayList<>(); + List remainingRules = new ArrayList<>(); + + for (DisposalRule rule : disposalRules.getObjects()) { + if (selectedIds.contains(rule.getId())) { + selectedRules.add(rule); + } else { + remainingRules.add(rule); + } + } + + // 3. Calculate the 0-based insertion index + int targetIndex = 0; + + switch (request.getPosition()) { + case TOP: + break; + case BOTTOM: + targetIndex = remainingRules.size(); + break; + case POSITION: + // Convert the 1-based UI position to a 0-based list index + targetIndex = request.getNewOrder() - 1; + + // Safety bounds checks + if (targetIndex < 0) { + targetIndex = 0; + } else if (targetIndex > remainingRules.size()) { + targetIndex = remainingRules.size(); + } + break; + } + + // 4. Insert the selected block into the remaining list + remainingRules.addAll(targetIndex, selectedRules); + + // 5. Reassign the "order" integer and collect the ones that actually changed + List rulesToUpdate = new ArrayList<>(); + + for (int i = 0; i < remainingRules.size(); i++) { + DisposalRule rule = remainingRules.get(i); + int newExpectedOrder = i; // 1-based ordering for the database + + // Only update if the order actually changed to save database/index calls + if (rule.getOrder() == null || rule.getOrder() != newExpectedOrder) { + rule.setOrder(newExpectedOrder); + rulesToUpdate.add(rule); + } + } + + // 6. Save the updated rules to your IndexService / Database + for (DisposalRule rule : rulesToUpdate) { + context.getModelService().updateDisposalRule(rule, context.getUser().getName()); + } + } + } + public void validateDisposalRule(DisposalRule disposalRule, ModelService model) throws GenericException, AuthorizationDeniedException, RequestNotValidException, IOException { DisposalSchedules disposalSchedules = model.listDisposalSchedules(); diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/services/DisposalScheduleService.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/services/DisposalScheduleService.java index 1c173633a6..28aba79dce 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/services/DisposalScheduleService.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/services/DisposalScheduleService.java @@ -50,7 +50,7 @@ public DisposalSchedules getDisposalSchedules(ModelService model) public DisposalSchedule retrieveDisposalSchedule(ModelService model, String id) throws AuthorizationDeniedException, RequestNotValidException, NotFoundException, GenericException { - return model.retrieveDisposalSchedule(id); + return model.retrieveDisposalSchedule(id); } public DisposalSchedule updateDisposalSchedule(DisposalSchedule schedule, RequestContext requestContext) diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/GenericDataPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/GenericDataPanel.java new file mode 100644 index 0000000000..f3a438ad8a --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/GenericDataPanel.java @@ -0,0 +1,12 @@ +package org.roda.wui.client; + +/** + * + * @author Miguel Guimarães + */ +public interface GenericDataPanel { + + boolean isValid(); + + T getValue(); +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DisposalHoldTabs.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DisposalHoldTabs.java new file mode 100644 index 0000000000..e31f814f9c --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DisposalHoldTabs.java @@ -0,0 +1,57 @@ +package org.roda.wui.client.browse.tabs; + +import org.roda.core.data.common.RodaConstants; +import org.roda.core.data.v2.disposal.hold.DisposalHold; +import org.roda.core.data.v2.index.filter.Filter; +import org.roda.core.data.v2.index.filter.SimpleFilterParameter; +import org.roda.core.data.v2.ip.AIPState; +import org.roda.core.data.v2.ip.IndexedAIP; +import org.roda.wui.client.common.actions.Actionable; +import org.roda.wui.client.common.actions.IndexedAIPDisposalHoldSearchWrapperActions; +import org.roda.wui.client.common.lists.utils.AsyncTableCellOptions; +import org.roda.wui.client.common.lists.utils.ConfigurableAsyncTableCell; +import org.roda.wui.client.common.lists.utils.ListBuilder; +import org.roda.wui.client.common.search.SearchWrapper; +import org.roda.wui.client.common.utils.PermissionClientUtils; +import org.roda.wui.client.disposal.hold.tabs.DisposalHoldDetailsPanel; + +import com.google.gwt.safehtml.shared.SafeHtmlUtils; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Widget; + +public class DisposalHoldTabs extends Tabs { + + public void init(DisposalHold hold, AsyncCallback actionCallback) { + + int activeIndex = this.getSelectedTabIndex(); + + this.clear(); + + // 1. Clear any existing tabs before building new ones! + createAndAddTab(SafeHtmlUtils.fromSafeConstant(messages.detailsTab()), new TabContentBuilder() { + @Override + public Widget buildTabWidget() { + return new DisposalHoldDetailsPanel(hold, actionCallback); + } + }); + + // Check if user has permissions to see the AIP + if (PermissionClientUtils.hasPermissions(RodaConstants.PERMISSION_METHOD_FIND_AIP)) { + + ListBuilder aipsListBuilder = new ListBuilder<>(() -> new ConfigurableAsyncTableCell<>(), + new AsyncTableCellOptions<>(IndexedAIP.class, "ShowDisposalSchedule_aips") + .withFilter(new Filter(new SimpleFilterParameter(RodaConstants.AIP_DISPOSAL_HOLDS_ID, hold.getId()), + new SimpleFilterParameter(RodaConstants.AIP_STATE, AIPState.ACTIVE.name()))) + .withActionable(new IndexedAIPDisposalHoldSearchWrapperActions(hold)).withSummary(messages.listOfAIPs()).bindOpener()); + + createAndAddTab(SafeHtmlUtils.fromSafeConstant(messages.disposalScheduleListAips()), new TabContentBuilder() { + @Override + public Widget buildTabWidget() { + return new SearchWrapper(false).createListAndSearchPanel(aipsListBuilder); + } + }); + } + + this.selectTabByIndex(activeIndex); + } +} \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DisposalPolicyTabs.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DisposalPolicyTabs.java new file mode 100644 index 0000000000..b0ac184198 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DisposalPolicyTabs.java @@ -0,0 +1,70 @@ +package org.roda.wui.client.browse.tabs; + +import org.roda.core.data.common.RodaConstants; +import org.roda.core.data.v2.disposal.hold.DisposalHold; +import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; +import org.roda.core.data.v2.disposal.rule.DisposalRule; +import org.roda.wui.client.common.actions.DisposalHoldSearchWrapperActions; +import org.roda.wui.client.common.actions.DisposalRuleAction; +import org.roda.wui.client.common.actions.DisposalRuleSearchWrapperActions; +import org.roda.wui.client.common.actions.DisposalScheduleSearchWrapperActions; +import org.roda.wui.client.common.lists.DisposalScheduleList; +import org.roda.wui.client.common.lists.DisposalHoldList; +import org.roda.wui.client.common.lists.DisposalRuleList; +import org.roda.wui.client.common.lists.utils.AsyncTableCellOptions; +import org.roda.wui.client.common.lists.utils.ListBuilder; +import org.roda.wui.client.common.search.SearchWrapper; +import org.roda.wui.client.common.utils.PermissionClientUtils; + +import com.google.gwt.safehtml.shared.SafeHtmlUtils; +import com.google.gwt.user.client.ui.Widget; +import com.google.gwt.storage.client.Storage; + +import java.util.List; + +public class DisposalPolicyTabs extends Tabs { + + public void init() { + + if (PermissionClientUtils.hasPermissions(RodaConstants.PERMISSION_METHOD_LIST_DISPOSAL_SCHEDULES)) { + ListBuilder scheduleListBuilder = new ListBuilder<>(() -> new DisposalScheduleList(), + new AsyncTableCellOptions<>(DisposalSchedule.class, "DisposalPolicyPage_disposalSchedules") + .withActionable(DisposalScheduleSearchWrapperActions.get()).withCsvDownloadButtonVisibility(false) + .bindOpener()); + + createAndAddTab(SafeHtmlUtils.fromSafeConstant(messages.disposalSchedulesTitle()), new TabContentBuilder() { + @Override + public Widget buildTabWidget() { + return new SearchWrapper(false).createListAndSearchPanel(scheduleListBuilder); + } + }); + } + + if (PermissionClientUtils.hasPermissions(RodaConstants.PERMISSION_METHOD_LIST_DISPOSAL_HOLDS)) { + ListBuilder holdListBuilder = new ListBuilder<>(() -> new DisposalHoldList(), + new AsyncTableCellOptions<>(DisposalHold.class, "DisposalPolicyPage_disposalHolds") + .withActionable(DisposalHoldSearchWrapperActions.get()).withCsvDownloadButtonVisibility(false).bindOpener()); + + createAndAddTab(SafeHtmlUtils.fromSafeConstant(messages.disposalHoldsTitle()), new TabContentBuilder() { + @Override + public Widget buildTabWidget() { + return new SearchWrapper(false).createListAndSearchPanel(holdListBuilder); + } + }); + } + + if (PermissionClientUtils.hasPermissions(RodaConstants.PERMISSION_METHOD_LIST_DISPOSAL_RULES)) { + ListBuilder ruleListBuilder = new ListBuilder<>(() -> new DisposalRuleList(), + new AsyncTableCellOptions<>(DisposalRule.class, "DisposalPolicyPage_disposalRules") + .withActionable(DisposalRuleSearchWrapperActions.get()).withActionBlacklist(List.of(DisposalRuleAction.SAVE, DisposalRuleAction.UPDATE)) + .withCsvDownloadButtonVisibility(false).bindOpener()); + + createAndAddTab(SafeHtmlUtils.fromSafeConstant(messages.disposalRulesTitle()), new TabContentBuilder() { + @Override + public Widget buildTabWidget() { + return new SearchWrapper(false).createListAndSearchPanel(ruleListBuilder); + } + }); + } + } +} \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DisposalRuleTabs.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DisposalRuleTabs.java new file mode 100644 index 0000000000..cea2581211 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DisposalRuleTabs.java @@ -0,0 +1,23 @@ +package org.roda.wui.client.browse.tabs; + +import org.roda.core.data.v2.disposal.rule.DisposalRule; +import org.roda.wui.client.common.actions.Actionable; +import org.roda.wui.client.disposal.rule.tabs.DisposalRuleDetailsPanel; + +import com.google.gwt.safehtml.shared.SafeHtmlUtils; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Widget; + +public class DisposalRuleTabs extends Tabs { + + public void init(DisposalRule rule, AsyncCallback actionCallback) { + this.clear(); + + createAndAddTab(SafeHtmlUtils.fromSafeConstant(messages.detailsTab()), new TabContentBuilder() { + @Override + public Widget buildTabWidget() { + return new DisposalRuleDetailsPanel(rule, actionCallback); + } + }); + } +} \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DisposalScheduleTabs.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DisposalScheduleTabs.java new file mode 100644 index 0000000000..9b92b8665c --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/DisposalScheduleTabs.java @@ -0,0 +1,57 @@ +package org.roda.wui.client.browse.tabs; + +import org.roda.core.data.common.RodaConstants; +import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; +import org.roda.core.data.v2.index.filter.Filter; +import org.roda.core.data.v2.index.filter.SimpleFilterParameter; +import org.roda.core.data.v2.ip.AIPState; +import org.roda.core.data.v2.ip.IndexedAIP; +import org.roda.wui.client.common.actions.Actionable; +import org.roda.wui.client.common.actions.IndexedAIPDisposalScheduleSearchWrapperActions; +import org.roda.wui.client.common.lists.utils.AsyncTableCellOptions; +import org.roda.wui.client.common.lists.utils.ConfigurableAsyncTableCell; +import org.roda.wui.client.common.lists.utils.ListBuilder; +import org.roda.wui.client.common.search.SearchWrapper; +import org.roda.wui.client.common.utils.PermissionClientUtils; +import org.roda.wui.client.disposal.schedule.tabs.DisposalScheduleDetailsPanel; + +import com.google.gwt.safehtml.shared.SafeHtmlUtils; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Widget; + +public class DisposalScheduleTabs extends Tabs { + + public void init(DisposalSchedule schedule, AsyncCallback actionCallback) { + + int activeIndex = this.getSelectedTabIndex(); + + this.clear(); + + // 1. Clear any existing tabs before building new ones! + createAndAddTab(SafeHtmlUtils.fromSafeConstant(messages.detailsTab()), new TabContentBuilder() { + @Override + public Widget buildTabWidget() { + return new DisposalScheduleDetailsPanel(schedule, actionCallback); + } + }); + + // Check if user has permissions to see the AIP + if (PermissionClientUtils.hasPermissions(RodaConstants.PERMISSION_METHOD_FIND_AIP)) { + + ListBuilder aipsListBuilder = new ListBuilder<>(() -> new ConfigurableAsyncTableCell<>(), + new AsyncTableCellOptions<>(IndexedAIP.class, "ShowDisposalSchedule_aips") + .withFilter(new Filter(new SimpleFilterParameter(RodaConstants.AIP_DISPOSAL_SCHEDULE_ID, schedule.getId()), + new SimpleFilterParameter(RodaConstants.AIP_STATE, AIPState.ACTIVE.name()))) + .withActionable(IndexedAIPDisposalScheduleSearchWrapperActions.get()).withSummary(messages.listOfAIPs()).bindOpener()); + + createAndAddTab(SafeHtmlUtils.fromSafeConstant(messages.disposalScheduleListAips()), new TabContentBuilder() { + @Override + public Widget buildTabWidget() { + return new SearchWrapper(false).createListAndSearchPanel(aipsListBuilder); + } + }); + } + + this.selectTabByIndex(activeIndex); + } +} \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/Tabs.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/Tabs.java index 7e3af0215c..3103c39022 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/Tabs.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/browse/tabs/Tabs.java @@ -22,6 +22,7 @@ import com.google.gwt.user.client.ui.FocusPanel; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.Widget; +import com.google.gwt.storage.client.Storage; import config.i18n.client.ClientMessages; @@ -32,15 +33,14 @@ public class Tabs extends Composite { protected static final ClientMessages messages = GWT.create(ClientMessages.class); private static Tabs.MyUiBinder uiBinder = GWT.create(Tabs.MyUiBinder.class); - + private final Map tabs; @UiField FlowPanel tabButtons; @UiField SimplePanel tabContentWrapper; - private Widget selectedTab; - private final Map tabs; private TabContentBuilder defaultContent; + private String storageKey = null; public Tabs() { initWidget(uiBinder.createAndBindUi(this)); @@ -55,6 +55,24 @@ public Widget buildTabWidget() { selectTab(null); } + public void setStorageKey(String storageKey) { + this.storageKey = storageKey; + + // Immediately attempt to restore the saved tab index + Storage sessionStorage = Storage.getSessionStorageIfSupported(); + if (sessionStorage != null) { + String savedIndexStr = sessionStorage.getItem(storageKey); + if (savedIndexStr != null) { + try { + int activeIndex = Integer.parseInt(savedIndexStr); + selectTabByIndex(activeIndex); + } catch (NumberFormatException e) { + // If parsing fails, it safely ignores and keeps the default tab + } + } + } + } + public void setDefaultContent(TabContentBuilder tabContentBuilder) { defaultContent = tabContentBuilder; } @@ -110,6 +128,14 @@ protected void selectTab(Widget tabButtonContainer) { selectedTab = tabButtonContainer; tab.getKey().addStyleName("tabSelected"); tabContentWrapper.setWidget(tab.getValue().buildTabWidget()); + + if (storageKey != null) { + Storage sessionStorage = Storage.getSessionStorageIfSupported(); + if (sessionStorage != null) { + sessionStorage.setItem(storageKey, String.valueOf(getSelectedTabIndex())); + } + } + } else { tab.getKey().removeStyleName("tabSelected"); } @@ -141,6 +167,36 @@ public void selectTabByIndex(int index) { } } + // --- NEW: Add the onLoad method --- + @Override + protected void onLoad() { + super.onLoad(); + + // 1. Dynamically generate a unique key based on the child class (e.g., + // "org.roda...DisposalPolicyTabs_ActiveIndex") + if (this.storageKey == null) { + this.storageKey = this.getClass().getName() + "_ActiveIndex"; + } + + // 2. Restore the saved index now that the tabs are built and the widget is + // attached + Storage sessionStorage = Storage.getSessionStorageIfSupported(); + if (sessionStorage != null) { + String savedIndexStr = sessionStorage.getItem(storageKey); + if (savedIndexStr != null) { + try { + int activeIndex = Integer.parseInt(savedIndexStr); + // Only select if the saved index is valid for the current number of tabs + if (activeIndex >= 0 && activeIndex < tabButtons.getWidgetCount()) { + selectTabByIndex(activeIndex); + } + } catch (NumberFormatException e) { + // Defaults gracefully if parsing fails + } + } + } + } + public interface TabContentBuilder { public Widget buildTabWidget(); } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/BrowseObjectActionsToolbar.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/BrowseObjectActionsToolbar.java index 94cf92c6cf..9f59bb8e96 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/BrowseObjectActionsToolbar.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/BrowseObjectActionsToolbar.java @@ -13,10 +13,12 @@ import org.roda.core.data.v2.index.IsIndexed; import org.roda.core.data.v2.ip.AIPState; import org.roda.core.data.v2.ip.Permissions; +import org.roda.wui.client.GenericDataPanel; import org.roda.wui.client.common.actions.Actionable; import org.roda.wui.client.common.labels.Tag; import com.google.gwt.user.client.rpc.AsyncCallback; +import org.roda.wui.client.disposal.rule.DisposalRuleDataPanel; /** * @@ -28,6 +30,7 @@ public abstract class BrowseObjectActionsToolbar extends Ac protected Permissions actionPermissions; protected AsyncCallback actionCallback; protected AIPState state; + private GenericDataPanel dataPanel; public void setObjectAndBuild(T object, AIPState state, Permissions permissions, AsyncCallback actionCallback) { @@ -50,6 +53,14 @@ public void setObjectAndBuild(T object, Permissions permissions, buildActions(); } + public void setDataPanel(GenericDataPanel dataPanel) { + this.dataPanel = dataPanel; + } + + public GenericDataPanel getDataPanel() { + return this.dataPanel; + } + protected abstract void buildIcon(); protected abstract void buildTags(); diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/CreateOrUpdateDisposalHoldActionsToolbar.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/CreateOrUpdateDisposalHoldActionsToolbar.java new file mode 100644 index 0000000000..9857fd5a1b --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/CreateOrUpdateDisposalHoldActionsToolbar.java @@ -0,0 +1,77 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE file at the root of the source + * tree and available online at + * + * https://github.com/keeps/roda + */ +package org.roda.wui.client.common; + +import java.util.List; + +import org.roda.core.data.v2.disposal.hold.DisposalHold; +import org.roda.wui.client.common.actions.AbstractActionable; +import org.roda.wui.client.common.actions.ActionableInterceptor; +import org.roda.wui.client.common.actions.DisposalHoldAction; +import org.roda.wui.client.common.actions.DisposalHoldToolbarActions; +import org.roda.wui.client.common.actions.model.ActionableObject; +import org.roda.wui.client.common.actions.widgets.ActionableWidgetBuilder; + +import com.google.gwt.user.client.rpc.AsyncCallback; + +/** + * + * @author Miguel Guimarães + */ +public class CreateOrUpdateDisposalHoldActionsToolbar extends BrowseObjectActionsToolbar { + private boolean isCreate = true; + + public void setIsCreate(boolean isCreate) { + this.isCreate = isCreate; + } + + public void buildIcon() { + // do nothing + } + + public void buildTags() { + // do nothing + } + + public void buildActions() { + this.actions.clear(); + + // Determine the correct action based on context + final DisposalHoldAction targetAction = isCreate ? DisposalHoldAction.SAVE : DisposalHoldAction.UPDATE; + + // Create an interceptor that checks validity before calling the backend + AbstractActionable interceptor = new ActionableInterceptor( + DisposalHoldToolbarActions.get()) { + + @Override + public void act(Action action, DisposalHold hold, AsyncCallback callback) { + + if (DisposalHoldAction.SAVE.equals(action) || DisposalHoldAction.UPDATE.equals(action)) { + // FRONT-END VALIDATION + if (getDataPanel() != null && !getDataPanel().isValid()) { + callback.onSuccess(ActionImpact.NONE); + return; + } + + // Pass the freshly validated data down the chain + DisposalHold value = getDataPanel().getValue(); + if (!isCreate) { + value.setId(hold.getId()); + } + super.act(action, value, callback); + } else { + super.act(action, hold, callback); + } + } + }; + + // Bind the SAVE button to our new interceptor + this.actions.add(new ActionableWidgetBuilder(interceptor).withActionCallback(actionCallback) + .buildUngroupedActions(new ActionableObject<>(object), List.of(targetAction))); + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/CreateOrUpdateDisposalRuleActionsToolbar.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/CreateOrUpdateDisposalRuleActionsToolbar.java new file mode 100644 index 0000000000..8115bddf5a --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/CreateOrUpdateDisposalRuleActionsToolbar.java @@ -0,0 +1,78 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE file at the root of the source + * tree and available online at + * + * https://github.com/keeps/roda + */ +package org.roda.wui.client.common; + +import java.util.List; + +import org.roda.core.data.v2.disposal.rule.DisposalRule; +import org.roda.wui.client.common.actions.AbstractActionable; +import org.roda.wui.client.common.actions.ActionableInterceptor; +import org.roda.wui.client.common.actions.DisposalRuleAction; +import org.roda.wui.client.common.actions.DisposalRuleSearchWrapperActions; +import org.roda.wui.client.common.actions.model.ActionableObject; +import org.roda.wui.client.common.actions.widgets.ActionableWidgetBuilder; + +import com.google.gwt.user.client.rpc.AsyncCallback; + +/** + * + * @author Miguel Guimarães + */ +public class CreateOrUpdateDisposalRuleActionsToolbar extends BrowseObjectActionsToolbar { + private boolean isCreate = true; + + public void setIsCreate(boolean isCreate) { + this.isCreate = isCreate; + } + + public void buildIcon() { + // do nothing + } + + public void buildTags() { + // do nothing + } + + public void buildActions() { + this.actions.clear(); + + // Determine the correct action based on context + final DisposalRuleAction targetAction = isCreate ? DisposalRuleAction.SAVE : DisposalRuleAction.UPDATE; + + // Create an interceptor that checks validity before calling the backend + AbstractActionable interceptor = new ActionableInterceptor( + DisposalRuleSearchWrapperActions.get()) { + + @Override + public void act(Action action, DisposalRule rule, AsyncCallback callback) { + + if (DisposalRuleAction.SAVE.equals(action) || DisposalRuleAction.UPDATE.equals(action)) { + // FRONT-END VALIDATION + if (getDataPanel() != null && !getDataPanel().isValid()) { + callback.onSuccess(ActionImpact.NONE); + return; + } + + // Pass the freshly validated data down the chain + DisposalRule value = getDataPanel().getValue(); + value.setOrder(rule.getOrder()); + if (!isCreate) { + value.setId(rule.getId()); + } + super.act(action, value, callback); + } else { + super.act(action, rule, callback); + } + } + }; + + // Bind the SAVE button to our new interceptor + this.actions.add(new ActionableWidgetBuilder(interceptor).withActionCallback(actionCallback) + .buildUngroupedActions(new ActionableObject<>(object), List.of(targetAction))); + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/CreateOrUpdateDisposalScheduleActionsToolbar.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/CreateOrUpdateDisposalScheduleActionsToolbar.java new file mode 100644 index 0000000000..ea0d7036ea --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/CreateOrUpdateDisposalScheduleActionsToolbar.java @@ -0,0 +1,78 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE file at the root of the source + * tree and available online at + * + * https://github.com/keeps/roda + */ +package org.roda.wui.client.common; + +import java.util.List; + +import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; +import org.roda.wui.client.common.actions.AbstractActionable; +import org.roda.wui.client.common.actions.ActionableInterceptor; +import org.roda.wui.client.common.actions.DisposalScheduleAction; +import org.roda.wui.client.common.actions.DisposalScheduleToolbarActions; +import org.roda.wui.client.common.actions.model.ActionableObject; +import org.roda.wui.client.common.actions.widgets.ActionableWidgetBuilder; + +import com.google.gwt.user.client.rpc.AsyncCallback; + +/** + * + * @author Miguel Guimarães + */ +public class CreateOrUpdateDisposalScheduleActionsToolbar extends BrowseObjectActionsToolbar { + private boolean isCreate = true; + + public void setIsCreate(boolean isCreate) { + this.isCreate = isCreate; + } + + public void buildIcon() { + // do nothing + } + + public void buildTags() { + // do nothing + } + + public void buildActions() { + this.actions.clear(); + + // Determine the correct action based on context + final DisposalScheduleAction targetAction = isCreate ? DisposalScheduleAction.SAVE : DisposalScheduleAction.UPDATE; + + // Create an interceptor that checks validity before calling the backend + AbstractActionable interceptor = new ActionableInterceptor( + DisposalScheduleToolbarActions.get()) { + + @Override + public void act(Action action, DisposalSchedule schedule, + AsyncCallback callback) { + + if (DisposalScheduleAction.SAVE.equals(action) || DisposalScheduleAction.UPDATE.equals(action)) { + // FRONT-END VALIDATION + if (getDataPanel() != null && !getDataPanel().isValid()) { + callback.onSuccess(ActionImpact.NONE); + return; + } + + // Pass the freshly validated data down the chain + DisposalSchedule value = getDataPanel().getValue(); + if (!isCreate) { + value.setId(schedule.getId()); + } + super.act(action, value, callback); + } else { + super.act(action, schedule, callback); + } + } + }; + + // Bind the SAVE button to our new interceptor + this.actions.add(new ActionableWidgetBuilder(interceptor).withActionCallback(actionCallback) + .buildUngroupedActions(new ActionableObject<>(object), List.of(targetAction))); + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/DisposalHoldActionsToolbar.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/DisposalHoldActionsToolbar.java new file mode 100644 index 0000000000..c09f6b0eb9 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/DisposalHoldActionsToolbar.java @@ -0,0 +1,38 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE file at the root of the source + * tree and available online at + * + * https://github.com/keeps/roda + */ +package org.roda.wui.client.common; + +import java.util.List; + +import org.roda.core.data.v2.disposal.hold.DisposalHold; +import org.roda.wui.client.common.actions.DisposalHoldAction; +import org.roda.wui.client.common.actions.DisposalHoldToolbarActions; +import org.roda.wui.client.common.actions.model.ActionableObject; +import org.roda.wui.client.common.actions.widgets.ActionableWidgetBuilder; + +/** + * + * @author Miguel Guimarães + */ +public class DisposalHoldActionsToolbar extends BrowseObjectActionsToolbar { + public void buildIcon() { + // do nothing + } + + public void buildTags() { + // do nothing + } + + public void buildActions() { + this.actions.clear(); + + DisposalHoldToolbarActions toolbarActions = DisposalHoldToolbarActions.get(); + this.actions.add(new ActionableWidgetBuilder(toolbarActions).withActionCallback(actionCallback) + .buildGroupedListWithObjects(new ActionableObject<>(object), List.of(DisposalHoldAction.LIFT), List.of())); + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/DisposalRuleActionsToolbar.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/DisposalRuleActionsToolbar.java new file mode 100644 index 0000000000..1d254db8c5 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/DisposalRuleActionsToolbar.java @@ -0,0 +1,38 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE file at the root of the source + * tree and available online at + * + * https://github.com/keeps/roda + */ +package org.roda.wui.client.common; + +import org.roda.core.data.v2.disposal.rule.DisposalRule; +import org.roda.wui.client.common.actions.DisposalRuleAction; +import org.roda.wui.client.common.actions.DisposalRuleToolbarActions; +import org.roda.wui.client.common.actions.model.ActionableObject; +import org.roda.wui.client.common.actions.widgets.ActionableWidgetBuilder; + +import java.util.List; + +/** + * + * @author Miguel Guimarães + */ +public class DisposalRuleActionsToolbar extends BrowseObjectActionsToolbar { + public void buildIcon() { + // do nothing + } + + public void buildTags() { + // do nothing + } + + public void buildActions() { + this.actions.clear(); + + this.actions.add( + new ActionableWidgetBuilder(DisposalRuleToolbarActions.get()).withActionCallback(actionCallback) + .buildGroupedListWithObjects(new ActionableObject<>(object), List.of(DisposalRuleAction.REMOVE), List.of())); + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/DisposalScheduleActionsToolbar.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/DisposalScheduleActionsToolbar.java new file mode 100644 index 0000000000..cdc279d350 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/DisposalScheduleActionsToolbar.java @@ -0,0 +1,44 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE file at the root of the source + * tree and available online at + * + * https://github.com/keeps/roda + */ +package org.roda.wui.client.common; + +import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; +import org.roda.wui.client.common.actions.DisposalScheduleAction; +import org.roda.wui.client.common.actions.DisposalScheduleToolbarActions; +import org.roda.wui.client.common.actions.model.ActionableObject; +import org.roda.wui.client.common.actions.widgets.ActionableWidgetBuilder; +import org.roda.wui.client.common.labels.Tag; + +import java.util.List; + +/** + * + * @author Miguel Guimarães + */ +public class DisposalScheduleActionsToolbar extends BrowseObjectActionsToolbar { + public void buildIcon() { + // do nothing + } + + public void buildTags() { + this.tags.clear(); + + if (object.isUsedInDisposalRule()) { + Tag tag = Tag.fromText(messages.disposalScheduleUsedInRule(), Tag.TagStyle.WARNING_LIGHT); + tags.add(tag); + } + } + + public void buildActions() { + this.actions.clear(); + + DisposalScheduleToolbarActions toolbarActions = DisposalScheduleToolbarActions.get(); + this.actions.add(new ActionableWidgetBuilder(toolbarActions).withActionCallback(actionCallback) + .buildGroupedListWithObjects(new ActionableObject<>(object), List.of(DisposalScheduleAction.REMOVE), List.of())); + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/NavigationToolbar.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/NavigationToolbar.java index 178c49ae0a..9f9934e371 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/NavigationToolbar.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/NavigationToolbar.java @@ -62,15 +62,12 @@ public class NavigationToolbar extends Composite implements // buttons on the right side @UiField - AccessibleFocusPanel infoSidebarButton; - @UiField FlowPanel toolbarPanel; private boolean requireControlKeyModifier = true; private boolean requireShiftKeyModifier = false; private boolean requireAltKeyModifier = false; private boolean skipButtonSetup = false; private T currentObject = null; - private Permissions permissions = null; private Map handlers = new EnumMap<>(Actionable.ActionImpact.class); private AsyncCallback handler = new NoAsyncCallback() { @Override @@ -103,7 +100,6 @@ public NavigationToolbar withProcessor(ProcessRelativeItem processor) { } public NavigationToolbar withPermissions(Permissions permissions) { - this.permissions = permissions; return this; } @@ -112,11 +108,6 @@ public NavigationToolbar withAlternativeStyle(boolean useAltStyle) { return this; } - public AccessibleFocusPanel getInfoSidebarButton() { - infoSidebarButton.setVisible(true); - return infoSidebarButton; - } - public NavigationToolbar withModifierKeys(boolean requireControlKeyModifier, boolean requireShiftKeyModifier, boolean requireAltKeyModifier) { this.requireControlKeyModifier = requireControlKeyModifier; @@ -129,7 +120,6 @@ private void hideButtons() { previousButton.setVisible(false); nextButton.setVisible(false); pageInformation.setVisible(false); - infoSidebarButton.setVisible(false); } public void build() { @@ -145,6 +135,7 @@ public void build() { int index = ListSelectionUtils.getIndex(currentObject.getClass().getName()) + 1; long total = ListSelectionUtils.getTotal(currentObject.getClass().getName()); + pageInformation.setHTML("" + NumberFormat.getDecimalFormat().format(index) + " / " + NumberFormat.getDecimalFormat().format(total) + ""); diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/NavigationToolbar.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/NavigationToolbar.ui.xml index 59683fd7cb..0efa8e1cf3 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/NavigationToolbar.ui.xml +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/NavigationToolbar.ui.xml @@ -1,41 +1,29 @@ + xmlns:g="urn:import:com.google.gwt.user.client.ui" + xmlns:wcag="urn:import:org.roda.wui.common.client.widgets.wcag"> - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/ActionableInterceptor.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/ActionableInterceptor.java new file mode 100644 index 0000000000..0104e1d075 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/ActionableInterceptor.java @@ -0,0 +1,78 @@ +package org.roda.wui.client.common.actions; + +import com.google.gwt.user.client.rpc.AsyncCallback; +import org.roda.core.data.v2.index.IsIndexed; +import org.roda.core.data.v2.index.select.SelectedItems; +import org.roda.wui.client.common.actions.model.ActionableBundle; + +/** + * @author Miguel Guimarães + */ + +public class ActionableInterceptor extends AbstractActionable { + private final Actionable delegate; + + public ActionableInterceptor(Actionable delegate) { + this.delegate = delegate; + } + + @Override + public Action[] getActions() { + return delegate.getActions(); + } + + @Override + public CanActResult userCanAct(Action action) { + return delegate.userCanAct(action); + } + + @Override + public CanActResult contextCanAct(Action action) { + return delegate.contextCanAct(action); + } + + @Override + public CanActResult userCanAct(Action action, T object) { + return delegate.userCanAct(action, object); + } + + @Override + public CanActResult contextCanAct(Action action, T object) { + return delegate.contextCanAct(action, object); + } + + @Override + public CanActResult userCanAct(Action action, SelectedItems objects) { + return delegate.userCanAct(action, objects); + } + + @Override + public CanActResult contextCanAct(Action action, SelectedItems objects) { + return delegate.contextCanAct(action, objects); + } + + @Override + public ActionableBundle createActionsBundle() { + return delegate.createActionsBundle(); + } + + @Override + public Action actionForName(String name) { + return delegate.actionForName(name); + } + + @Override + public void act(Action action, AsyncCallback callback) { + delegate.act(action, callback); + } + + @Override + public void act(Action action, T object, AsyncCallback callback) { + delegate.act(action, object, callback); + } + + @Override + public void act(Action action, SelectedItems objects, AsyncCallback callback) { + delegate.act(action, objects, callback); + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalHoldAction.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalHoldAction.java new file mode 100644 index 0000000000..68c1f63e1d --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalHoldAction.java @@ -0,0 +1,32 @@ +package org.roda.wui.client.common.actions; + +import java.util.Arrays; +import java.util.List; + +import org.roda.core.data.common.RodaConstants; +import org.roda.core.data.v2.disposal.hold.DisposalHold; + +/** + * @author Miguel Guimarães + */ + +public enum DisposalHoldAction implements Actionable.Action { + NEW(RodaConstants.PERMISSION_METHOD_CREATE_DISPOSAL_HOLD), + SAVE(RodaConstants.PERMISSION_METHOD_CREATE_DISPOSAL_HOLD), + EDIT(RodaConstants.PERMISSION_METHOD_UPDATE_DISPOSAL_HOLD), + UPDATE(RodaConstants.PERMISSION_METHOD_UPDATE_DISPOSAL_HOLD), + LIFT(RodaConstants.PERMISSION_METHOD_ASSOCIATE_DISPOSAL_HOLD), + DISASSOCIATE(RodaConstants.PERMISSION_METHOD_ASSOCIATE_DISPOSAL_HOLD); + + + private final List methods; + + DisposalHoldAction(String... methods) { + this.methods = Arrays.asList(methods); + } + + @Override + public List getMethods() { + return this.methods; + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalHoldActions.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalHoldActions.java index c291b126e5..957be72b58 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalHoldActions.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalHoldActions.java @@ -1,198 +1,72 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE file at the root of the source - * tree and available online at - * - * https://github.com/keeps/roda - */ package org.roda.wui.client.common.actions; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import org.roda.core.data.common.RodaConstants; -import org.roda.core.data.utils.SelectedItemsUtils; -import org.roda.core.data.v2.disposal.hold.DisposalHold; -import org.roda.core.data.v2.index.select.SelectedItems; -import org.roda.core.data.v2.ip.IndexedAIP; -import org.roda.core.data.v2.ip.disposalhold.DisassociateDisposalHoldRequest; -import org.roda.wui.client.common.actions.callbacks.ActionNoAsyncCallback; -import org.roda.wui.client.common.actions.model.ActionableBundle; -import org.roda.wui.client.common.actions.model.ActionableGroup; -import org.roda.wui.client.common.actions.model.ActionableObject; -import org.roda.wui.client.common.dialogs.Dialogs; -import org.roda.wui.client.common.lists.utils.ClientSelectedItemsUtils; -import org.roda.wui.client.ingest.process.ShowJob; -import org.roda.wui.client.process.InternalProcess; -import org.roda.wui.client.services.Services; -import org.roda.wui.common.client.tools.HistoryUtils; -import org.roda.wui.common.client.widgets.Toast; - import com.google.gwt.core.client.GWT; import com.google.gwt.regexp.shared.RegExp; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.rpc.AsyncCallback; - import config.i18n.client.ClientMessages; +import org.roda.core.data.common.RodaConstants; +import org.roda.core.data.v2.disposal.hold.DisposalHold; +import org.roda.wui.client.common.actions.callbacks.ActionAsyncCallback; +import org.roda.wui.client.common.dialogs.Dialogs; +import org.roda.wui.client.common.utils.AsyncCallbackUtils; +import org.roda.wui.client.services.Services; +import org.roda.wui.common.client.widgets.Toast; /** - * @author Tiago Fraga + * @author Miguel Guimarães */ -public class DisposalHoldActions extends AbstractActionable { - private static final DisposalHoldActions INSTANCE = new DisposalHoldActions(); - private static final ClientMessages messages = GWT.create(ClientMessages.class); - private static final Set POSSIBLE_ACTIONS_ON_DISPOSAL_HOLD = new HashSet<>( - Arrays.asList(DisposalHoldAction.DISASSOCIATE)); - private DisposalHold disposalHold; - - public DisposalHoldActions() { - } - - public DisposalHoldActions(DisposalHold disposalHold) { - this.disposalHold = disposalHold; - } - - public static DisposalHoldActions get() { - return INSTANCE; - } - - @Override - public Action[] getActions() { - return DisposalHoldActions.DisposalHoldAction.values(); - } - @Override - public Action actionForName(String name) { - return DisposalHoldActions.DisposalHoldAction.valueOf(name); - } - - @Override - public CanActResult userCanAct(Action action, ActionableObject object) { - return new CanActResult(hasPermissions(action), CanActResult.Reason.USER, messages.reasonUserLacksPermission()); - } - - @Override - public CanActResult contextCanAct(Action action, ActionableObject object) { - if (object.getObject() != null || object.getObjects() != null) { - return new CanActResult(POSSIBLE_ACTIONS_ON_DISPOSAL_HOLD.contains(action), CanActResult.Reason.CONTEXT, - messages.reasonInvalidContext()); - } else { - return new CanActResult(false, CanActResult.Reason.CONTEXT, messages.reasonNoObjectSelected()); - } - } - - @Override - public void act(Action action, IndexedAIP aip, AsyncCallback callback) { - if (DisposalHoldAction.DISASSOCIATE.equals(action)) { - disassociate(objectToSelectedItems(aip, IndexedAIP.class), callback); - } else { - unsupportedAction(action, callback); - } - } +public class DisposalHoldActions { + private static final ClientMessages messages = GWT.create(ClientMessages.class); - @Override - public void act(Action action, SelectedItems aips, AsyncCallback callback) { - if (DisposalHoldAction.DISASSOCIATE.equals(action)) { - disassociate(aips, callback); - } else { - unsupportedAction(action, callback); - } + private DisposalHoldActions() { + // private constructor to prevent instantiation } - private void disassociate(final SelectedItems aips, final AsyncCallback callback) { - ClientSelectedItemsUtils.size(IndexedAIP.class, aips, new ActionNoAsyncCallback(callback) { - @Override - public void onSuccess(final Long size) { - Dialogs.showConfirmDialog(messages.disassociateDisposalHoldDialogTitle(), - messages.disassociateDisposalHoldDialogMessage(size.intValue()), messages.dialogNo(), messages.dialogYes(), - new ActionNoAsyncCallback(callback) { - @Override - public void onSuccess(Boolean result) { - if (result) { - Dialogs.showPromptDialog(messages.outcomeDetailTitle(), null, null, messages.outcomeDetailPlaceholder(), - RegExp.compile(".*"), messages.cancelButton(), messages.confirmButton(), false, true, - new ActionNoAsyncCallback(callback) { - @Override - public void onFailure(Throwable caught) { - // do nothing - } - - @Override - public void onSuccess(String details) { - DisassociateDisposalHoldRequest request = new DisassociateDisposalHoldRequest(); - request.setClear(false); - request.setDetails(details); - request.setSelectedItems(SelectedItemsUtils.convertToRESTRequest(aips)); - Services services = new Services("Lift disposal hold", "job"); - services.disposalHoldResource(s -> s.disassociateDisposalHold(request, disposalHold.getId())) - .whenComplete((job, throwable) -> { - if (throwable != null) { - callback.onFailure(throwable); - HistoryUtils.newHistory(InternalProcess.RESOLVER); - } else { - Dialogs.showJobRedirectDialog(messages.jobCreatedMessage(), new AsyncCallback() { - - @Override - public void onFailure(Throwable caught) { - Toast.showInfo(messages.runningInBackgroundTitle(), - messages.runningInBackgroundDescription()); - - Timer timer = new Timer() { - @Override - public void run() { - doActionCallbackUpdated(); - } - }; - - timer.schedule(RodaConstants.ACTION_TIMEOUT); - } - - @Override - public void onSuccess(final Void nothing) { - doActionCallbackNone(); - HistoryUtils.newHistory(ShowJob.RESOLVER, job.getId()); - } - }); + public static void lift(DisposalHold hold, AsyncCallback callback) { + Dialogs.showConfirmDialog(messages.liftDisposalHoldDialogTitle(), messages.liftDisposalHoldDialogMessage(1), + messages.cancelButton(), messages.confirmButton(), new ActionAsyncCallback(callback) { + @Override + public void onFailure(Throwable throwable) { + callback.onSuccess(Actionable.ActionImpact.NONE); + } + + @Override + public void onSuccess(Boolean confirm) { + if (confirm) { + Dialogs.showPromptDialog(messages.outcomeDetailTitle(), null, null, messages.outcomeDetailPlaceholder(), + RegExp.compile(".*"), messages.cancelButton(), messages.confirmButton(), false, true, + new AsyncCallback() { + @Override + public void onFailure(Throwable throwable) { + // do nothing + } + + @Override + public void onSuccess(String details) { + Services services = new Services("Lift disposal hold", "job"); + + services.disposalHoldResource(s -> s.liftDisposalHold(hold.getUUID(), details)) + .whenComplete((job, throwable) -> { + if (throwable != null) { + AsyncCallbackUtils.defaultFailureTreatment(throwable); + } else { + Toast.showInfo(messages.runningInBackgroundTitle(), messages.updateDisposalHoldMessage()); + Timer timer = new Timer() { + @Override + public void run() { + callback.onSuccess(Actionable.ActionImpact.UPDATED); } - }); - } - }); - } else { - doActionCallbackNone(); - } - } - }); - } - }); - } - - @Override - public ActionableBundle createActionsBundle() { - ActionableBundle disposalHoldActionableBundle = new ActionableBundle<>(); - - ActionableGroup managementGroup = new ActionableGroup<>(messages.sidebarActionsTitle()); - managementGroup.addButton(messages.disassociateDisposalHoldButton(), DisposalHoldAction.DISASSOCIATE, - ActionImpact.UPDATED, "btn-lift-hold"); - - disposalHoldActionableBundle.addGroup(managementGroup); - return disposalHoldActionableBundle; + }; + + timer.schedule(RodaConstants.ACTION_TIMEOUT); + } + }); + } + }); + } + } + }); } - - public enum DisposalHoldAction implements Action { - DISASSOCIATE(RodaConstants.PERMISSION_METHOD_ASSOCIATE_DISPOSAL_HOLD); - - private final List methods; - - DisposalHoldAction(String... methods) { - this.methods = Arrays.asList(methods); - } - - @Override - public List getMethods() { - return this.methods; - } - } - } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalHoldSearchWrapperActions.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalHoldSearchWrapperActions.java new file mode 100644 index 0000000000..c85adba028 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalHoldSearchWrapperActions.java @@ -0,0 +1,133 @@ +package org.roda.wui.client.common.actions; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.roda.core.data.v2.disposal.hold.DisposalHold; +import org.roda.core.data.v2.disposal.hold.DisposalHoldState; +import org.roda.core.data.v2.index.select.SelectedItems; +import org.roda.wui.client.common.actions.model.ActionableBundle; +import org.roda.wui.client.common.actions.model.ActionableGroup; +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.rpc.AsyncCallback; + +import config.i18n.client.ClientMessages; +import org.roda.wui.client.disposal.hold.CreateDisposalHold; +import org.roda.wui.client.disposal.hold.EditDisposalHold; +import org.roda.wui.common.client.tools.HistoryUtils; + +/** + * @author Miguel Guimarães + */ + +public class DisposalHoldSearchWrapperActions extends AbstractActionable { + private static final DisposalHoldSearchWrapperActions INSTANCE = new DisposalHoldSearchWrapperActions(); + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private static final Set POSSIBLE_ACTIONS_WITHOUT_HOLD = new HashSet<>( + Arrays.asList(DisposalHoldAction.NEW)); + + private static final Set POSSIBLE_ACTIONS_ON_SINGLE_DISPOSAL_HOLD = new HashSet<>( + Arrays.asList(DisposalHoldAction.EDIT, DisposalHoldAction.LIFT)); + + private DisposalHoldSearchWrapperActions() { + } + + public static DisposalHoldSearchWrapperActions get() { + return INSTANCE; + } + + @Override + public Action[] getActions() { + return DisposalHoldAction.values(); + } + + @Override + public CanActResult userCanAct(Action action) { + return new CanActResult(hasPermissions(action), CanActResult.Reason.USER, messages.reasonUserLacksPermission()); + } + + @Override + public CanActResult contextCanAct(Action action) { + return new CanActResult(POSSIBLE_ACTIONS_WITHOUT_HOLD.contains(action), CanActResult.Reason.CONTEXT, + messages.reasonNoObjectSelected()); + } + + @Override + public CanActResult userCanAct(Action action, DisposalHold object) { + return new CanActResult(hasPermissions(action), CanActResult.Reason.USER, messages.reasonUserLacksPermission()); + } + + @Override + public CanActResult contextCanAct(Action action, DisposalHold object) { + if (DisposalHoldState.LIFTED.equals(object.getState())) { + return new CanActResult(false, CanActResult.Reason.CONTEXT, messages.reasonHoldAlreadyLifted()); + } + + return new CanActResult(POSSIBLE_ACTIONS_ON_SINGLE_DISPOSAL_HOLD.contains(action), CanActResult.Reason.CONTEXT, + messages.reasonCantActOnSingleObject()); + } + + @Override + public CanActResult userCanAct(Action action, SelectedItems objects) { + return new CanActResult(hasPermissions(action), CanActResult.Reason.USER, messages.reasonUserLacksPermission()); + } + + @Override + public CanActResult contextCanAct(Action action, SelectedItems objects) { + return new CanActResult(false, CanActResult.Reason.CONTEXT, messages.reasonCantActOnMultipleObjects()); + } + + @Override + public void act(Action action, DisposalHold hold, AsyncCallback callback) { + if (DisposalHoldAction.EDIT.equals(action)) { + edit(hold, callback); + } else if (DisposalHoldAction.LIFT.equals(action)) { + DisposalHoldActions.lift(hold, callback); + } else { + unsupportedAction(action, callback); + } + } + + @Override + public void act(Action action, AsyncCallback callback) { + if (DisposalHoldAction.NEW.equals(action)) { + newDisposalHold(callback); + } else { + unsupportedAction(action, callback); + } + } + + @Override + public ActionableBundle createActionsBundle() { + ActionableBundle disposalHoldActionableBundle = new ActionableBundle<>(); + + ActionableGroup managementGroup = new ActionableGroup<>(messages.sidebarActionsTitle()); + managementGroup.addButton(messages.newDisposalHoldTitle(), DisposalHoldAction.NEW, ActionImpact.UPDATED, + "btn-plus-circle"); + + managementGroup.addButton(messages.editButton(), DisposalHoldAction.EDIT, ActionImpact.UPDATED, "btn-edit"); + + managementGroup.addButton(messages.liftButton(), DisposalHoldAction.LIFT, ActionImpact.UPDATED, "btn-lift-hold"); + + disposalHoldActionableBundle.addGroup(managementGroup); + return disposalHoldActionableBundle; + } + + @Override + public Action actionForName(String name) { + return null; + } + + private void newDisposalHold(AsyncCallback callback) { + HistoryUtils.newHistory(CreateDisposalHold.RESOLVER.getHistoryPath()); + callback.onSuccess(ActionImpact.UPDATED); + } + + private void edit(DisposalHold hold, AsyncCallback callback) { + HistoryUtils.newHistory(EditDisposalHold.RESOLVER, hold.getId()); + callback.onSuccess(ActionImpact.UPDATED); + } + + +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalHoldToolbarActions.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalHoldToolbarActions.java new file mode 100644 index 0000000000..d62e19cb73 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalHoldToolbarActions.java @@ -0,0 +1,152 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE file at the root of the source + * tree and available online at + * + * https://github.com/keeps/roda + */ +package org.roda.wui.client.common.actions; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.roda.core.data.v2.disposal.hold.DisposalHold; +import org.roda.core.data.v2.disposal.hold.DisposalHoldState; +import org.roda.core.data.v2.ip.disposalhold.UpdateDisposalHoldRequest; +import org.roda.wui.client.common.actions.model.ActionableBundle; +import org.roda.wui.client.common.actions.model.ActionableGroup; +import org.roda.wui.client.common.actions.model.ActionableObject; +import org.roda.wui.client.disposal.hold.EditDisposalHold; +import org.roda.wui.client.disposal.hold.ShowDisposalHold; +import org.roda.wui.client.services.Services; +import org.roda.wui.common.client.tools.HistoryUtils; +import org.roda.wui.common.client.widgets.Toast; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.rpc.AsyncCallback; + +import config.i18n.client.ClientMessages; + +/** + * @author Miguel Guimarães + */ +public class DisposalHoldToolbarActions extends AbstractActionable { + private static final ClientMessages messages = GWT.create(ClientMessages.class); + + private static final Set POSSIBLE_ACTIONS_ON_DISPOSAL_HOLD = new HashSet<>(Arrays + .asList(DisposalHoldAction.LIFT, DisposalHoldAction.SAVE, DisposalHoldAction.EDIT, DisposalHoldAction.UPDATE)); + + private static final DisposalHoldToolbarActions GENERAL_INSTANCE = new DisposalHoldToolbarActions(); + + private DisposalHoldToolbarActions() { + } + + public static DisposalHoldToolbarActions get() { + return GENERAL_INSTANCE; + } + + @Override + public Action[] getActions() { + return DisposalHoldAction.values(); + } + + @Override + public CanActResult userCanAct(Action action, ActionableObject object) { + return new CanActResult(hasPermissions(action), CanActResult.Reason.USER, messages.reasonUserLacksPermission()); + } + + @Override + public CanActResult userCanAct(Action action, DisposalHold object) { + return new CanActResult(hasPermissions(action), CanActResult.Reason.USER, messages.reasonUserLacksPermission()); + } + + @Override + public CanActResult contextCanAct(Action action, DisposalHold object) { + if (object.getState().equals(DisposalHoldState.LIFTED)) { + return new CanActResult(false, CanActResult.Reason.CONTEXT, messages.reasonHoldAlreadyLifted()); + } + + return new CanActResult(POSSIBLE_ACTIONS_ON_DISPOSAL_HOLD.contains(action), CanActResult.Reason.CONTEXT, + messages.reasonCantActOnGroup()); + } + + @Override + public CanActResult contextCanAct(Action action, ActionableObject object) { + if (object.getObject().getState().equals(DisposalHoldState.LIFTED)) { + return new CanActResult(false, CanActResult.Reason.CONTEXT, messages.reasonHoldAlreadyLifted()); + } + return new CanActResult(POSSIBLE_ACTIONS_ON_DISPOSAL_HOLD.contains(action), CanActResult.Reason.CONTEXT, + messages.reasonCantActOnGroup()); + } + + @Override + public void act(Action action, DisposalHold hold, AsyncCallback callback) { + if (DisposalHoldAction.SAVE.equals(action)) { + create(hold, callback); + } else if (DisposalHoldAction.UPDATE.equals(action)) { + update(hold, callback); + } else if (DisposalHoldAction.EDIT.equals(action)) { + edit(hold, callback); + } else if (DisposalHoldAction.LIFT.equals(action)) { + DisposalHoldActions.lift(hold, callback); + } else { + unsupportedAction(action, callback); + } + } + + @Override + public ActionableBundle createActionsBundle() { + ActionableBundle disposalHoldActionableBundle = new ActionableBundle<>(); + + ActionableGroup managementGroup = new ActionableGroup<>(messages.manage(), "btn-edit"); + managementGroup.addButton(messages.editButton(), DisposalHoldAction.EDIT, ActionImpact.UPDATED, "fas fa-pencil"); + managementGroup.addButton(messages.liftButton(), DisposalHoldAction.LIFT, ActionImpact.UPDATED, "btn-lift-hold"); + managementGroup.addButton(messages.saveButton(), DisposalHoldAction.SAVE, ActionImpact.UPDATED, "btn-play"); + managementGroup.addButton(messages.updateButton(), DisposalHoldAction.UPDATE, ActionImpact.UPDATED, "btn-play"); + + disposalHoldActionableBundle.addGroup(managementGroup); + return disposalHoldActionableBundle; + } + + @Override + public Action actionForName(String name) { + return null; + } + + private void edit(DisposalHold hold, AsyncCallback callback) { + HistoryUtils.newHistory(EditDisposalHold.RESOLVER, hold.getId()); + callback.onSuccess(ActionImpact.NONE); + } + + private void create(DisposalHold hold, AsyncCallback callback) { + + Services services = new Services("Create disposal hold", "create"); + services.disposalHoldResource(s -> s.createDisposalHold(hold)).whenComplete((created, throwable) -> { + if (throwable != null) { + Toast.showError(throwable); + callback.onSuccess(ActionImpact.NONE); + } else { + Toast.showInfo(messages.showDisposalHoldTitle(), messages.disposalHoldSuccessfullyCreated()); + HistoryUtils.newHistory(ShowDisposalHold.RESOLVER, created.getId()); + callback.onSuccess(ActionImpact.UPDATED); + } + }); + } + + private void update(DisposalHold hold, AsyncCallback callback) { + Services services = new Services("Update disposal hold", "update"); + UpdateDisposalHoldRequest request = new UpdateDisposalHoldRequest(); + request.setDisposalHold(hold); + services.disposalHoldResource(s -> s.updateDisposalHold(request)).whenComplete((updated, throwable) -> { + if (throwable != null) { + Toast.showError(throwable); + callback.onSuccess(ActionImpact.NONE); + } else { + Toast.showInfo(messages.showDisposalHoldTitle(), messages.disposalHoldSuccessfullyUpdated()); + HistoryUtils.newHistory(ShowDisposalHold.RESOLVER, updated.getId()); + callback.onSuccess(ActionImpact.UPDATED); + } + }); + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalRuleAction.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalRuleAction.java new file mode 100644 index 0000000000..d34ddf982e --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalRuleAction.java @@ -0,0 +1,31 @@ +package org.roda.wui.client.common.actions; + +import java.util.Arrays; +import java.util.List; + +import org.roda.core.data.common.RodaConstants; +import org.roda.core.data.v2.disposal.rule.DisposalRule; + +/** + * @author Miguel Guimarães + */ + +public enum DisposalRuleAction implements Actionable.Action { + NEW(RodaConstants.PERMISSION_METHOD_CREATE_DISPOSAL_RULE), SAVE(RodaConstants.PERMISSION_METHOD_CREATE_DISPOSAL_RULE), + EDIT(RodaConstants.PERMISSION_METHOD_UPDATE_DISPOSAL_RULE), + UPDATE(RodaConstants.PERMISSION_METHOD_CREATE_DISPOSAL_RULE), + APPLY_RULES(RodaConstants.PERMISSION_METHOD_ASSOCIATE_DISPOSAL_SCHEDULE), + CHANGE_ORDER(RodaConstants.PERMISSION_METHOD_UPDATE_DISPOSAL_RULE), + REMOVE(RodaConstants.PERMISSION_METHOD_DELETE_DISPOSAL_RULE); + + private final List methods; + + DisposalRuleAction(String... methods) { + this.methods = Arrays.asList(methods); + } + + @Override + public List getMethods() { + return this.methods; + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalRuleActions.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalRuleActions.java index 007b8ecdac..cf6745a745 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalRuleActions.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalRuleActions.java @@ -7,11 +7,18 @@ */ package org.roda.wui.client.common.actions; +import com.google.gwt.user.client.Timer; +import org.roda.core.data.common.RodaConstants; +import org.roda.core.data.utils.SelectedItemsUtils; +import org.roda.core.data.v2.disposal.rule.ChangeOrderRequest; +import org.roda.core.data.v2.disposal.rule.DisposalRule; +import org.roda.core.data.v2.index.select.SelectedItems; import org.roda.wui.client.common.NoAsyncCallback; import org.roda.wui.client.common.dialogs.Dialogs; import org.roda.wui.client.common.dialogs.DisposalDialogs; import org.roda.wui.client.common.utils.AsyncCallbackUtils; import org.roda.wui.client.disposal.policy.DisposalPolicy; +import org.roda.wui.client.disposal.rule.EditDisposalRule; import org.roda.wui.client.ingest.process.ShowJob; import org.roda.wui.client.process.InternalProcess; import org.roda.wui.client.services.Services; @@ -29,6 +36,9 @@ public class DisposalRuleActions { private static final ClientMessages messages = GWT.create(ClientMessages.class); + private DisposalRuleActions() { + } + public static void applyDisposalRulesAction() { Dialogs.showConfirmDialog(messages.applyDisposalRulesDialogTitle(), messages.applyDisposalRulesDialogMessage(), messages.dialogNo(), messages.dialogYes(), new NoAsyncCallback() { @@ -66,4 +76,74 @@ public void onSuccess(final Void nothing) { } }); } + + public static void editDisposalRule(DisposalRule rule, AsyncCallback callback) { + HistoryUtils.newHistory(EditDisposalRule.RESOLVER, rule.getId()); + callback.onSuccess(Actionable.ActionImpact.UPDATED); + } + + public static void removeDisposalRule(DisposalRule rule, AsyncCallback callback) { + Dialogs.showConfirmDialog(messages.deleteDisposalRuleDialogTitle(), + messages.deleteDisposalRuleDialogMessage(rule.getTitle()), messages.dialogNo(), messages.dialogYes(), + new NoAsyncCallback() { + @Override + public void onSuccess(Boolean confirm) { + if (confirm) { + Services services = new Services("Delete disposal rule", "deletion"); + services.disposalRuleResource(s -> s.deleteDisposalRule(rule.getId())).whenComplete((unused, throwable) -> { + if (throwable != null) { + AsyncCallbackUtils.defaultFailureTreatment(throwable); + } else { + Toast.showInfo(messages.showDisposalRuleTitle(), + messages.deleteDisposalRuleSuccessMessage(rule.getTitle())); + DisposalRuleActions.applyDisposalRulesAction(); + } + }); + } else { + callback.onSuccess(Actionable.ActionImpact.NONE); + } + } + }); + } + + public static void removeMultipleDisposalRules(SelectedItems items, + AsyncCallback callback) { + + } + + public static void changeDisposalRulesOrder(SelectedItems items, + AsyncCallback callback) { + DisposalDialogs.reOrderDisposalRules(messages.editRules(), messages.disposalRuleChangeOrderMessage(), + new AsyncCallback() { + @Override + public void onSuccess(ChangeOrderRequest changeOrderRequest) { + changeOrderRequest.setItems(SelectedItemsUtils.convertToRESTRequest(items)); + + Services services = new Services("Update disposal rule order", "update"); + services.disposalRuleResource(s -> s.changeDisposalRuleOrder(changeOrderRequest)) + .whenComplete((unused, throwable) -> { + if (throwable != null) { + AsyncCallbackUtils.defaultFailureTreatment(throwable); + callback.onSuccess(Actionable.ActionImpact.NONE); + } else { + Toast.showInfo(messages.showDisposalRuleTitle(), + messages.updateDisposalRuleOrderSuccessMessage()); + Timer timer = new Timer() { + @Override + public void run() { + callback.onSuccess(Actionable.ActionImpact.UPDATED); + } + }; + + timer.schedule(RodaConstants.ACTION_TIMEOUT); + } + }); + } + + @Override + public void onFailure(Throwable caught) { + callback.onSuccess(Actionable.ActionImpact.NONE); + } + }); + } } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalRuleSearchWrapperActions.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalRuleSearchWrapperActions.java new file mode 100644 index 0000000000..dd34fe737d --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalRuleSearchWrapperActions.java @@ -0,0 +1,181 @@ +package org.roda.wui.client.common.actions; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.roda.core.data.v2.disposal.rule.DisposalRule; +import org.roda.core.data.v2.index.select.SelectedItems; +import org.roda.wui.client.common.actions.model.ActionableBundle; +import org.roda.wui.client.common.actions.model.ActionableGroup; +import org.roda.wui.client.disposal.rule.CreateDisposalRule; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.rpc.AsyncCallback; + +import config.i18n.client.ClientMessages; +import org.roda.wui.client.disposal.rule.ShowDisposalRule; +import org.roda.wui.client.services.Services; +import org.roda.wui.common.client.tools.HistoryUtils; +import org.roda.wui.common.client.widgets.Toast; + +/** + * @author Miguel Guimarães + */ + +public class DisposalRuleSearchWrapperActions extends AbstractActionable { + private static final DisposalRuleSearchWrapperActions INSTANCE = new DisposalRuleSearchWrapperActions(); + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private static final Set POSSIBLE_ACTIONS_WITHOUT_DISPOSAL_RULE = new HashSet<>( + Arrays.asList(DisposalRuleAction.NEW, DisposalRuleAction.APPLY_RULES)); + + private static final Set POSSIBLE_ACTIONS_ON_SINGLE_DISPOSAL_RULE = new HashSet<>( + Arrays.asList(DisposalRuleAction.EDIT, DisposalRuleAction.REMOVE, DisposalRuleAction.SAVE, + DisposalRuleAction.UPDATE, DisposalRuleAction.CHANGE_ORDER)); + + private static final Set POSSIBLE_ACTIONS_ON_MULTIPLE_DISPOSAL_RULE = new HashSet<>( + Arrays.asList(DisposalRuleAction.REMOVE, DisposalRuleAction.CHANGE_ORDER)); + + private DisposalRuleSearchWrapperActions() { + } + + public static DisposalRuleSearchWrapperActions get() { + return INSTANCE; + } + + @Override + public Action[] getActions() { + return DisposalRuleAction.values(); + } + + @Override + public CanActResult userCanAct(Action action) { + return new CanActResult(hasPermissions(action), CanActResult.Reason.USER, messages.reasonUserLacksPermission()); + } + + @Override + public CanActResult contextCanAct(Action action) { + return new CanActResult(POSSIBLE_ACTIONS_WITHOUT_DISPOSAL_RULE.contains(action), CanActResult.Reason.CONTEXT, + messages.reasonNoObjectSelected()); + } + + @Override + public CanActResult userCanAct(Action action, DisposalRule object) { + return new CanActResult(hasPermissions(action), CanActResult.Reason.USER, messages.reasonUserLacksPermission()); + } + + @Override + public CanActResult contextCanAct(Action action, DisposalRule object) { + return new CanActResult(POSSIBLE_ACTIONS_ON_SINGLE_DISPOSAL_RULE.contains(action), CanActResult.Reason.CONTEXT, + messages.reasonCantActOnSingleObject()); + } + + @Override + public CanActResult userCanAct(Action action, SelectedItems objects) { + return new CanActResult(hasPermissions(action), CanActResult.Reason.USER, messages.reasonUserLacksPermission()); + } + + @Override + public CanActResult contextCanAct(Action action, SelectedItems objects) { + return new CanActResult(POSSIBLE_ACTIONS_ON_MULTIPLE_DISPOSAL_RULE.contains(action), CanActResult.Reason.CONTEXT, + messages.reasonCantActOnMultipleObjects()); + } + + @Override + public void act(Action action, AsyncCallback callback) { + if (DisposalRuleAction.NEW.equals(action)) { + newRule(callback); + } else if (DisposalRuleAction.APPLY_RULES.equals(action)) { + DisposalRuleActions.applyDisposalRulesAction(); + callback.onSuccess(ActionImpact.NONE); + } else { + unsupportedAction(action, callback); + } + } + + @Override + public void act(Action action, DisposalRule rule, AsyncCallback callback) { + if (DisposalRuleAction.SAVE.equals(action)) { + saveRule(rule, callback); + } else if (DisposalRuleAction.EDIT.equals(action)) { + DisposalRuleActions.editDisposalRule(rule, callback); + } else if (DisposalRuleAction.REMOVE.equals(action)) { + DisposalRuleActions.removeDisposalRule(rule, callback); + } else if (DisposalRuleAction.UPDATE.equals(action)) { + updateRule(rule, callback); + } else if (DisposalRuleAction.CHANGE_ORDER.equals(action)) { + DisposalRuleActions.changeDisposalRulesOrder(objectToSelectedItems(rule, DisposalRule.class), callback); + } else { + unsupportedAction(action, callback); + } + } + + @Override + public void act(Action action, SelectedItems objects, AsyncCallback callback) { + if (DisposalRuleAction.REMOVE.equals(action)) { + DisposalRuleActions.removeMultipleDisposalRules(objects, callback); + } else if (DisposalRuleAction.CHANGE_ORDER.equals(action)) { + DisposalRuleActions.changeDisposalRulesOrder(objects, callback); + } else { + unsupportedAction(action, callback); + } + } + + @Override + public ActionableBundle createActionsBundle() { + ActionableBundle disposalRuleActionableBundle = new ActionableBundle<>(); + + ActionableGroup managementGroup = new ActionableGroup<>(messages.sidebarActionsTitle()); + managementGroup.addButton(messages.newDisposalRuleTitle(), DisposalRuleAction.NEW, ActionImpact.UPDATED, + "btn-plus-circle"); + + managementGroup.addButton(messages.editButton(), DisposalRuleAction.EDIT, ActionImpact.UPDATED, "btn-edit"); + managementGroup.addButton(messages.editRulesOrder(), DisposalRuleAction.CHANGE_ORDER, ActionImpact.UPDATED, + "btn-edit-order"); + managementGroup.addButton(messages.removeButton(), DisposalRuleAction.REMOVE, ActionImpact.UPDATED, "btn-ban"); + managementGroup.addButton(messages.saveButton(), DisposalRuleAction.SAVE, ActionImpact.UPDATED, "btn-play"); + managementGroup.addButton(messages.updateButton(), DisposalRuleAction.UPDATE, ActionImpact.UPDATED, "btn-play"); + managementGroup.addButton(messages.applyRules(), DisposalRuleAction.APPLY_RULES, ActionImpact.UPDATED, "btn-play"); + + disposalRuleActionableBundle.addGroup(managementGroup); + return disposalRuleActionableBundle; + } + + @Override + public Action actionForName(String name) { + return null; + } + + private void newRule(AsyncCallback callback) { + HistoryUtils.newHistory(CreateDisposalRule.RESOLVER.getHistoryPath()); + callback.onSuccess(ActionImpact.UPDATED); + } + + private void saveRule(DisposalRule rule, AsyncCallback callback) { + Services services = new Services("Create disposal rule", "create"); + services.disposalRuleResource(s -> s.createDisposalRule(rule)).whenComplete((created, throwable) -> { + if (throwable != null) { + Toast.showError(throwable); + callback.onSuccess(ActionImpact.NONE); + } else { + Toast.showInfo(messages.disposalRulesTitle(), messages.disposalRuleSuccessfullyCreated()); + HistoryUtils.newHistory(ShowDisposalRule.RESOLVER, created.getId()); + callback.onSuccess(ActionImpact.UPDATED); + } + }); + } + + private void updateRule(DisposalRule rule, AsyncCallback callback) { + Services services = new Services("Update disposal rule", "update"); + services.disposalRuleResource(s -> s.updateDisposalRule(rule)).whenComplete((updated, throwable) -> { + if (throwable != null) { + Toast.showError(throwable); + callback.onSuccess(ActionImpact.NONE); + } else { + Toast.showInfo(messages.disposalRulesTitle(), messages.disposalRuleSuccessfullyUpdated()); + HistoryUtils.newHistory(ShowDisposalRule.RESOLVER, updated.getId()); + callback.onSuccess(ActionImpact.UPDATED); + } + }); + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalRuleToolbarActions.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalRuleToolbarActions.java new file mode 100644 index 0000000000..40f7b06400 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalRuleToolbarActions.java @@ -0,0 +1,98 @@ +package org.roda.wui.client.common.actions; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.roda.core.data.v2.disposal.rule.DisposalRule; +import org.roda.core.data.v2.index.select.SelectedItems; +import org.roda.wui.client.common.actions.model.ActionableBundle; +import org.roda.wui.client.common.actions.model.ActionableGroup; +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.rpc.AsyncCallback; + +import config.i18n.client.ClientMessages; + +/** + * @author Miguel Guimarães + */ + +public class DisposalRuleToolbarActions extends AbstractActionable { + private static final DisposalRuleToolbarActions INSTANCE = new DisposalRuleToolbarActions(); + private static final ClientMessages messages = GWT.create(ClientMessages.class); + + private static final Set POSSIBLE_ACTIONS_ON_SINGLE_DISPOSAL_RULE = new HashSet<>( + Arrays.asList(DisposalRuleAction.EDIT, DisposalRuleAction.REMOVE)); + + private DisposalRuleToolbarActions() { + } + + public static DisposalRuleToolbarActions get() { + return INSTANCE; + } + + @Override + public Action[] getActions() { + return DisposalRuleAction.values(); + } + + @Override + public CanActResult userCanAct(Action action) { + return new CanActResult(hasPermissions(action), CanActResult.Reason.USER, messages.reasonUserLacksPermission()); + } + + @Override + public CanActResult contextCanAct(Action action) { + return new CanActResult(false, CanActResult.Reason.CONTEXT, messages.reasonNoObjectSelected()); + } + + @Override + public CanActResult userCanAct(Action action, DisposalRule object) { + return new CanActResult(hasPermissions(action), CanActResult.Reason.USER, messages.reasonUserLacksPermission()); + } + + @Override + public CanActResult contextCanAct(Action action, DisposalRule object) { + return new CanActResult(POSSIBLE_ACTIONS_ON_SINGLE_DISPOSAL_RULE.contains(action), CanActResult.Reason.CONTEXT, + messages.reasonCantActOnSingleObject()); + } + + @Override + public CanActResult userCanAct(Action action, SelectedItems objects) { + return new CanActResult(hasPermissions(action), CanActResult.Reason.USER, messages.reasonUserLacksPermission()); + } + + @Override + public CanActResult contextCanAct(Action action, SelectedItems objects) { + return new CanActResult(false, CanActResult.Reason.CONTEXT, messages.reasonCantActOnMultipleObjects()); + } + + @Override + public void act(Action action, DisposalRule rule, AsyncCallback callback) { + if (DisposalRuleAction.REMOVE.equals(action)) { + DisposalRuleActions.removeDisposalRule(rule, callback); + } else if (DisposalRuleAction.EDIT.equals(action)) { + DisposalRuleActions.editDisposalRule(rule, callback); + } else { + unsupportedAction(action, callback); + } + } + + @Override + public ActionableBundle createActionsBundle() { + ActionableBundle disposalRuleActionableBundle = new ActionableBundle<>(); + + ActionableGroup managementGroup = new ActionableGroup<>(messages.manage()); + + managementGroup.addButton(messages.editButton(), DisposalRuleAction.EDIT, ActionImpact.UPDATED, "btn-edit"); + managementGroup.addButton(messages.removeButton(), DisposalRuleAction.REMOVE, ActionImpact.UPDATED, "btn-ban"); + + disposalRuleActionableBundle.addGroup(managementGroup); + return disposalRuleActionableBundle; + } + + @Override + public Action actionForName(String name) { + return null; + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalScheduleAction.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalScheduleAction.java new file mode 100644 index 0000000000..07f73db660 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalScheduleAction.java @@ -0,0 +1,31 @@ +package org.roda.wui.client.common.actions; + +import java.util.Arrays; +import java.util.List; + +import org.roda.core.data.common.RodaConstants; +import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; + +/** + * @author Miguel Guimarães + */ + +public enum DisposalScheduleAction implements Actionable.Action { + NEW(RodaConstants.PERMISSION_METHOD_CREATE_DISPOSAL_SCHEDULE), + SAVE(RodaConstants.PERMISSION_METHOD_CREATE_DISPOSAL_SCHEDULE), + REMOVE(RodaConstants.PERMISSION_METHOD_DELETE_DISPOSAL_SCHEDULE), + DISASSOCIATE(RodaConstants.PERMISSION_METHOD_ASSOCIATE_DISPOSAL_SCHEDULE), + EDIT(RodaConstants.PERMISSION_METHOD_UPDATE_DISPOSAL_SCHEDULE), + UPDATE(RodaConstants.PERMISSION_METHOD_UPDATE_DISPOSAL_SCHEDULE); + + private final List methods; + + DisposalScheduleAction(String... methods) { + this.methods = Arrays.asList(methods); + } + + @Override + public List getMethods() { + return this.methods; + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalScheduleActions.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalScheduleActions.java index 1eea5961b5..aa196a5140 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalScheduleActions.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalScheduleActions.java @@ -1,186 +1,63 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE file at the root of the source - * tree and available online at - * - * https://github.com/keeps/roda - */ package org.roda.wui.client.common.actions; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import org.roda.core.data.common.RodaConstants; -import org.roda.core.data.utils.SelectedItemsUtils; -import org.roda.core.data.v2.index.select.SelectedItems; -import org.roda.core.data.v2.ip.IndexedAIP; -import org.roda.wui.client.common.actions.callbacks.ActionNoAsyncCallback; -import org.roda.wui.client.common.actions.model.ActionableBundle; -import org.roda.wui.client.common.actions.model.ActionableGroup; -import org.roda.wui.client.common.actions.model.ActionableObject; -import org.roda.wui.client.common.dialogs.Dialogs; -import org.roda.wui.client.common.lists.utils.ClientSelectedItemsUtils; -import org.roda.wui.client.ingest.process.ShowJob; -import org.roda.wui.client.process.InternalProcess; -import org.roda.wui.client.services.Services; -import org.roda.wui.common.client.tools.HistoryUtils; -import org.roda.wui.common.client.widgets.Toast; - import com.google.gwt.core.client.GWT; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.rpc.AsyncCallback; - import config.i18n.client.ClientMessages; +import org.roda.core.data.common.RodaConstants; +import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; +import org.roda.wui.client.common.actions.callbacks.ActionAsyncCallback; +import org.roda.wui.client.common.dialogs.Dialogs; +import org.roda.wui.client.services.Services; +import org.roda.wui.common.client.widgets.Toast; /** * @author Miguel Guimarães */ -public class DisposalScheduleActions extends AbstractActionable { - public static final String NO_DISPOSAL_SCHEDULE_ID = null; - - private static final DisposalScheduleActions INSTANCE = new DisposalScheduleActions(NO_DISPOSAL_SCHEDULE_ID); - private static final ClientMessages messages = GWT.create(ClientMessages.class); - - private final String disposalScheduleId; - - private DisposalScheduleActions(String disposalScheduleId) { - this.disposalScheduleId = disposalScheduleId; - } - private static final Set POSSIBLE_ACTIONS_ON_DISPOSAL_SCHEDULE = new HashSet<>( - Collections.singletonList(DisposalScheduleAction.DISASSOCIATE)); +public class DisposalScheduleActions { - public enum DisposalScheduleAction implements Action { - DISASSOCIATE(RodaConstants.PERMISSION_METHOD_ASSOCIATE_DISPOSAL_SCHEDULE); - - private List methods; - - DisposalScheduleAction(String... methods) { - this.methods = Arrays.asList(methods); - } - - @Override - public List getMethods() { - return this.methods; - } - } - - @Override - public Action[] getActions() { - return DisposalScheduleAction.values(); - } - - public static DisposalScheduleActions get() { - return INSTANCE; - } - - public static DisposalScheduleActions get(String disposalScheduleId) { - return new DisposalScheduleActions(disposalScheduleId); - } - - @Override - public CanActResult userCanAct(Action action, ActionableObject object) { - return new CanActResult(hasPermissions(action), CanActResult.Reason.USER, messages.reasonUserLacksPermission()); - } - - @Override - public CanActResult contextCanAct(Action action, ActionableObject object) { - if (object.getObject() != null || object.getObjects() != null) { - return new CanActResult(POSSIBLE_ACTIONS_ON_DISPOSAL_SCHEDULE.contains(action), CanActResult.Reason.CONTEXT, - messages.reasonInvalidContext()); - } else { - return new CanActResult(false, CanActResult.Reason.CONTEXT, messages.reasonNoObjectSelected()); - } - } - - @Override - public void act(Action action, IndexedAIP aip, AsyncCallback callback) { - if (DisposalScheduleAction.DISASSOCIATE.equals(action)) { - disassociate(objectToSelectedItems(aip, IndexedAIP.class), callback); - } else { - unsupportedAction(action, callback); - } - } - - @Override - public void act(Action action, SelectedItems items, AsyncCallback callback) { - if (DisposalScheduleAction.DISASSOCIATE.equals(action)) { - disassociate(items, callback); - } else { - unsupportedAction(action, callback); - } - } - - private void disassociate(SelectedItems items, AsyncCallback callback) { - ClientSelectedItemsUtils.size(IndexedAIP.class, items, new ActionNoAsyncCallback(callback) { - @Override - public void onSuccess(final Long size) { - Dialogs.showConfirmDialog(messages.disassociateDisposalScheduleDialogTitle(), - messages.disassociateDisposalScheduleDialogMessage(size.intValue()), messages.dialogNo(), - messages.dialogYes(), new ActionNoAsyncCallback(callback) { - @Override - public void onSuccess(Boolean result) { - if (result) { - Services services = new Services("Disassociate disposal schedule from AIP", "job"); - services - .disposalScheduleResource( - s -> s.disassociatedDisposalSchedule(SelectedItemsUtils.convertToRESTRequest(items))) - .whenComplete((job, throwable) -> { - if (throwable != null) { - callback.onFailure(throwable); - HistoryUtils.newHistory(InternalProcess.RESOLVER); - } else { - Dialogs.showJobRedirectDialog(messages.jobCreatedMessage(), new AsyncCallback() { - - @Override - public void onFailure(Throwable caught) { - Toast.showInfo(messages.runningInBackgroundTitle(), - messages.runningInBackgroundDescription()); - - Timer timer = new Timer() { - @Override - public void run() { - doActionCallbackUpdated(); - } - }; - - timer.schedule(RodaConstants.ACTION_TIMEOUT); - } + private static final ClientMessages messages = GWT.create(ClientMessages.class); - @Override - public void onSuccess(final Void nothing) { - doActionCallbackNone(); - HistoryUtils.newHistory(ShowJob.RESOLVER, job.getId()); - } - }); + private DisposalScheduleActions() { + // Private constructor to prevent instantiation + } + + public static void removeDisposalSchedule(DisposalSchedule schedule, + AsyncCallback callback) { + Dialogs.showConfirmDialog(messages.disposalScheduleRemoveConfirmDialogTitle(), + messages.disposalScheduleRemoveConfirmDialogMessage(), messages.dialogNo(), messages.dialogYes(), + new ActionAsyncCallback(callback) { + @Override + public void onSuccess(Boolean confirmed) { + if (confirmed) { + Services services = new Services("Delete disposal schedule", "delete"); + services.disposalScheduleResource(s -> s.deleteDisposalSchedule(schedule.getId())) + .whenComplete((res, error) -> { + if (error == null) { + Timer timer = new Timer() { + @Override + public void run() { + Toast.showInfo(messages.disposalSchedulesTitle(), messages.disposalScheduleRemovedWithSuccess()); + doActionCallbackDestroyed(); } - }); - } else { - doActionCallbackNone(); - } - } - }); - } - }); - } - - @Override - public ActionableBundle createActionsBundle() { - ActionableBundle disposalScheduleActionableBundle = new ActionableBundle<>(); - - ActionableGroup managementGroup = new ActionableGroup<>(messages.sidebarActionsTitle()); - managementGroup.addButton(messages.disassociateDisposalScheduleButton(), DisposalScheduleAction.DISASSOCIATE, - ActionImpact.UPDATED, "fas fa-calendar"); - - disposalScheduleActionableBundle.addGroup(managementGroup); - return disposalScheduleActionableBundle; - } - - @Override - public Action actionForName(String name) { - return null; + }; + + timer.schedule(RodaConstants.ACTION_TIMEOUT); + } else { + Toast.showError(error.getMessage()); + callback.onSuccess(Actionable.ActionImpact.NONE); + } + }); + } else { + doActionCallbackNone(); + } + } + + @Override + public void onFailure(Throwable caught) { + callback.onFailure(caught); + } + }); } } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalScheduleSearchWrapperActions.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalScheduleSearchWrapperActions.java new file mode 100644 index 0000000000..174910b417 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalScheduleSearchWrapperActions.java @@ -0,0 +1,129 @@ +package org.roda.wui.client.common.actions; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; +import org.roda.core.data.v2.disposal.schedule.DisposalSchedules; +import org.roda.core.data.v2.index.select.SelectedItems; +import org.roda.wui.client.common.actions.model.ActionableBundle; +import org.roda.wui.client.common.actions.model.ActionableGroup; +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.rpc.AsyncCallback; + +import config.i18n.client.ClientMessages; +import org.roda.wui.client.disposal.Disposal; +import org.roda.wui.client.disposal.schedule.CreateDisposalSchedule; +import org.roda.wui.client.disposal.schedule.EditDisposalSchedule; +import org.roda.wui.common.client.tools.HistoryUtils; + +/** + * @author Miguel Guimarães + */ + +public class DisposalScheduleSearchWrapperActions extends AbstractActionable { + private static final DisposalScheduleSearchWrapperActions INSTANCE = new DisposalScheduleSearchWrapperActions(); + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private static final Set POSSIBLE_ACTIONS_WITHOUT_SCHEDULE = new HashSet<>( + Arrays.asList(DisposalScheduleAction.NEW)); + + private static final Set POSSIBLE_ACTIONS_ON_SINGLE_DISPOSAL_SCHEDULE = new HashSet<>( + Arrays.asList(DisposalScheduleAction.EDIT, DisposalScheduleAction.REMOVE)); + + private DisposalScheduleSearchWrapperActions() { + } + + public static DisposalScheduleSearchWrapperActions get() { + return INSTANCE; + } + + @Override + public Action[] getActions() { + return DisposalScheduleAction.values(); + } + + @Override + public CanActResult userCanAct(Action action) { + return new CanActResult(hasPermissions(action), CanActResult.Reason.USER, messages.reasonUserLacksPermission()); + } + + @Override + public CanActResult contextCanAct(Action action) { + return new CanActResult(POSSIBLE_ACTIONS_WITHOUT_SCHEDULE.contains(action), CanActResult.Reason.CONTEXT, + messages.reasonNoObjectSelected()); + } + + @Override + public CanActResult userCanAct(Action action, DisposalSchedule object) { + return new CanActResult(hasPermissions(action), CanActResult.Reason.USER, messages.reasonUserLacksPermission()); + } + + @Override + public CanActResult contextCanAct(Action action, DisposalSchedule object) { + return new CanActResult(POSSIBLE_ACTIONS_ON_SINGLE_DISPOSAL_SCHEDULE.contains(action), CanActResult.Reason.CONTEXT, + messages.reasonCantActOnSingleObject()); + } + + @Override + public CanActResult userCanAct(Action action, SelectedItems objects) { + return new CanActResult(hasPermissions(action), CanActResult.Reason.USER, messages.reasonUserLacksPermission()); + } + + @Override + public CanActResult contextCanAct(Action action, SelectedItems objects) { + return new CanActResult(false, CanActResult.Reason.CONTEXT, messages.reasonCantActOnMultipleObjects()); + } + + @Override + public void act(Action action, AsyncCallback callback) { + if (DisposalScheduleAction.NEW.equals(action)) { + newDisposalSchedule(callback); + } else { + unsupportedAction(action, callback); + } + } + + @Override + public void act(Action action, DisposalSchedule schedule, AsyncCallback callback) { + if (DisposalScheduleAction.REMOVE.equals(action)) { + DisposalScheduleActions.removeDisposalSchedule(schedule, callback); + } else if (DisposalScheduleAction.EDIT.equals(action)) { + editDisposalSchedule(schedule, callback); + } else { + unsupportedAction(action, callback); + } + } + + @Override + public ActionableBundle createActionsBundle() { + ActionableBundle disposalScheduleActionableBundle = new ActionableBundle<>(); + + ActionableGroup managementGroup = new ActionableGroup<>(messages.sidebarActionsTitle()); + managementGroup.addButton(messages.newDisposalScheduleTitle(), DisposalScheduleAction.NEW, ActionImpact.UPDATED, + "btn-plus-circle"); + + managementGroup.addButton(messages.editButton(), DisposalScheduleAction.EDIT, ActionImpact.UPDATED, "btn-edit"); + + managementGroup.addButton(messages.removeButton(), DisposalScheduleAction.REMOVE, ActionImpact.DESTROYED, + "btn-ban"); + + disposalScheduleActionableBundle.addGroup(managementGroup); + return disposalScheduleActionableBundle; + } + + @Override + public Action actionForName(String name) { + return null; + } + + private void newDisposalSchedule(AsyncCallback callback) { + HistoryUtils.newHistory(CreateDisposalSchedule.RESOLVER.getHistoryPath()); + callback.onSuccess(ActionImpact.UPDATED); + } + + private void editDisposalSchedule(DisposalSchedule schedule, AsyncCallback callback) { + HistoryUtils.newHistory(EditDisposalSchedule.RESOLVER, schedule.getId()); + callback.onSuccess(ActionImpact.UPDATED); + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalScheduleToolbarActions.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalScheduleToolbarActions.java new file mode 100644 index 0000000000..c6ce9fb664 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/DisposalScheduleToolbarActions.java @@ -0,0 +1,148 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE file at the root of the source + * tree and available online at + * + * https://github.com/keeps/roda + */ +package org.roda.wui.client.common.actions; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; +import org.roda.wui.client.common.actions.callbacks.ActionAsyncCallback; +import org.roda.wui.client.common.actions.model.ActionableBundle; +import org.roda.wui.client.common.actions.model.ActionableGroup; +import org.roda.wui.client.common.actions.model.ActionableObject; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.rpc.AsyncCallback; + +import config.i18n.client.ClientMessages; +import org.roda.wui.client.common.dialogs.DisposalDialogs; +import org.roda.wui.client.disposal.schedule.EditDisposalSchedule; +import org.roda.wui.client.disposal.schedule.ShowDisposalSchedule; +import org.roda.wui.client.services.Services; +import org.roda.wui.common.client.tools.HistoryUtils; +import org.roda.wui.common.client.widgets.Toast; + +/** + * @author Miguel Guimarães + */ +public class DisposalScheduleToolbarActions extends AbstractActionable { + private static final ClientMessages messages = GWT.create(ClientMessages.class); + + private static final Set POSSIBLE_ACTIONS_ON_DISPOSAL_SCHEDULE = new HashSet<>( + Arrays.asList(DisposalScheduleAction.EDIT, DisposalScheduleAction.REMOVE, DisposalScheduleAction.SAVE, + DisposalScheduleAction.UPDATE)); + + private static final DisposalScheduleToolbarActions GENERAL_INSTANCE = new DisposalScheduleToolbarActions(); + + private DisposalScheduleToolbarActions() { + } + + public static DisposalScheduleToolbarActions get() { + return GENERAL_INSTANCE; + } + + @Override + public Action[] getActions() { + return DisposalScheduleAction.values(); + } + + @Override + public CanActResult userCanAct(Action action, ActionableObject object) { + return new CanActResult(hasPermissions(action), CanActResult.Reason.USER, messages.reasonUserLacksPermission()); + } + + @Override + public CanActResult contextCanAct(Action action, ActionableObject object) { + return new CanActResult(POSSIBLE_ACTIONS_ON_DISPOSAL_SCHEDULE.contains(action), CanActResult.Reason.CONTEXT, + messages.reasonCantActOnGroup()); + } + + @Override + public CanActResult userCanAct(Action action, DisposalSchedule object) { + return new CanActResult(hasPermissions(action), CanActResult.Reason.USER, messages.reasonUserLacksPermission()); + } + + @Override + public CanActResult contextCanAct(Action action, DisposalSchedule object) { + return new CanActResult(POSSIBLE_ACTIONS_ON_DISPOSAL_SCHEDULE.contains(action), CanActResult.Reason.CONTEXT, + messages.reasonCantActOnSingleObject()); + } + + @Override + public void act(Action action, DisposalSchedule schedule, AsyncCallback callback) { + if (DisposalScheduleAction.EDIT.equals(action)) { + edit(schedule, callback); + } else if (DisposalScheduleAction.SAVE.equals(action)) { + create(schedule, callback); + } else if (DisposalScheduleAction.UPDATE.equals(action)) { + update(schedule, callback); + } else if (DisposalScheduleAction.REMOVE.equals(action)) { + DisposalScheduleActions.removeDisposalSchedule(schedule, callback); + } else { + unsupportedAction(action, callback); + } + } + + @Override + public ActionableBundle createActionsBundle() { + ActionableBundle disposalScheduleActionableBundle = new ActionableBundle<>(); + + ActionableGroup managementGroup = new ActionableGroup<>(messages.manage(), "btn-edit"); + managementGroup.addButton(messages.editButton(), DisposalScheduleAction.EDIT, ActionImpact.UPDATED, + "fas fa-pencil"); + managementGroup.addButton(messages.removeButton(), DisposalScheduleAction.REMOVE, ActionImpact.UPDATED, + "fas fa-trash"); + + managementGroup.addButton(messages.saveButton(), DisposalScheduleAction.SAVE, ActionImpact.UPDATED, "btn-play"); + + managementGroup.addButton(messages.updateButton(), DisposalScheduleAction.UPDATE, ActionImpact.UPDATED, "btn-play"); + + disposalScheduleActionableBundle.addGroup(managementGroup); + return disposalScheduleActionableBundle; + } + + @Override + public Action actionForName(String name) { + return null; + } + + private void edit(DisposalSchedule schedule, AsyncCallback callback) { + HistoryUtils.newHistory(EditDisposalSchedule.RESOLVER, schedule.getId()); + callback.onSuccess(ActionImpact.UPDATED); + } + + private void create(DisposalSchedule schedule, AsyncCallback callback) { + Services services = new Services("Create disposal schedule", "create"); + services.disposalScheduleResource(s -> s.createDisposalSchedule(schedule)).whenComplete((created, throwable) -> { + if (throwable != null) { + Toast.showError(throwable); + callback.onSuccess(ActionImpact.NONE); + } else { + Toast.showInfo("Disposal schedule", "Disposal schedule successfully created"); + HistoryUtils.newHistory(ShowDisposalSchedule.RESOLVER, created.getId()); + callback.onSuccess(ActionImpact.UPDATED); + } + }); + } + + private void update(DisposalSchedule schedule, AsyncCallback callback) { + Services services = new Services("Update disposal schedule", "update"); + services.disposalScheduleResource(s -> s.updateDisposalSchedule(schedule)).whenComplete((updated, throwable) -> { + if (throwable != null) { + Toast.showError(throwable); + callback.onSuccess(ActionImpact.NONE); + } else { + Toast.showInfo("Disposal schedule", "Disposal schedule successfully updated"); + HistoryUtils.newHistory(ShowDisposalSchedule.RESOLVER, updated.getId()); + callback.onSuccess(ActionImpact.UPDATED); + } + }); + } + +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/IndexedAIPDisposalHoldSearchWrapperActions.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/IndexedAIPDisposalHoldSearchWrapperActions.java new file mode 100644 index 0000000000..9af43e80e5 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/IndexedAIPDisposalHoldSearchWrapperActions.java @@ -0,0 +1,198 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE file at the root of the source + * tree and available online at + * + * https://github.com/keeps/roda + */ +package org.roda.wui.client.common.actions; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.roda.core.data.common.RodaConstants; +import org.roda.core.data.utils.SelectedItemsUtils; +import org.roda.core.data.v2.disposal.hold.DisposalHold; +import org.roda.core.data.v2.index.select.SelectedItems; +import org.roda.core.data.v2.ip.IndexedAIP; +import org.roda.core.data.v2.ip.disposalhold.DisassociateDisposalHoldRequest; +import org.roda.wui.client.common.actions.callbacks.ActionNoAsyncCallback; +import org.roda.wui.client.common.actions.model.ActionableBundle; +import org.roda.wui.client.common.actions.model.ActionableGroup; +import org.roda.wui.client.common.actions.model.ActionableObject; +import org.roda.wui.client.common.dialogs.Dialogs; +import org.roda.wui.client.common.lists.utils.ClientSelectedItemsUtils; +import org.roda.wui.client.ingest.process.ShowJob; +import org.roda.wui.client.process.InternalProcess; +import org.roda.wui.client.services.Services; +import org.roda.wui.common.client.tools.HistoryUtils; +import org.roda.wui.common.client.widgets.Toast; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.regexp.shared.RegExp; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.rpc.AsyncCallback; + +import config.i18n.client.ClientMessages; + +/** + * @author Tiago Fraga + */ +public class IndexedAIPDisposalHoldSearchWrapperActions extends AbstractActionable { + private static final IndexedAIPDisposalHoldSearchWrapperActions INSTANCE = new IndexedAIPDisposalHoldSearchWrapperActions(); + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private static final Set POSSIBLE_ACTIONS_ON_DISPOSAL_HOLD = new HashSet<>( + Arrays.asList(DisposalHoldAction.DISASSOCIATE)); + private DisposalHold disposalHold; + + public IndexedAIPDisposalHoldSearchWrapperActions() { + } + + public IndexedAIPDisposalHoldSearchWrapperActions(DisposalHold disposalHold) { + this.disposalHold = disposalHold; + } + + public static IndexedAIPDisposalHoldSearchWrapperActions get() { + return INSTANCE; + } + + @Override + public Action[] getActions() { + return IndexedAIPDisposalHoldSearchWrapperActions.DisposalHoldAction.values(); + } + + @Override + public Action actionForName(String name) { + return IndexedAIPDisposalHoldSearchWrapperActions.DisposalHoldAction.valueOf(name); + } + + @Override + public CanActResult userCanAct(Action action, ActionableObject object) { + return new CanActResult(hasPermissions(action), CanActResult.Reason.USER, messages.reasonUserLacksPermission()); + } + + @Override + public CanActResult contextCanAct(Action action, ActionableObject object) { + if (object.getObject() != null || object.getObjects() != null) { + return new CanActResult(POSSIBLE_ACTIONS_ON_DISPOSAL_HOLD.contains(action), CanActResult.Reason.CONTEXT, + messages.reasonInvalidContext()); + } else { + return new CanActResult(false, CanActResult.Reason.CONTEXT, messages.reasonNoObjectSelected()); + } + } + + @Override + public void act(Action action, IndexedAIP aip, AsyncCallback callback) { + if (DisposalHoldAction.DISASSOCIATE.equals(action)) { + disassociate(objectToSelectedItems(aip, IndexedAIP.class), callback); + } else { + unsupportedAction(action, callback); + } + } + + @Override + public void act(Action action, SelectedItems aips, AsyncCallback callback) { + if (DisposalHoldAction.DISASSOCIATE.equals(action)) { + disassociate(aips, callback); + } else { + unsupportedAction(action, callback); + } + } + + private void disassociate(final SelectedItems aips, final AsyncCallback callback) { + ClientSelectedItemsUtils.size(IndexedAIP.class, aips, new ActionNoAsyncCallback(callback) { + @Override + public void onSuccess(final Long size) { + Dialogs.showConfirmDialog(messages.disassociateDisposalHoldDialogTitle(), + messages.disassociateDisposalHoldDialogMessage(size.intValue()), messages.dialogNo(), messages.dialogYes(), + new ActionNoAsyncCallback(callback) { + @Override + public void onSuccess(Boolean result) { + if (result) { + Dialogs.showPromptDialog(messages.outcomeDetailTitle(), null, null, messages.outcomeDetailPlaceholder(), + RegExp.compile(".*"), messages.cancelButton(), messages.confirmButton(), false, true, + new ActionNoAsyncCallback(callback) { + @Override + public void onFailure(Throwable caught) { + // do nothing + } + + @Override + public void onSuccess(String details) { + DisassociateDisposalHoldRequest request = new DisassociateDisposalHoldRequest(); + request.setClear(false); + request.setDetails(details); + request.setSelectedItems(SelectedItemsUtils.convertToRESTRequest(aips)); + Services services = new Services("Lift disposal hold", "job"); + services.disposalHoldResource(s -> s.disassociateDisposalHold(request, disposalHold.getId())) + .whenComplete((job, throwable) -> { + if (throwable != null) { + callback.onFailure(throwable); + HistoryUtils.newHistory(InternalProcess.RESOLVER); + } else { + Dialogs.showJobRedirectDialog(messages.jobCreatedMessage(), new AsyncCallback() { + + @Override + public void onFailure(Throwable caught) { + Toast.showInfo(messages.runningInBackgroundTitle(), + messages.runningInBackgroundDescription()); + + Timer timer = new Timer() { + @Override + public void run() { + doActionCallbackUpdated(); + } + }; + + timer.schedule(RodaConstants.ACTION_TIMEOUT); + } + + @Override + public void onSuccess(final Void nothing) { + doActionCallbackNone(); + HistoryUtils.newHistory(ShowJob.RESOLVER, job.getId()); + } + }); + } + }); + } + }); + } else { + doActionCallbackNone(); + } + } + }); + } + }); + } + + @Override + public ActionableBundle createActionsBundle() { + ActionableBundle disposalHoldActionableBundle = new ActionableBundle<>(); + + ActionableGroup managementGroup = new ActionableGroup<>(messages.sidebarActionsTitle()); + managementGroup.addButton(messages.disassociateDisposalHoldButton(), DisposalHoldAction.DISASSOCIATE, + ActionImpact.UPDATED, "btn-lift-hold"); + + disposalHoldActionableBundle.addGroup(managementGroup); + return disposalHoldActionableBundle; + } + + public enum DisposalHoldAction implements Action { + DISASSOCIATE(RodaConstants.PERMISSION_METHOD_ASSOCIATE_DISPOSAL_HOLD); + + private final List methods; + + DisposalHoldAction(String... methods) { + this.methods = Arrays.asList(methods); + } + + @Override + public List getMethods() { + return this.methods; + } + } + +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/IndexedAIPDisposalScheduleSearchWrapperActions.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/IndexedAIPDisposalScheduleSearchWrapperActions.java new file mode 100644 index 0000000000..4d8d674977 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/IndexedAIPDisposalScheduleSearchWrapperActions.java @@ -0,0 +1,170 @@ +package org.roda.wui.client.common.actions; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.roda.core.data.common.RodaConstants; +import org.roda.core.data.utils.SelectedItemsUtils; +import org.roda.core.data.v2.index.select.SelectedItems; +import org.roda.core.data.v2.ip.IndexedAIP; +import org.roda.wui.client.common.actions.callbacks.ActionNoAsyncCallback; +import org.roda.wui.client.common.actions.model.ActionableBundle; +import org.roda.wui.client.common.actions.model.ActionableGroup; +import org.roda.wui.client.common.actions.model.ActionableObject; +import org.roda.wui.client.common.dialogs.Dialogs; +import org.roda.wui.client.common.lists.utils.ClientSelectedItemsUtils; +import org.roda.wui.client.ingest.process.ShowJob; +import org.roda.wui.client.process.InternalProcess; +import org.roda.wui.client.services.Services; +import org.roda.wui.common.client.tools.HistoryUtils; +import org.roda.wui.common.client.widgets.Toast; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.rpc.AsyncCallback; + +import config.i18n.client.ClientMessages; + +/** + * @author Miguel Guimarães + */ + +public class IndexedAIPDisposalScheduleSearchWrapperActions extends AbstractActionable { + private static final IndexedAIPDisposalScheduleSearchWrapperActions INSTANCE = new IndexedAIPDisposalScheduleSearchWrapperActions(); + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private static final Set POSSIBLE_ACTIONS_ON_DISPOSAL_SCHEDULE = new HashSet<>( + Collections.singletonList(DisposalScheduleAction.DISASSOCIATE)); + + private IndexedAIPDisposalScheduleSearchWrapperActions() { + } + + public static IndexedAIPDisposalScheduleSearchWrapperActions get() { + return INSTANCE; + } + + @Override + public Action[] getActions() { + return DisposalScheduleAction.values(); + } + + @Override + public CanActResult userCanAct(Action action, ActionableObject object) { + return new CanActResult(hasPermissions(action), CanActResult.Reason.USER, messages.reasonUserLacksPermission()); + } + + @Override + public CanActResult contextCanAct(Action action, ActionableObject object) { + if (object.getObject() != null || object.getObjects() != null) { + return new CanActResult(POSSIBLE_ACTIONS_ON_DISPOSAL_SCHEDULE.contains(action), CanActResult.Reason.CONTEXT, + messages.reasonInvalidContext()); + } else { + return new CanActResult(false, CanActResult.Reason.CONTEXT, messages.reasonNoObjectSelected()); + } + } + + @Override + public void act(Action action, IndexedAIP aip, AsyncCallback callback) { + if (DisposalScheduleAction.DISASSOCIATE.equals(action)) { + disassociate(objectToSelectedItems(aip, IndexedAIP.class), callback); + } else { + unsupportedAction(action, callback); + } + } + + @Override + public void act(Action action, SelectedItems items, AsyncCallback callback) { + if (DisposalScheduleAction.DISASSOCIATE.equals(action)) { + disassociate(items, callback); + } else { + unsupportedAction(action, callback); + } + } + + private void disassociate(SelectedItems items, AsyncCallback callback) { + ClientSelectedItemsUtils.size(IndexedAIP.class, items, new ActionNoAsyncCallback(callback) { + @Override + public void onSuccess(final Long size) { + Dialogs.showConfirmDialog(messages.disassociateDisposalScheduleDialogTitle(), + messages.disassociateDisposalScheduleDialogMessage(size.intValue()), messages.dialogNo(), + messages.dialogYes(), new ActionNoAsyncCallback(callback) { + @Override + public void onSuccess(Boolean result) { + if (result) { + Services services = new Services("Disassociate disposal schedule from AIP", "job"); + services + .disposalScheduleResource( + s -> s.disassociatedDisposalSchedule(SelectedItemsUtils.convertToRESTRequest(items))) + .whenComplete((job, throwable) -> { + if (throwable != null) { + callback.onFailure(throwable); + HistoryUtils.newHistory(InternalProcess.RESOLVER); + } else { + Dialogs.showJobRedirectDialog(messages.jobCreatedMessage(), new AsyncCallback() { + + @Override + public void onFailure(Throwable caught) { + Toast.showInfo(messages.runningInBackgroundTitle(), + messages.runningInBackgroundDescription()); + + Timer timer = new Timer() { + @Override + public void run() { + doActionCallbackUpdated(); + } + }; + + timer.schedule(RodaConstants.ACTION_TIMEOUT); + } + + @Override + public void onSuccess(final Void nothing) { + doActionCallbackNone(); + HistoryUtils.newHistory(ShowJob.RESOLVER, job.getId()); + } + }); + } + }); + } else { + doActionCallbackNone(); + } + } + }); + } + }); + } + + @Override + public ActionableBundle createActionsBundle() { + ActionableBundle disposalScheduleActionableBundle = new ActionableBundle<>(); + + ActionableGroup managementGroup = new ActionableGroup<>(messages.sidebarActionsTitle()); + managementGroup.addButton(messages.disassociateDisposalScheduleButton(), DisposalScheduleAction.DISASSOCIATE, + ActionImpact.UPDATED, "fas fa-calendar"); + + disposalScheduleActionableBundle.addGroup(managementGroup); + return disposalScheduleActionableBundle; + } + + @Override + public Action actionForName(String name) { + return null; + } + + public enum DisposalScheduleAction implements Action { + DISASSOCIATE(RodaConstants.PERMISSION_METHOD_ASSOCIATE_DISPOSAL_SCHEDULE); + + private List methods; + + DisposalScheduleAction(String... methods) { + this.methods = Arrays.asList(methods); + } + + @Override + public List getMethods() { + return this.methods; + } + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberToolbarActions.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberToolbarActions.java index baf4b8e973..dcb8f6350c 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberToolbarActions.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberToolbarActions.java @@ -26,7 +26,6 @@ import org.roda.wui.client.common.dialogs.Dialogs; import org.roda.wui.client.common.dialogs.RODAMembersDialogs; import org.roda.wui.client.ingest.process.ShowJob; -import org.roda.wui.client.management.members.MemberManagement; import org.roda.wui.client.management.members.tabs.PermissionsPanel; import org.roda.wui.client.services.Services; import org.roda.wui.common.client.tools.HistoryUtils; @@ -44,9 +43,6 @@ public class RODAMemberToolbarActions extends AbstractActionable { private static final ClientMessages messages = GWT.create(ClientMessages.class); - private static final Set POSSIBLE_ACTIONS_ON_USER = new HashSet<>( - Arrays.asList(RODAMemberAction.ACTIVATE, RODAMemberAction.DEACTIVATE, RODAMemberAction.REMOVE)); - private static final Set POSSIBLE_ACTIONS_ON_GROUP = new HashSet<>( Arrays.asList(RODAMemberAction.EDIT, RODAMemberAction.EDIT_PERMISSIONS, RODAMemberAction.REMOVE, RODAMemberAction.ADD_NEW_MEMBER)); diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RiskActions.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RiskActions.java index e262afebc3..2dbcdee485 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RiskActions.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RiskActions.java @@ -124,7 +124,6 @@ public CanActResult contextCanAct(Action action, IndexedRisk object @Override public CanActResult userCanAct(Action action, SelectedItems objects) { return new CanActResult(hasPermissions(action), CanActResult.Reason.USER, messages.reasonUserLacksPermission()); - } @Override diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/widgets/ActionableWidgetBuilder.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/widgets/ActionableWidgetBuilder.java index a0c6fdb7be..6307f7af9e 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/widgets/ActionableWidgetBuilder.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/widgets/ActionableWidgetBuilder.java @@ -72,7 +72,7 @@ public ActionableWidgetBuilder withActionCallback(AsyncCallback /** * Add a consumer to be called when the actionable widget is generated, the - * integer parameter will have the number of buttons the uer can act on. + * integer parameter will have the number of buttons the user can act on. */ public ActionableWidgetBuilder withWidgetCreatedHandler(Consumer widgetCreatedHandler) { this.widgetCreatedHandler = widgetCreatedHandler; @@ -168,6 +168,13 @@ public FlowPanel buildGroupedListWithObjects(ActionableObject objects, List objects, + List> ungroupedActions) { + ActionableBundle actionableBundle = actionable.createActionsBundle(); + + return createGroupedActionsMenu(actionableBundle, objects, ungroupedActions, true); + } + // Internal (GUI elements creation) private FlowPanel createActionsMenu(ActionableBundle actionableBundle, ActionableObject objects) { @@ -319,7 +326,12 @@ public void onSuccess(Actionable.ActionImpact result) { } private FlowPanel createGroupedActionsMenu(ActionableBundle actionableBundle, ActionableObject objects, - List> ungroupedActions) { + List> ungroupedActions) { + return createGroupedActionsMenu(actionableBundle, objects, ungroupedActions, false); + } + + private FlowPanel createGroupedActionsMenu(ActionableBundle actionableBundle, ActionableObject objects, + List> ungroupedActions, boolean hideGroups) { FlowPanel panel = new FlowPanel(); panel.addStyleName("groupedActionableMenu"); @@ -328,6 +340,7 @@ private FlowPanel createGroupedActionsMenu(ActionableBundle actionableBundle, int addedButtonCount = 0; boolean firstGroup = true; + for (ActionableGroup actionGroup : actionableBundle.getGroups()) { FlowPanel groupPanel = null; FlowPanel buttonsPanel = new FlowPanel(); @@ -369,13 +382,18 @@ public void onClick(ClickEvent event) { if (!firstGroup) { SimplePanel verticalDivider = new SimplePanel(); verticalDivider.addStyleName("verticalDivider"); - panel.add(verticalDivider); + if (!hideGroups) { + panel.add(verticalDivider); + } } else { firstGroup = false; } - panel.add(groupPanel); - groupPanel.add(groupButton); - groupPanel.add(anchorPanel); + + if (!hideGroups) { + panel.add(groupPanel); + groupPanel.add(groupButton); + groupPanel.add(anchorPanel); + } popupPanel.add(buttonsPanel); break; } @@ -413,7 +431,9 @@ public void onSuccess(Actionable.ActionImpact result) { if (!firstGroup) { SimplePanel verticalDivider = new SimplePanel(); verticalDivider.addStyleName("verticalDivider"); - panel.add(verticalDivider); + if (!hideGroups) { + panel.add(verticalDivider); + } } else { firstGroup = false; } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/dialogs/DisposalDialogs.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/dialogs/DisposalDialogs.java index fcf23e6226..cc4585df03 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/dialogs/DisposalDialogs.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/dialogs/DisposalDialogs.java @@ -7,9 +7,17 @@ */ package org.roda.wui.client.common.dialogs; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.user.cellview.client.TextColumn; +import com.google.gwt.user.client.ui.HasVerticalAlignment; +import com.google.gwt.user.client.ui.HorizontalPanel; +import com.google.gwt.user.client.ui.IntegerBox; +import com.google.gwt.user.client.ui.RadioButton; import org.roda.core.data.v2.disposal.hold.DisposalHold; import org.roda.core.data.v2.disposal.hold.DisposalHolds; +import org.roda.core.data.v2.disposal.rule.ChangeOrderRequest; +import org.roda.core.data.v2.disposal.rule.OrderPositions; import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; import org.roda.core.data.v2.disposal.schedule.DisposalSchedules; import org.roda.wui.client.common.dialogs.utils.DisposalHoldDialogResult; @@ -37,14 +45,103 @@ import config.i18n.client.ClientMessages; -; - /** * @author Miguel Guimarães */ public class DisposalDialogs { private static final ClientMessages messages = GWT.create(ClientMessages.class); + public static void reOrderDisposalRules(String title, String message, AsyncCallback callback) { + IntegerBox positionInput; + final DialogBox dialogBox = new DialogBox(false, true); + dialogBox.setHTML(title); + dialogBox.addStyleName("create-group-dialog"); + + final FlowPanel layout = new FlowPanel(); + + dialogBox.addStyleName("wui-dialog-prompt"); + layout.addStyleName("wui-dialog-layout"); + + final FlowPanel buttonPanel = new FlowPanel(); + final Button cancelButton = new Button(messages.cancelButton()); + final Button saveButton = new Button(messages.saveButton()); + buttonPanel.add(cancelButton); + buttonPanel.add(saveButton); + + final FlowPanel content = new FlowPanel(); + content.addStyleName("wui-dialog-content"); + + + HTML messageLabel = new HTML(message); + messageLabel.addStyleName("wui-dialog-message"); + layout.add(messageLabel); + + // Create Radio Buttons in the same group + RadioButton moveTopRadio = new RadioButton("destinationGroup", messages.moveToTop()); + RadioButton moveBottomRadio = new RadioButton("destinationGroup", messages.moveToBottom()); + RadioButton movePosRadio = new RadioButton("destinationGroup", messages.moveToPositionNumber()); + + moveTopRadio.setValue(true); // Default selection + + // Input box for specific position + positionInput = new IntegerBox(); + positionInput.setWidth("50px"); + positionInput.setEnabled(false); // Disabled by default until radio is clicked + + // Handler to toggle the input box based on radio selection + ClickHandler radioClickHandler = event -> positionInput.setEnabled(movePosRadio.getValue()); + moveTopRadio.addClickHandler(radioClickHandler); + moveBottomRadio.addClickHandler(radioClickHandler); + movePosRadio.addClickHandler(radioClickHandler); + + HorizontalPanel posPanel = new HorizontalPanel(); + posPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE); + posPanel.add(movePosRadio); + posPanel.add(positionInput); + + content.add(moveTopRadio); + content.add(moveBottomRadio); + content.add(posPanel); + + layout.add(content); + layout.add(buttonPanel); + dialogBox.setWidget(layout); + + dialogBox.setGlassEnabled(true); + dialogBox.setAnimationEnabled(false); + + cancelButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + dialogBox.hide(); + callback.onFailure(null); + } + }); + + saveButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + ChangeOrderRequest changeOrderRequest = new ChangeOrderRequest(); + dialogBox.hide(); + if (moveTopRadio.getValue()) { + changeOrderRequest.setPosition(OrderPositions.TOP); + } else if (moveBottomRadio.getValue()) { + changeOrderRequest.setPosition(OrderPositions.BOTTOM); + } else { + changeOrderRequest.setPosition(OrderPositions.POSITION); + changeOrderRequest.setNewOrder(positionInput.getValue() != null ? positionInput.getValue() : 1); + } + callback.onSuccess(changeOrderRequest); + } + }); + + cancelButton.addStyleName("btn btn-link"); + saveButton.addStyleName("pull-right btn btn-play"); + + dialogBox.center(); + dialogBox.show(); + } + public static void showDisposalHoldSelection(String title, DisposalHolds holds, final AsyncCallback callback) { final DialogBox dialogBox = new DialogBox(false, true); diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/labels/Tag.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/labels/Tag.java index ac847a8a5e..25e3703154 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/labels/Tag.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/labels/Tag.java @@ -27,7 +27,7 @@ public class Tag extends Composite { public enum TagStyle { DEFAULT, SUCCESS, FAILURE, WARNING_LIGHT, DANGER_LIGHT, NEUTRAL, MONO, ICON_CALENDAR, ICON_LOCK, ICON_CLOCK, - BORDER_BLACK, BORDER_DANGER; + ICON_GAVEL, BORDER_BLACK, BORDER_DANGER; private static String toStyleName(TagStyle tagStyle) { switch (tagStyle) { @@ -45,6 +45,8 @@ private static String toStyleName(TagStyle tagStyle) { return "tagGrey"; case ICON_CALENDAR: return "tagIconCalendar"; + case ICON_GAVEL: + return "tagIconGavel"; case ICON_LOCK: return "tagIconLock"; case ICON_CLOCK: diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/DisposalHoldList.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/DisposalHoldList.java new file mode 100644 index 0000000000..117a389569 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/DisposalHoldList.java @@ -0,0 +1,105 @@ +package org.roda.wui.client.common.lists; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.roda.core.data.common.RodaConstants; +import org.roda.core.data.v2.disposal.hold.DisposalHold; +import org.roda.core.data.v2.index.sort.Sorter; +import org.roda.wui.client.common.lists.utils.AsyncTableCell; +import org.roda.wui.client.common.lists.utils.AsyncTableCellOptions; +import org.roda.wui.client.common.utils.HtmlSnippetUtils; + +import com.google.gwt.cell.client.SafeHtmlCell; +import com.google.gwt.core.client.GWT; +import com.google.gwt.safehtml.shared.SafeHtml; +import com.google.gwt.user.cellview.client.CellTable; +import com.google.gwt.user.cellview.client.Column; +import com.google.gwt.user.cellview.client.ColumnSortList; +import com.google.gwt.user.cellview.client.TextColumn; + +import config.i18n.client.ClientMessages; + +/** + * @author Miguel Guimarães + */ + +public class DisposalHoldList extends AsyncTableCell { + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private static final List fieldsToReturn = Arrays.asList(RodaConstants.INDEX_UUID, + RodaConstants.DISPOSAL_HOLD_TITLE, RodaConstants.DISPOSAL_HOLD_MANDATE, + RodaConstants.DISPOSAL_HOLD_SCOPE_NOTES, RodaConstants.DISPOSAL_HOLD_STATE); + + private TextColumn titleColumn; + private TextColumn mandateColumn; + private TextColumn scopeNotesColumn; + private Column stateColumn; + + @Override + protected void adjustOptions(AsyncTableCellOptions options) { + options.withFieldsToReturn(fieldsToReturn); + } + + @Override + protected void configureDisplay(CellTable display) { + titleColumn = new TextColumn() { + @Override + public String getValue(DisposalHold hold) { + return hold != null ? hold.getTitle() : null; + } + }; + + mandateColumn = new TextColumn() { + @Override + public String getValue(DisposalHold hold) { + return hold != null ? hold.getMandate() : null; + } + }; + + scopeNotesColumn = new TextColumn() { + @Override + public String getValue(DisposalHold hold) { + return hold != null ? hold.getScopeNotes() : null; + } + }; + + stateColumn = new Column(new SafeHtmlCell()) { + @Override + public SafeHtml getValue(DisposalHold hold) { + SafeHtml ret = null; + if (hold != null) { + ret = HtmlSnippetUtils.getDisposalHoldStateHtml(hold); + } + + return ret; + } + }; + + titleColumn.setSortable(true); + mandateColumn.setSortable(true); + scopeNotesColumn.setSortable(true); + stateColumn.setSortable(true); + + addColumn(titleColumn, messages.disposalHoldTitle(), false, false); + addColumn(mandateColumn, messages.disposalHoldMandate(), false, false, 20); + addColumn(scopeNotesColumn, messages.disposalHoldNotes(), false, false, 10); + addColumn(stateColumn, messages.disposalHoldStateCol(), false, false, 9); + + // default sorting + display.getColumnSortList().push(new ColumnSortList.ColumnSortInfo(titleColumn, false)); + } + + @Override + protected Sorter getSorter(ColumnSortList columnSortList) { + Map, List> columnSortingKeyMap = new HashMap<>(); + + columnSortingKeyMap.put(titleColumn, Collections.singletonList(RodaConstants.DISPOSAL_HOLD_TITLE)); + columnSortingKeyMap.put(mandateColumn, Collections.singletonList(RodaConstants.DISPOSAL_HOLD_MANDATE)); + columnSortingKeyMap.put(scopeNotesColumn, Collections.singletonList(RodaConstants.DISPOSAL_HOLD_SCOPE_NOTES)); + columnSortingKeyMap.put(stateColumn, Collections.singletonList(RodaConstants.DISPOSAL_HOLD_STATE)); + return createSorter(columnSortList, columnSortingKeyMap); + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/DisposalRuleList.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/DisposalRuleList.java new file mode 100644 index 0000000000..85eaa08c87 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/DisposalRuleList.java @@ -0,0 +1,106 @@ +package org.roda.wui.client.common.lists; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.roda.core.data.common.RodaConstants; +import org.roda.core.data.v2.disposal.rule.ConditionType; +import org.roda.core.data.v2.disposal.rule.DisposalRule; +import org.roda.core.data.v2.index.sort.Sorter; +import org.roda.wui.client.common.lists.utils.AsyncTableCell; +import org.roda.wui.client.common.lists.utils.AsyncTableCellOptions; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.cellview.client.CellTable; +import com.google.gwt.user.cellview.client.Column; +import com.google.gwt.user.cellview.client.ColumnSortList; +import com.google.gwt.user.cellview.client.TextColumn; + +import config.i18n.client.ClientMessages; + +/** + * @author Miguel Guimarães + */ + +public class DisposalRuleList extends AsyncTableCell { + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private static final List fieldsToReturn = Arrays.asList(RodaConstants.INDEX_UUID, + RodaConstants.DISPOSAL_RULE_ORDER, RodaConstants.DISPOSAL_RULE_TITLE, RodaConstants.DISPOSAL_RULE_SELECTION_METHOD, + RodaConstants.DISPOSAL_RULE_CONDITION_KEY, RodaConstants.DISPOSAL_RULE_CONDITION_VALUE); + + private TextColumn orderColumn; + private TextColumn titleColumn; + private TextColumn selectionMethodColumn; + private TextColumn conditionColumn; + + @Override + protected void adjustOptions(AsyncTableCellOptions options) { + options.withFieldsToReturn(fieldsToReturn); + } + + @Override + protected void configureDisplay(CellTable display) { + orderColumn = new TextColumn() { + @Override + public String getValue(DisposalRule rule) { + int showOrder = rule.getOrder() + 1; + return "" + showOrder; + } + }; + + titleColumn = new TextColumn() { + @Override + public String getValue(DisposalRule rule) { + return rule != null ? rule.getTitle() : null; + } + }; + + selectionMethodColumn = new TextColumn() { + @Override + public String getValue(DisposalRule rule) { + return rule != null ? messages.disposalRuleTypeValue(rule.getType().toString()) : null; + } + }; + + conditionColumn = new TextColumn() { + @Override + public String getValue(DisposalRule rule) { + String condition = ""; + if (rule.getType().equals(ConditionType.METADATA_FIELD)) { + condition = rule.getConditionKey() + " " + messages.disposalRuleConditionOperator() + " " + + rule.getConditionValue(); + } else { + condition = rule.getConditionValue(); + } + return messages.disposalRuleTypeValue(condition); + } + }; + + orderColumn.setSortable(true); + titleColumn.setSortable(true); + selectionMethodColumn.setSortable(true); + conditionColumn.setSortable(false); + + addColumn(orderColumn, messages.disposalRuleOrder(), false, false, 4); + addColumn(titleColumn, messages.disposalRuleTitle(), false, false); + addColumn(selectionMethodColumn, messages.disposalRuleType(), false, false, 20); + addColumn(conditionColumn, messages.disposalRuleCondition(), false, false, 25); + + // default sorting + display.getColumnSortList().push(new ColumnSortList.ColumnSortInfo(orderColumn, true)); + } + + @Override + protected Sorter getSorter(ColumnSortList columnSortList) { + Map, List> columnSortingKeyMap = new HashMap<>(); + + columnSortingKeyMap.put(orderColumn, Collections.singletonList(RodaConstants.DISPOSAL_RULE_ORDER)); + columnSortingKeyMap.put(titleColumn, Collections.singletonList(RodaConstants.DISPOSAL_RULE_TITLE)); + columnSortingKeyMap.put(selectionMethodColumn, + Collections.singletonList(RodaConstants.DISPOSAL_RULE_SELECTION_METHOD)); + return createSorter(columnSortList, columnSortingKeyMap); + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/DisposalScheduleList.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/DisposalScheduleList.java new file mode 100644 index 0000000000..d86f7b6f9b --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/DisposalScheduleList.java @@ -0,0 +1,124 @@ +package org.roda.wui.client.common.lists; + +import com.google.gwt.cell.client.SafeHtmlCell; +import com.google.gwt.core.client.GWT; +import com.google.gwt.safehtml.shared.SafeHtml; +import com.google.gwt.user.cellview.client.CellTable; +import com.google.gwt.user.cellview.client.Column; +import com.google.gwt.user.cellview.client.ColumnSortList; +import com.google.gwt.user.cellview.client.TextColumn; +import config.i18n.client.ClientMessages; +import org.roda.core.data.common.RodaConstants; +import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; +import org.roda.core.data.v2.index.sort.Sorter; +import org.roda.wui.client.common.lists.utils.AsyncTableCell; +import org.roda.wui.client.common.lists.utils.AsyncTableCellOptions; +import org.roda.wui.client.common.utils.HtmlSnippetUtils; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Miguel Guimarães + */ + +public class DisposalScheduleList extends AsyncTableCell { + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private static final List fieldsToReturn = Arrays.asList(RodaConstants.INDEX_UUID, + RodaConstants.DISPOSAL_SCHEDULE_TITLE, RodaConstants.DISPOSAL_SCHEDULE_MANDATE, + RodaConstants.DISPOSAL_SCHEDULE_SCOPE_NOTES, RodaConstants.DISPOSAL_SCHEDULE_STATE, + RodaConstants.DISPOSAL_SCHEDULE_ACTION, RodaConstants.DISPOSAL_SCHEDULE_RETENTION_PERIOD_DURATION, + RodaConstants.DISPOSAL_SCHEDULE_RETENTION_PERIOD_INTERVAL_CODE); + + private TextColumn titleColumn; + private TextColumn mandateColumn; + private TextColumn periodColumn; + private TextColumn actionColumn; + private Column stateColumn; + + @Override + protected void adjustOptions(AsyncTableCellOptions options) { + options.withFieldsToReturn(fieldsToReturn); + } + + @Override + protected void configureDisplay(CellTable display) { + titleColumn = new TextColumn() { + @Override + public String getValue(DisposalSchedule schedule) { + return schedule != null ? schedule.getTitle() : null; + } + }; + + mandateColumn = new TextColumn() { + @Override + public String getValue(DisposalSchedule schedule) { + return schedule != null ? schedule.getMandate() : null; + } + }; + + periodColumn = new TextColumn() { + @Override + public String getValue(DisposalSchedule schedule) { + if (schedule.getRetentionPeriodDuration() == null && schedule.getRetentionPeriodIntervalCode() == null) { + return ""; + } else if (schedule.getRetentionPeriodDuration() == null) { + return messages.retentionPeriod(0, schedule.getRetentionPeriodIntervalCode().toString()); + } else { + return messages.retentionPeriod(schedule.getRetentionPeriodDuration(), + schedule.getRetentionPeriodIntervalCode().toString()); + } + } + }; + + actionColumn = new TextColumn() { + @Override + public String getValue(DisposalSchedule schedule) { + return messages.disposalScheduleAction(schedule.getActionCode().toString()); + } + }; + + stateColumn = new Column(new SafeHtmlCell()) { + @Override + public SafeHtml getValue(DisposalSchedule schedule) { + SafeHtml ret = null; + if (schedule != null) { + ret = HtmlSnippetUtils.getDisposalScheduleStateHtml(schedule); + } + + return ret; + } + }; + + titleColumn.setSortable(true); + mandateColumn.setSortable(true); + periodColumn.setSortable(true); + actionColumn.setSortable(true); + stateColumn.setSortable(true); + + addColumn(titleColumn, messages.disposalScheduleTitle(), false, false); + addColumn(mandateColumn, messages.disposalScheduleMandate(), false, false, 20); + addColumn(periodColumn, messages.disposalSchedulePeriod(), false, false, 15); + addColumn(actionColumn, messages.disposalScheduleActionCol(), false, false, 15); + addColumn(stateColumn, messages.disposalScheduleStateCol(), false, false, 9); + + // default sorting + display.getColumnSortList().push(new ColumnSortList.ColumnSortInfo(titleColumn, false)); + } + + @Override + protected Sorter getSorter(ColumnSortList columnSortList) { + Map, List> columnSortingKeyMap = new HashMap<>(); + + columnSortingKeyMap.put(titleColumn, Collections.singletonList(RodaConstants.DISPOSAL_SCHEDULE_TITLE)); + columnSortingKeyMap.put(mandateColumn, Collections.singletonList(RodaConstants.DISPOSAL_SCHEDULE_MANDATE)); + columnSortingKeyMap.put(periodColumn, + Collections.singletonList(RodaConstants.DISPOSAL_SCHEDULE_RETENTION_PERIOD_DURATION)); + columnSortingKeyMap.put(actionColumn, Collections.singletonList(RodaConstants.DISPOSAL_SCHEDULE_ACTION)); + columnSortingKeyMap.put(stateColumn, Collections.singletonList(RodaConstants.DISPOSAL_SCHEDULE_STATE)); + return createSorter(columnSortList, columnSortingKeyMap); + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/pagination/ListSelectionStateMappers.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/pagination/ListSelectionStateMappers.java index c331a5a186..4d4d4d0cee 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/pagination/ListSelectionStateMappers.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/pagination/ListSelectionStateMappers.java @@ -7,6 +7,9 @@ */ package org.roda.wui.client.common.lists.pagination; +import org.roda.core.data.v2.disposal.hold.DisposalHold; +import org.roda.core.data.v2.disposal.rule.DisposalRule; +import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; import org.roda.core.data.v2.index.IsIndexed; import org.roda.core.data.v2.ip.DIPFile; import org.roda.core.data.v2.ip.IndexedAIP; @@ -21,44 +24,17 @@ public class ListSelectionStateMappers { - // IndexedAIP - public interface IndexedAIPMapper extends ObjectMapper> { - } - + private static final ClientLogger logger = new ClientLogger(ListSelectionUtils.class.getName()); private static IndexedAIPMapper indexedAIPMapper = GWT.create(IndexedAIPMapper.class); - - // IndexedRepresentation - public interface IndexedRepresentationMapper extends ObjectMapper> { - } - private static IndexedRepresentationMapper indexedRepresentationMapper = GWT .create(IndexedRepresentationMapper.class); - - // IndexedFile - public interface IndexedFileMapper extends ObjectMapper> { - } - private static IndexedFileMapper indexedFileMapper = GWT.create(IndexedFileMapper.class); - - // IndexedDIP - public interface IndexedDIPMapper extends ObjectMapper> { - } - private static IndexedDIPMapper indexedDIPMapper = GWT.create(IndexedDIPMapper.class); - - // DIPFile - public interface DIPFileMapper extends ObjectMapper> { - } - private static DIPFileMapper dipFileMapper = GWT.create(DIPFileMapper.class); - - // Job report - public interface IndexedReportMapper extends ObjectMapper> { - } - private static IndexedReportMapper indexedReportMapper = GWT.create(IndexedReportMapper.class); - - private static final ClientLogger logger = new ClientLogger(ListSelectionUtils.class.getName()); + private static DisposalHoldMapper disposalHoldMapper = GWT.create(DisposalHoldMapper.class); + private static DisposalScheduleMapper disposalScheduleMapper = GWT.create(DisposalScheduleMapper.class); + private static DisposalRuleMapper disposalRuleMapper = GWT.create(DisposalRuleMapper.class); private ListSelectionStateMappers() { // do nothing @@ -79,13 +55,18 @@ public static String getJson(String objectClass, ListSelec ret = dipFileMapper.write((ListSelectionState) object); } else if (IndexedReport.class.getName().equals(objectClass)) { ret = indexedReportMapper.write((ListSelectionState) object); + } else if (DisposalHold.class.getName().equals(objectClass)) { + ret = disposalHoldMapper.write((ListSelectionState) object); + } else if (DisposalSchedule.class.getName().equals(objectClass)) { + ret = disposalScheduleMapper.write((ListSelectionState) object); + } else if (DisposalRule.class.getName().equals(objectClass)) { + ret = disposalRuleMapper.write((ListSelectionState) object); } else { ret = null; } return ret; } - @SuppressWarnings("unchecked") public static ListSelectionState getObject(String objectClass, String json) { ListSelectionState state; if (IndexedAIP.class.getName().equals(objectClass)) { @@ -100,10 +81,52 @@ public static ListSelectionState getObject(String objec state = (ListSelectionState) dipFileMapper.read(json); } else if (IndexedReport.class.getName().equals(objectClass)) { state = (ListSelectionState) indexedReportMapper.read(json); + } else if (DisposalHold.class.getName().equals(objectClass)) { + state = (ListSelectionState) disposalHoldMapper.read(json); + } else if (DisposalSchedule.class.getName().equals(objectClass)) { + state = (ListSelectionState) disposalScheduleMapper.read(json); + } else if (DisposalRule.class.getName().equals(objectClass)) { + state = (ListSelectionState) disposalRuleMapper.read(json); } else { state = null; } return state; } + // IndexedAIP + public interface IndexedAIPMapper extends ObjectMapper> { + } + + // IndexedRepresentation + public interface IndexedRepresentationMapper extends ObjectMapper> { + } + + // IndexedFile + public interface IndexedFileMapper extends ObjectMapper> { + } + + // IndexedDIP + public interface IndexedDIPMapper extends ObjectMapper> { + } + + // DIPFile + public interface DIPFileMapper extends ObjectMapper> { + } + + // Job report + public interface IndexedReportMapper extends ObjectMapper> { + } + + // Disposal hold + public interface DisposalHoldMapper extends ObjectMapper> { + } + + // Disposal schedule + public interface DisposalScheduleMapper extends ObjectMapper> { + } + + // Disposal rule + public interface DisposalRuleMapper extends ObjectMapper> { + } + } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/AsyncTableCellOptions.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/AsyncTableCellOptions.java index 7073dac391..87fc83ca53 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/AsyncTableCellOptions.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/AsyncTableCellOptions.java @@ -121,7 +121,6 @@ public AsyncTableCellOptions withFilter(Filter filter) { } public AsyncTableCellOptions withJustActive(boolean justActive) { - this.justActive = justActive; return this; } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/resources/main.gss b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/resources/main.gss index 92ef24eced..5361f688dc 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/resources/main.gss +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/resources/main.gss @@ -1136,6 +1136,10 @@ button:after { content: "\e3c9"; } +.btn-edit-order:after { + content: "\eb61"; +} + .btn-key:after { content: "\e73c"; } @@ -3565,6 +3569,10 @@ td.datePickerMonth, td.datePickerYear { content: "\f023"; } +.tagIconGavel .gwt-Label:before { + content: "\e90e"; +} + .tagIconClock .gwt-Label:before { @mixin fontawesome(); content: "\e900"; @@ -4979,6 +4987,13 @@ td.datePickerMonth, td.datePickerYear { max-width: 60vw; } +.wui-dialog-layout .wui-dialog-content { + display: flex; + flex-direction: column; + gap: 5px; + margin-bottom: 10px; +} + .gwt-DialogBox.wui-dialog-confirm .wui-dialog-layout, .gwt-DialogBox.wui-dialog-information .wui-dialog-layout, .gwt-DialogBox.wui-dialog-prompt .wui-dialog-layout { @@ -9741,8 +9756,6 @@ wui-disposal-hold-data .form-textbox { font-size: 13px; } - - /* Container and Search */ .permissions-dashboard { width: 100%; @@ -9854,33 +9867,3 @@ wui-disposal-hold-data .form-textbox { user-select: none; } -/* Add max-height so the dialog knows when to stop growing and start scrolling */ -.edit-permissions-dialog .wui-dialog-layout, -.edit-user-information-dialog .wui-dialog-layout, -.create-user-dialog .wui-dialog-layout { - width: 80vw; - max-width: 80vw !important; - max-height: 85vh; /* ADD THIS: Keeps the dialog within the monitor height */ - - /* Use flexbox to ensure internal scrolling works correctly */ - display: flex; - flex-direction: column; -} - -/* This will now work because we added content.addStyleName("content") in Java */ -.edit-permissions-dialog .wui-dialog-layout > .content, -.edit-user-information-dialog .wui-dialog-layout > .content, -.create-user-dialog .wui-dialog-layout > .content { - flex: 1; - overflow-y: auto; - min-height: 0; - padding-bottom: 20px; -} - -/* Update this selector to target the class we added, instead of GWT's FlowPanel object name */ -.edit-permissions-dialog .wui-dialog-layout > .dialog-button-panel, -.edit-user-information-dialog .wui-dialog-layout > .dialog-button-panel, -.create-user-dialog .wui-dialog-layout > .dialog-button-panel { - flex-shrink: 0; - margin-top: 15px; -} \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/utils/FormUtilities.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/utils/FormUtilities.java index 5266fa556f..88a23fc806 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/utils/FormUtilities.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/utils/FormUtilities.java @@ -13,6 +13,8 @@ import java.util.Set; import java.util.concurrent.Callable; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.user.client.ui.InlineHTML; import org.roda.core.data.v2.generics.MetadataValue; import org.roda.wui.client.common.RichTextToolbar; @@ -598,6 +600,21 @@ public static void addIfNotBlank(FlowPanel panel, String label, String value) { } } + public static void addIfNotBlank(FlowPanel panel, String label, SafeHtml value) { + if (StringUtils.isNotBlank(value.asString())) { + panel.add(buildField(label, new InlineHTML(value))); + } + } + + public static void addIfNotBlank(FlowPanel panel, String label, String value, String styles, ClickHandler clickHandler) { + if (StringUtils.isNotBlank(value)) { + InlineHTML inlineHTML = new InlineHTML(SafeHtmlUtils.htmlEscape(value)); + inlineHTML.addStyleName(styles); + inlineHTML.addClickHandler(clickHandler); + panel.add(buildField(label, inlineHTML)); + } + } + public static FlowPanel buildSeparator(String text) { FlowPanel separator = new FlowPanel(); separator.addStyleName("form-separator"); diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/utils/HtmlSnippetUtils.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/utils/HtmlSnippetUtils.java index 752b1ad321..b8acef57cc 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/utils/HtmlSnippetUtils.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/utils/HtmlSnippetUtils.java @@ -17,6 +17,7 @@ import org.roda.core.data.v2.disposal.hold.DisposalHold; import org.roda.core.data.v2.disposal.hold.DisposalHoldAssociation; import org.roda.core.data.v2.disposal.hold.DisposalHoldState; +import org.roda.core.data.v2.disposal.rule.DisposalRule; import org.roda.core.data.v2.disposal.schedule.DisposalActionCode; import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; import org.roda.core.data.v2.generics.MetadataValue; @@ -71,7 +72,6 @@ import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.UIObject; import config.i18n.client.ClientMessages; @@ -152,6 +152,18 @@ public static SafeHtml getDisposalScheduleStateHtml(DisposalSchedule disposalSch return ret; } + public static SafeHtml getDisposalRuleTypeHtml(DisposalRule disposalRule) { + SafeHtml ret = null; + if (disposalRule != null && disposalRule.getType() != null) { + SafeHtmlBuilder b = new SafeHtmlBuilder(); + b.append(SafeHtmlUtils.fromSafeConstant(OPEN_SPAN_CLASS_LABEL_INFO)); + b.append(SafeHtmlUtils.fromString(messages.disposalRuleTypeValue(disposalRule.getType().toString()))); + b.append(SafeHtmlUtils.fromSafeConstant(CLOSE_SPAN)); + ret = b.toSafeHtml(); + } + return ret; + } + public static SafeHtml getDisposalHoldStateHtml(DisposalHoldAssociation disposalHoldAssociation) { SafeHtmlBuilder b = new SafeHtmlBuilder(); if (disposalHoldAssociation.getLiftedOn() != null) { @@ -723,9 +735,9 @@ public static String getTransferredResourceStateHTML(String state) { switch (state) { case "Deleted": - response = OPEN_SPAN_CLASS_LABEL_DANGER + messages.sipDeleted() + CLOSE_SPAN; + response = OPEN_SPAN_CLASS_LABEL_DANGER + messages.sipDeleted() + CLOSE_SPAN; break; - } + } return response; } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/CreateDisposalHold.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/CreateDisposalHold.java index 531c286a31..11cd1aa2d0 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/CreateDisposalHold.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/CreateDisposalHold.java @@ -9,22 +9,24 @@ import java.util.List; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.FocusPanel; import org.roda.core.data.v2.disposal.hold.DisposalHold; +import org.roda.wui.client.common.CreateOrUpdateDisposalHoldActionsToolbar; +import org.roda.wui.client.common.NavigationToolbar; +import org.roda.wui.client.common.TitlePanel; import org.roda.wui.client.common.UserLogin; +import org.roda.wui.client.common.actions.Actionable; import org.roda.wui.client.common.utils.AsyncCallbackUtils; import org.roda.wui.client.disposal.policy.DisposalPolicy; -import org.roda.wui.client.services.Services; +import org.roda.wui.client.main.BreadcrumbUtils; import org.roda.wui.common.client.HistoryResolver; -import org.roda.wui.common.client.tools.HistoryUtils; import org.roda.wui.common.client.tools.ListUtils; import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.uibinder.client.UiHandler; import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Widget; @@ -58,49 +60,55 @@ public String getHistoryToken() { } }; private static final ClientMessages messages = GWT.create(ClientMessages.class); - private static CreateDisposalHold.MyUiBinder uiBinder = GWT.create(CreateDisposalHold.MyUiBinder.class); + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); + + @UiField + FocusPanel keyboardFocus; + @UiField + NavigationToolbar navigationToolbar; + @UiField + CreateOrUpdateDisposalHoldActionsToolbar actionsToolbar; @UiField - Button buttonApply; + TitlePanel title; @UiField - Button buttonCancel; - @UiField(provided = true) - DisposalHoldDataPanel disposalHoldDataPanel; - private DisposalHold disposalHold; + FlowPanel disposalHoldDataPanel; public CreateDisposalHold(DisposalHold disposalHold) { - this.disposalHold = disposalHold; - this.disposalHoldDataPanel = new DisposalHoldDataPanel(disposalHold, false); - initWidget(uiBinder.createAndBindUi(this)); - } - @UiHandler("buttonApply") - void buttonApplyHandler(ClickEvent e) { - if (disposalHoldDataPanel.isValid()) { - disposalHold = disposalHoldDataPanel.getDisposalHold(); - Services services = new Services("Create disposal hold", "create"); - services.disposalHoldResource(s -> s.createDisposalHold(disposalHold)) - .whenComplete((disposalHold1, throwable) -> { - if (throwable != null) { - AsyncCallbackUtils.defaultFailureTreatment(throwable); - } else { - HistoryUtils.newHistory(DisposalPolicy.RESOLVER); - } - }); - } + // 1. Create the panel and keep a reference + DisposalHoldDataPanel dataPanel = new DisposalHoldDataPanel(disposalHold, false); + disposalHoldDataPanel.add(dataPanel); - } + navigationToolbar.withoutButtons().build(); + navigationToolbar.updateBreadcrumbPath(BreadcrumbUtils.getCreateDisposalHoldBreadcrumbs()); - @UiHandler("buttonCancel") - void buttonCancelHandler(ClickEvent e) { - cancel(); - } + actionsToolbar.setLabel(messages.showDisposalHoldTitle()); + + // 2. Give the toolbar the panel so it can check validity + actionsToolbar.setDataPanel(dataPanel); + actionsToolbar.setIsCreate(true); + + // 3. Pass the shared object + actionsToolbar.setObjectAndBuild(disposalHold, null, new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + AsyncCallbackUtils.defaultFailureTreatment(caught); + } - private void cancel() { - HistoryUtils.newHistory(DisposalPolicy.RESOLVER); + @Override + public void onSuccess(Actionable.ActionImpact result) { + // Redirect user or show success message if result == ActionImpact.UPDATED + } + }); + + title.setText(messages.newDisposalHoldTitle()); + title.setIconClass("DisposalHold"); + + keyboardFocus.setFocus(true); + keyboardFocus.addStyleName("browse"); } interface MyUiBinder extends UiBinder { } - } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/CreateDisposalHold.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/CreateDisposalHold.ui.xml index fb57bb8905..d6ba08021b 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/CreateDisposalHold.ui.xml +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/CreateDisposalHold.ui.xml @@ -1,32 +1,21 @@ - + - + + + - - - - - - + - - - - - - - - - - - - - + + + + - + diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/DisposalHoldDataPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/DisposalHoldDataPanel.java index 65d134fc42..2388892694 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/DisposalHoldDataPanel.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/DisposalHoldDataPanel.java @@ -32,12 +32,13 @@ import com.google.gwt.user.client.ui.Widget; import config.i18n.client.ClientMessages; +import org.roda.wui.client.GenericDataPanel; import org.roda.wui.client.disposal.schedule.DisposalScheduleDataPanel; /** * @author Tiago Fraga */ -public class DisposalHoldDataPanel extends Composite implements HasValueChangeHandlers { +public class DisposalHoldDataPanel extends Composite implements GenericDataPanel, HasValueChangeHandlers { private static final ClientMessages messages = GWT.create(ClientMessages.class); private static DisposalHoldDataPanel.MyUiBinder uiBinder = GWT.create(DisposalHoldDataPanel.MyUiBinder.class); diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/EditDisposalHold.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/EditDisposalHold.java index 4fe94e1467..0b865c12aa 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/EditDisposalHold.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/EditDisposalHold.java @@ -9,24 +9,27 @@ import java.util.List; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.FocusPanel; import org.roda.core.data.v2.disposal.hold.DisposalHold; import org.roda.core.data.v2.disposal.hold.DisposalHoldState; -import org.roda.core.data.v2.ip.disposalhold.UpdateDisposalHoldRequest; +import org.roda.wui.client.common.CreateOrUpdateDisposalHoldActionsToolbar; +import org.roda.wui.client.common.NavigationToolbar; +import org.roda.wui.client.common.TitlePanel; import org.roda.wui.client.common.UserLogin; +import org.roda.wui.client.common.actions.Actionable; import org.roda.wui.client.common.utils.AsyncCallbackUtils; import org.roda.wui.client.disposal.policy.DisposalPolicy; +import org.roda.wui.client.main.BreadcrumbUtils; import org.roda.wui.client.services.Services; import org.roda.wui.common.client.HistoryResolver; import org.roda.wui.common.client.tools.HistoryUtils; import org.roda.wui.common.client.tools.ListUtils; import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.uibinder.client.UiHandler; import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Widget; @@ -49,7 +52,7 @@ public void resolve(List historyTokens, final AsyncCallback call AsyncCallbackUtils.defaultFailureTreatment(throwable); } else { if (DisposalHoldState.LIFTED.equals(hold.getState())) { - HistoryUtils.newHistory(DisposalPolicy.RESOLVER); + HistoryUtils.newHistory(ShowDisposalHold.RESOLVER, hold.getId()); } else { EditDisposalHold panel = new EditDisposalHold(hold); callback.onSuccess(panel); @@ -75,62 +78,53 @@ public String getHistoryToken() { } }; private static final ClientMessages messages = GWT.create(ClientMessages.class); - private static EditDisposalHold instance = null; + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); - private static EditDisposalHold.MyUiBinder uiBinder = GWT.create(EditDisposalHold.MyUiBinder.class); @UiField - Button buttonApply; + FocusPanel keyboardFocus; @UiField - Button buttonCancel; - @UiField(provided = true) - DisposalHoldDataPanel disposalHoldDataPanel; - private DisposalHold disposalHold; + NavigationToolbar navigationToolbar; + @UiField + CreateOrUpdateDisposalHoldActionsToolbar actionsToolbar; + @UiField + TitlePanel title; + @UiField + FlowPanel disposalHoldDataPanel; - public EditDisposalHold() { + public EditDisposalHold(DisposalHold disposalHold) { initWidget(uiBinder.createAndBindUi(this)); - } - public EditDisposalHold(DisposalHold disposalHold) { - this.disposalHold = disposalHold; - this.disposalHoldDataPanel = new DisposalHoldDataPanel(disposalHold, true); + // 1. Create the panel and keep a reference + DisposalHoldDataPanel dataPanel = new DisposalHoldDataPanel(disposalHold, true); + disposalHoldDataPanel.add(dataPanel); - initWidget(uiBinder.createAndBindUi(this)); - } + navigationToolbar.withoutButtons().build(); + navigationToolbar.updateBreadcrumbPath(BreadcrumbUtils.getEditDisposalHoldBreadcrumbs(disposalHold)); - public static EditDisposalHold getInstance() { - if (instance == null) { - instance = new EditDisposalHold(); - } - return instance; - } + actionsToolbar.setLabel(messages.showDisposalHoldTitle()); - @UiHandler("buttonApply") - void buttonApplyHandler(ClickEvent e) { - if (disposalHoldDataPanel.isChanged() && disposalHoldDataPanel.isValid()) { - - DisposalHold disposalHoldUpdated = disposalHoldDataPanel.getDisposalHold(); - disposalHold.setTitle(disposalHoldUpdated.getTitle()); - disposalHold.setMandate(disposalHoldUpdated.getMandate()); - disposalHold.setDescription(disposalHoldUpdated.getDescription()); - disposalHold.setScopeNotes(disposalHoldUpdated.getScopeNotes()); - UpdateDisposalHoldRequest request = new UpdateDisposalHoldRequest(); - request.setDisposalHold(disposalHold); - Services services = new Services("Update disposal hold", "update"); - services.disposalHoldResource(s -> s.updateDisposalHold(request)).whenComplete((hold, throwable) -> { - if (throwable != null) { - AsyncCallbackUtils.defaultFailureTreatment(throwable); - } else { - HistoryUtils.newHistory(ShowDisposalHold.RESOLVER, hold.getId()); - } - }); - } else { - HistoryUtils.newHistory(ShowDisposalHold.RESOLVER, disposalHold.getId()); - } - } + // 2. Give the toolbar the panel so it can check validity + actionsToolbar.setDataPanel(dataPanel); + actionsToolbar.setIsCreate(false); + + // 3. Pass the shared object + actionsToolbar.setObjectAndBuild(disposalHold, null, new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + AsyncCallbackUtils.defaultFailureTreatment(caught); + } + + @Override + public void onSuccess(Actionable.ActionImpact result) { + // Redirect user or show success message if result == ActionImpact.UPDATED + } + }); + + title.setText(messages.editDisposalHoldTitle()); + title.setIconClass("DisposalHold"); - @UiHandler("buttonCancel") - void buttonCancelHandler(ClickEvent e) { - HistoryUtils.newHistory(ShowDisposalHold.RESOLVER, disposalHold.getId()); + keyboardFocus.setFocus(true); + keyboardFocus.addStyleName("browse"); } interface MyUiBinder extends UiBinder { diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/EditDisposalHold.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/EditDisposalHold.ui.xml index b57ff3642d..bad12a2330 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/EditDisposalHold.ui.xml +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/EditDisposalHold.ui.xml @@ -1,32 +1,21 @@ - + - + + + - - - - - - + - - - - - - - - - - - - - + + + + - + diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/ShowDisposalHold.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/ShowDisposalHold.java index 8da13d3901..24505eabcb 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/ShowDisposalHold.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/ShowDisposalHold.java @@ -7,57 +7,32 @@ */ package org.roda.wui.client.disposal.hold; +import java.util.HashMap; import java.util.List; +import java.util.Map; -import org.roda.core.data.common.RodaConstants; -import org.roda.core.data.exceptions.DisposalHoldAlreadyExistsException; +import com.google.gwt.user.client.ui.FocusPanel; import org.roda.core.data.v2.disposal.hold.DisposalHold; -import org.roda.core.data.v2.disposal.hold.DisposalHoldState; -import org.roda.core.data.v2.generics.select.SelectedItemsFilterRequest; -import org.roda.core.data.v2.generics.select.SelectedItemsRequest; -import org.roda.core.data.v2.index.CountRequest; -import org.roda.core.data.v2.index.filter.Filter; -import org.roda.core.data.v2.index.filter.SimpleFilterParameter; -import org.roda.core.data.v2.ip.IndexedAIP; -import org.roda.core.data.v2.ip.disposalhold.DisassociateDisposalHoldRequest; -import org.roda.core.data.v2.ip.disposalhold.UpdateDisposalHoldRequest; +import org.roda.wui.client.browse.tabs.DisposalHoldTabs; +import org.roda.wui.client.common.DisposalHoldActionsToolbar; +import org.roda.wui.client.common.NavigationToolbar; import org.roda.wui.client.common.NoAsyncCallback; import org.roda.wui.client.common.TitlePanel; import org.roda.wui.client.common.UserLogin; import org.roda.wui.client.common.actions.Actionable; -import org.roda.wui.client.common.actions.DisposalHoldActions; -import org.roda.wui.client.common.dialogs.Dialogs; -import org.roda.wui.client.common.lists.utils.AsyncTableCellOptions; -import org.roda.wui.client.common.lists.utils.ConfigurableAsyncTableCell; -import org.roda.wui.client.common.lists.utils.ListBuilder; -import org.roda.wui.client.common.search.SearchWrapper; import org.roda.wui.client.common.utils.AsyncCallbackUtils; -import org.roda.wui.client.common.utils.HtmlSnippetUtils; -import org.roda.wui.client.common.utils.PermissionClientUtils; import org.roda.wui.client.disposal.policy.DisposalPolicy; -import org.roda.wui.client.ingest.process.ShowJob; -import org.roda.wui.client.process.InternalProcess; +import org.roda.wui.client.main.BreadcrumbUtils; import org.roda.wui.client.services.Services; import org.roda.wui.common.client.HistoryResolver; import org.roda.wui.common.client.tools.HistoryUtils; -import org.roda.wui.common.client.tools.Humanize; import org.roda.wui.common.client.tools.ListUtils; -import org.roda.wui.common.client.tools.StringUtils; -import org.roda.wui.common.client.widgets.Toast; import com.google.gwt.core.client.GWT; -import com.google.gwt.regexp.shared.RegExp; -import com.google.gwt.safehtml.shared.SafeHtmlUtils; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.Widget; import config.i18n.client.ClientMessages; @@ -66,13 +41,22 @@ * @author Tiago Fraga */ public class ShowDisposalHold extends Composite { - private static final ClientMessages messages = GWT.create(ClientMessages.class); - private static ShowDisposalHold instance = null; public static final HistoryResolver RESOLVER = new HistoryResolver() { @Override public void resolve(List historyTokens, final AsyncCallback callback) { - getInstance().resolve(historyTokens, callback); + if (historyTokens.size() == 1) { + Services services = new Services("Retrieve disposal hold", "get"); + services.disposalHoldResource(s -> s.retrieveDisposalHold(historyTokens.get(0))) + .whenComplete((hold, throwable) -> { + if (throwable != null) { + AsyncCallbackUtils.defaultFailureTreatment(throwable); + } else { + ShowDisposalHold panel = new ShowDisposalHold(hold); + callback.onSuccess(panel); + } + }); + } } @Override @@ -90,232 +74,85 @@ public String getHistoryToken() { return "disposal_hold"; } }; - private static ShowDisposalHold.MyUiBinder uiBinder = GWT.create(ShowDisposalHold.MyUiBinder.class); - @UiField - Label disposalHoldId; - @UiField - Label dateCreated, dateUpdated; - @UiField - TitlePanel title; - @UiField - Label disposalHoldMandateKey; - @UiField - HTML disposalHoldMandateValue; - @UiField - Label disposalHoldDescriptionKey; - @UiField - HTML disposalHoldDescriptionValue; - @UiField - Label disposalHoldNotesKey; - @UiField - HTML disposalHoldNotesValue; - @UiField - Label disposalHoldStateKey; + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private static ShowDisposalHold instance = null; + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); + @UiField - HTML disposalHoldStateValue; + FocusPanel keyboardFocus; + @UiField - FlowPanel buttonsPanel; + NavigationToolbar navigationToolbar; + @UiField - FlowPanel aipListTitle; + DisposalHoldActionsToolbar actionsToolbar; + // @UiField - SimplePanel aipsListCard; - private DisposalHold disposalHold; - private SearchWrapper aipsSearchWrapper; - - public ShowDisposalHold() { - this.disposalHold = new DisposalHold(); - } - - public ShowDisposalHold(final DisposalHold disposalHold) { - instance = this; - this.disposalHold = disposalHold; - initAipsList(); - - initWidget(uiBinder.createAndBindUi(this)); - initElements(); - initButtons(); - } - - public static ShowDisposalHold getInstance() { - if (instance == null) { - instance = new ShowDisposalHold(); - } - return instance; - } + TitlePanel title; - private void initAipsList() { - DisposalHoldActions disposalHoldActions = new DisposalHoldActions(disposalHold); + @UiField + DisposalHoldTabs browseTab; - AsyncCallback listActionableCallback = new NoAsyncCallback() { - @Override - public void onSuccess(Actionable.ActionImpact impact) { - if (!Actionable.ActionImpact.NONE.equals(impact)) { - refresh(); - } + private Map handlers = new HashMap<>(); + private AsyncCallback handler = new NoAsyncCallback() { + @Override + public void onSuccess(Actionable.ActionImpact result) { + if (handlers.containsKey(result)) { + handlers.get(result).run(); } - }; - - ListBuilder aipsListBuilder = new ListBuilder<>(() -> new ConfigurableAsyncTableCell<>(), - new AsyncTableCellOptions<>(IndexedAIP.class, "ShowDisposalHold_aips") - .withFilter(new Filter(new SimpleFilterParameter(RodaConstants.AIP_DISPOSAL_HOLDS_ID, disposalHold.getId()))) - .withSummary(messages.listOfAIPs()).bindOpener().withActionable(disposalHoldActions) - .withActionableCallback(listActionableCallback)); - - aipsSearchWrapper = new SearchWrapper(false).createListAndSearchPanel(aipsListBuilder); - } - - public void initElements() { - title.setText(disposalHold.getTitle()); - - disposalHoldId.setText(messages.disposalHoldIdentifier() + ": " + disposalHold.getId()); - - if (disposalHold.getCreatedOn() != null && StringUtils.isNotBlank(disposalHold.getCreatedBy())) { - dateCreated.setText( - messages.dateCreated(Humanize.formatDateTime(disposalHold.getCreatedOn()), disposalHold.getCreatedBy())); - } - - if (disposalHold.getUpdatedOn() != null && StringUtils.isNotBlank(disposalHold.getUpdatedBy())) { - dateUpdated.setText( - messages.dateUpdated(Humanize.formatDateTime(disposalHold.getUpdatedOn()), disposalHold.getUpdatedBy())); - } - - disposalHoldDescriptionValue.setHTML(SafeHtmlUtils.fromString(disposalHold.getDescription())); - disposalHoldDescriptionKey.setVisible(StringUtils.isNotBlank(disposalHold.getDescription())); - - disposalHoldMandateValue.setHTML(SafeHtmlUtils.fromString(disposalHold.getMandate())); - disposalHoldMandateKey.setVisible(StringUtils.isNotBlank(disposalHold.getMandate())); - - disposalHoldNotesValue.setHTML(SafeHtmlUtils.fromString(disposalHold.getScopeNotes())); - disposalHoldNotesKey.setVisible(StringUtils.isNotBlank(disposalHold.getScopeNotes())); - - disposalHoldStateValue.setHTML(HtmlSnippetUtils.getDisposalHoldStateHtml(disposalHold)); - - // Records with this hold - - if (disposalHold.getState().equals(DisposalHoldState.ACTIVE) - && PermissionClientUtils.hasPermissions(RodaConstants.PERMISSION_METHOD_FIND_AIP)) { - Label aipTitle = new Label(); - aipTitle.addStyleName("h5"); - aipTitle.setText(messages.disposalHoldListAips()); - aipListTitle.clear(); - aipListTitle.add(aipTitle); - - aipsListCard.setWidget(aipsSearchWrapper); - aipsListCard.setVisible(true); - } else { - aipListTitle.setVisible(false); - aipsListCard.setVisible(false); } + }; + private DisposalHold disposalHold; - } - - public void initButtons() { - buttonsPanel.clear(); - - if (disposalHold.getState().equals(DisposalHoldState.ACTIVE) - && PermissionClientUtils.hasPermissions(RodaConstants.PERMISSION_METHOD_UPDATE_DISPOSAL_HOLD)) { - Button editHoldBtn = new Button(); - editHoldBtn.addStyleName("btn btn-block btn-edit"); - editHoldBtn.setText(messages.editButton()); - editHoldBtn - .addClickHandler(clickEvent -> HistoryUtils.newHistory(EditDisposalHold.RESOLVER, disposalHold.getId())); - - buttonsPanel.add(editHoldBtn); + public ShowDisposalHold(final DisposalHold hold) { + this.disposalHold = hold; - Button liftHoldBtn = new Button(); - liftHoldBtn.addStyleName("btn btn-block btn-danger btn-lift-hold"); - liftHoldBtn.setText(messages.liftButton()); + initWidget(uiBinder.createAndBindUi(this)); - liftHoldBtn.addClickHandler(clickEvent -> { - Dialogs.showConfirmDialog(messages.liftDisposalHoldDialogTitle(), messages.liftDisposalHoldDialogMessage(1), - messages.cancelButton(), messages.confirmButton(), new AsyncCallback() { - @Override - public void onFailure(Throwable throwable) { - // do nothing - } + initHandlers(hold); - @Override - public void onSuccess(Boolean confirm) { - if (confirm) { - Dialogs.showPromptDialog(messages.outcomeDetailTitle(), null, null, messages.outcomeDetailPlaceholder(), - RegExp.compile(".*"), messages.cancelButton(), messages.confirmButton(), false, true, - new AsyncCallback() { - @Override - public void onFailure(Throwable throwable) { - // do nothing - } + navigationToolbar.withObject(disposalHold); + navigationToolbar.updateBreadcrumbPath(BreadcrumbUtils.getDisposalHoldBreadcrumbs(hold)); + navigationToolbar.build(); - @Override - public void onSuccess(String details) { - Services services = new Services("Lift disposal hold", "job"); + actionsToolbar.setLabel(messages.showDisposalHoldTitle()); + actionsToolbar.setObjectAndBuild(hold, null, handler); - services.disposalHoldResource(s -> s.liftDisposalHold(disposalHold.getId(), details)) - .whenComplete((job, throwable) -> { - if (throwable != null) { - AsyncCallbackUtils.defaultFailureTreatment(throwable); - } else { - Toast.showInfo(messages.runningInBackgroundTitle(), messages.updateDisposalHoldMessage()); - Timer timer = new Timer() { - @Override - public void run() { - refresh(); - } - }; + title.setText(hold.getTitle()); + title.setIconClass("DisposalHold"); - timer.schedule(RodaConstants.ACTION_TIMEOUT); - } - }); - } - }); - } - } - }); - }); - buttonsPanel.add(liftHoldBtn); - } - - Button backBtn = new Button(); - backBtn.setText(messages.backButton()); - backBtn.addStyleName("btn btn-block btn-default btn-times-circle"); - backBtn.addClickHandler(clickEvent -> HistoryUtils.newHistory(DisposalPolicy.RESOLVER)); - buttonsPanel.add(backBtn); - } + keyboardFocus.setFocus(true); + keyboardFocus.addStyleName("browse"); - private void errorMessage(Throwable caught) { - if (caught instanceof DisposalHoldAlreadyExistsException) { - Toast.showError(messages.createDisposalHoldAlreadyExists(disposalHold.getTitle())); - } else { - Toast.showError(messages.createDisposalHoldFailure(caught.getMessage())); - } + browseTab.init(hold, handler); } - private void refresh() { - Services services = new Services("Retrieve disposal hold", "get"); - services.disposalHoldResource(s -> s.retrieveDisposalHold(disposalHold.getId())).whenComplete((hold, throwable) -> { - if (throwable != null) { - AsyncCallbackUtils.defaultFailureTreatment(throwable); - } else { - disposalHold = hold; - initElements(); - initAipsList(); - initButtons(); - } - }); - } + private void initHandlers(DisposalHold hold) { + handlers.put(Actionable.ActionImpact.DESTROYED, + () -> HistoryUtils.newHistory(DisposalPolicy.RESOLVER.getHistoryPath())); - private void resolve(List historyTokens, final AsyncCallback callback) { - if (historyTokens.size() == 1) { - Services services = new Services("Retrieve disposal hold", "get"); - services.disposalHoldResource(s -> s.retrieveDisposalHold(historyTokens.get(0))) - .whenComplete((hold, throwable) -> { + // Use the DOM Swap refresh method instead of HistoryUtils + handlers.put(Actionable.ActionImpact.UPDATED, () -> { + Services services = new Services("Retrieve updated disposal hold", "get"); + services.disposalHoldResource(s -> s.retrieveDisposalHold(hold.getId())) + .whenComplete((updatedHold, throwable) -> { if (throwable != null) { AsyncCallbackUtils.defaultFailureTreatment(throwable); } else { - ShowDisposalHold panel = new ShowDisposalHold(hold); - callback.onSuccess(panel); + // 1. Update local reference + this.disposalHold = updatedHold; + + // 2. Update the main TitlePanel + title.setText(updatedHold.getTitle()); + + navigationToolbar.updateBreadcrumbPath(BreadcrumbUtils.getDisposalHoldBreadcrumbs(updatedHold)); + + // 4. Re-initialize tabs (this recreates the details panel with the new + // schedule) + browseTab.init(updatedHold, handler); } }); - } + }); } interface MyUiBinder extends UiBinder { diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/ShowDisposalHold.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/ShowDisposalHold.ui.xml index 9bd1b5dbc9..0d3ebd5911 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/ShowDisposalHold.ui.xml +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/ShowDisposalHold.ui.xml @@ -1,69 +1,23 @@ - + - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - - + + \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/tabs/DisposalHoldDetailsPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/tabs/DisposalHoldDetailsPanel.java new file mode 100644 index 0000000000..4fc0f4c12e --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/tabs/DisposalHoldDetailsPanel.java @@ -0,0 +1,117 @@ +package org.roda.wui.client.disposal.hold.tabs; + +import java.util.List; + +import org.roda.core.data.v2.disposal.hold.DisposalHold; +import org.roda.wui.client.common.ActionsToolbar; +import org.roda.wui.client.common.actions.Actionable; +import org.roda.wui.client.common.actions.DisposalHoldAction; +import org.roda.wui.client.common.actions.DisposalHoldToolbarActions; +import org.roda.wui.client.common.actions.model.ActionableObject; +import org.roda.wui.client.common.actions.widgets.ActionableWidgetBuilder; +import org.roda.wui.client.common.utils.FormUtilities; +import org.roda.wui.client.common.utils.HtmlSnippetUtils; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.Widget; + +import config.i18n.client.ClientMessages; +import org.roda.wui.common.client.tools.Humanize; + +/** + * @author Miguel Guimarães + */ + +public class DisposalHoldDetailsPanel extends Composite { + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); + + @UiField + ActionsToolbar actionsToolbar; + + @UiField + FlowPanel detailsPanel; + + private DisposalHold hold; + private AsyncCallback localCallback; + + public DisposalHoldDetailsPanel(DisposalHold disposalHold, AsyncCallback actionCallback) { + initWidget(uiBinder.createAndBindUi(this)); + + // Promote localCallback to an instance variable so refresh() can use it + this.localCallback = new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + actionCallback.onFailure(caught); + } + + @Override + public void onSuccess(Actionable.ActionImpact result) { + if (Actionable.ActionImpact.UPDATED.equals(result)) { + actionCallback.onSuccess(result); + } + } + }; + + actionsToolbar.setLabelVisible(false); + actionsToolbar.setTagsVisible(false); + + // Initial load + refresh(disposalHold); + } + + // Update the method signature to accept the new hold + public void refresh(DisposalHold newHold) { + this.hold = newHold; + + // 1. Clear out the old details + clear(); + + // 2. Re-populate text fields with the new data + init(this.hold); + + // 3. Re-bind the actions toolbar with the new hold object + actionsToolbar.setActionableMenu(new ActionableWidgetBuilder(DisposalHoldToolbarActions.get()) + .withActionCallback(localCallback).buildGroupedListWithObjects(new ActionableObject<>(hold), + List.of(DisposalHoldAction.EDIT), List.of(DisposalHoldAction.EDIT)), + true); + } + + private void init(DisposalHold hold) { + FormUtilities.addIfNotBlank(detailsPanel, messages.disposalHoldTitle(), hold.getTitle()); + FormUtilities.addIfNotBlank(detailsPanel, messages.disposalHoldDescription(), hold.getDescription()); + FormUtilities.addIfNotBlank(detailsPanel, messages.disposalHoldMandate(), hold.getMandate()); + FormUtilities.addIfNotBlank(detailsPanel, messages.disposalHoldNotes(), hold.getScopeNotes()); + + FlowPanel topPanel = new FlowPanel(); + FlowPanel status = new FlowPanel(); + Label statusLabel = new Label(); + statusLabel.addStyleName("label"); + statusLabel.setText(messages.showUserStatusLabel()); + status.add(statusLabel); + HTML statusValue = new HTML(); + statusValue.addStyleName("value"); + statusValue.setHTML(HtmlSnippetUtils.getDisposalHoldStateHtml(hold)); + status.add(statusValue); + status.addStyleName("field"); + topPanel.addStyleName("descriptiveMetadata"); + topPanel.add(status); + detailsPanel.add(topPanel); + } + + public void clear() { + detailsPanel.clear(); + } + + interface MyUiBinder extends UiBinder { + Widget createAndBindUi(DisposalHoldDetailsPanel detailsPanel); + } + +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/tabs/DisposalHoldDetailsPanel.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/tabs/DisposalHoldDetailsPanel.ui.xml new file mode 100644 index 0000000000..ec66c371e8 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/tabs/DisposalHoldDetailsPanel.ui.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/policy/DisposalPolicy.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/policy/DisposalPolicy.java index 4bf411962d..95786ac4f6 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/policy/DisposalPolicy.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/policy/DisposalPolicy.java @@ -9,11 +9,8 @@ import java.util.List; -import org.roda.core.data.common.RodaConstants; +import org.roda.wui.client.browse.tabs.DisposalPolicyTabs; import org.roda.wui.client.common.UserLogin; -import org.roda.wui.client.common.actions.DisposalRuleActions; -import org.roda.wui.client.common.utils.PermissionClientUtils; -import org.roda.wui.client.common.utils.SidebarUtils; import org.roda.wui.client.disposal.Disposal; import org.roda.wui.client.disposal.hold.CreateDisposalHold; import org.roda.wui.client.disposal.hold.EditDisposalHold; @@ -31,24 +28,19 @@ import org.roda.wui.common.client.widgets.HTMLWidgetWrapper; import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Widget; -import config.i18n.client.ClientMessages; - /** * @author Tiago Fraga */ public class DisposalPolicy extends Composite { + private static DisposalPolicy instance = null; public static final HistoryResolver RESOLVER = new HistoryResolver() { @Override public void resolve(List historyTokens, AsyncCallback callback) { @@ -70,15 +62,17 @@ public String getHistoryToken() { return "policy"; } }; - - private static DisposalPolicy instance = null; - - interface MyUiBinder extends UiBinder { - } - private static DisposalPolicy.MyUiBinder uiBinder = GWT.create(DisposalPolicy.MyUiBinder.class); + @UiField + FlowPanel disposalPolicyDescription; + @UiField + DisposalPolicyTabs browseTab; - private static final ClientMessages messages = GWT.create(ClientMessages.class); + private DisposalPolicy() { + initWidget(uiBinder.createAndBindUi(this)); + disposalPolicyDescription.add(new HTMLWidgetWrapper("DisposalPolicyDescription.html")); + browseTab.init(); + } /** * Get the singleton instance @@ -92,181 +86,6 @@ public static DisposalPolicy getInstance() { return instance; } - @UiField - FlowPanel disposalPolicyDescription; - - @UiField - FlowPanel contentFlowPanel; - - @UiField - FlowPanel sidebarFlowPanel; - - @UiField - FlowPanel sidebarButtonsPanel; - - // Disposal Schedules - @UiField(provided = true) - DisposalPolicySchedulesPanel disposalPolicySchedulesPanel; - - // Disposal Holds - @UiField(provided = true) - DisposalPolicyHoldsPanel disposalPolicyHoldsPanel; - - // Disposal Rules - @UiField(provided = true) - DisposalPolicyRulesPanel disposalPolicyRulesPanel; - - private boolean initSidebarButtons(FlowPanel sidebar) { - boolean hasCreatedScheduleBtns = initDisposalScheduleButtons(sidebar, true, true); - boolean hasCreatedRuleBtns = initDisposalRuleButtons(sidebar, true, true); - boolean hasCreatedHoldBtns = initDisposalHoldButtons(sidebar, true, true); - - return hasCreatedRuleBtns || hasCreatedScheduleBtns || hasCreatedHoldBtns; - } - - private boolean initDisposalScheduleButtons(FlowPanel panel, boolean isGroup, boolean isSidebar) { - if (PermissionClientUtils.hasPermissions(RodaConstants.PERMISSION_METHOD_CREATE_DISPOSAL_SCHEDULE)) { - if (isSidebar) { - Label label = new Label(); - label.setText(messages.disposalSchedulesTitle()); - panel.add(label); - } - Button newDisposalScheduleBtn = new Button(); - newDisposalScheduleBtn.addStyleName("btn btn-plus"); - if (isGroup) { - newDisposalScheduleBtn.addStyleName("btn-block"); - } - newDisposalScheduleBtn.setText(messages.newDisposalScheduleTitle()); - newDisposalScheduleBtn.addClickHandler(new ClickHandler() { - - @Override - public void onClick(ClickEvent event) { - HistoryUtils.newHistory(CreateDisposalSchedule.RESOLVER); - } - }); - panel.add(newDisposalScheduleBtn); - return true; - } - return false; - } - - private boolean initDisposalHoldButtons(FlowPanel panel, boolean isGroup, boolean isSidebar) { - if (PermissionClientUtils.hasPermissions(RodaConstants.PERMISSION_METHOD_CREATE_DISPOSAL_HOLD)) { - if (isSidebar) { - Label label = new Label(); - label.setText(messages.disposalHoldsTitle()); - label.addStyleName("sidebarDisposalLabels"); - panel.add(label); - } - Button newDisposalHoldBtn = new Button(); - newDisposalHoldBtn.addStyleName("btn btn-plus"); - if (isGroup) { - newDisposalHoldBtn.addStyleName("btn-block"); - } - newDisposalHoldBtn.setText(messages.newDisposalHoldTitle()); - newDisposalHoldBtn.addClickHandler(new ClickHandler() { - - @Override - public void onClick(ClickEvent event) { - HistoryUtils.newHistory(Disposal.RESOLVER, DisposalPolicy.RESOLVER.getHistoryToken(), - CreateDisposalHold.RESOLVER.getHistoryToken()); - } - }); - panel.add(newDisposalHoldBtn); - return true; - } - return false; - } - - private boolean initDisposalRuleButtons(FlowPanel panel, boolean isGroup, boolean isSidebar) { - boolean ret = false; - - if (PermissionClientUtils.hasPermissions(RodaConstants.PERMISSION_METHOD_CREATE_DISPOSAL_RULE)) { - Button createRuleBtn = new Button(); - if (isSidebar) { - Label label = new Label(); - label.setText(messages.disposalRulesTitle()); - label.addStyleName("sidebarDisposalLabels"); - panel.add(label); - } - createRuleBtn.addStyleName("btn btn-plus"); - if (isGroup) { - createRuleBtn.addStyleName("btn-block"); - } - createRuleBtn.setText(messages.newDisposalRuleTitle()); - createRuleBtn.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - HistoryUtils.newHistory(DisposalPolicy.RESOLVER, CreateDisposalRule.RESOLVER.getHistoryToken()); - } - }); - panel.add(createRuleBtn); - ret = true; - } - - if (PermissionClientUtils.hasPermissions(RodaConstants.PERMISSION_METHOD_UPDATE_DISPOSAL_RULE)) { - if (isSidebar && !ret) { - Label label = new Label(); - label.setText(messages.disposalRulesTitle()); - label.addStyleName("sidebarDisposalLabels"); - panel.add(label); - } - Button editRulesOrderBtn = new Button(); - editRulesOrderBtn.addStyleName("btn btn-edit"); - if (isGroup) { - editRulesOrderBtn.addStyleName("btn-block"); - } - editRulesOrderBtn.setText(messages.editRules()); - editRulesOrderBtn.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - HistoryUtils.newHistory(DisposalPolicy.RESOLVER, OrderDisposalRules.RESOLVER.getHistoryToken()); - } - }); - panel.add(editRulesOrderBtn); - ret = true; - } - - if (PermissionClientUtils.hasPermissions(RodaConstants.PERMISSION_METHOD_ASSOCIATE_DISPOSAL_SCHEDULE)) { - if (!ret && isSidebar) { - Label label = new Label(); - label.setText(messages.disposalRulesTitle()); - label.addStyleName("sidebarDisposalLabels"); - panel.add(label); - } - Button applyRules = new Button(); - applyRules.addStyleName("btn btn-play"); - if (isGroup) { - applyRules.addStyleName("btn-block"); - } - applyRules.setText(messages.applyRules()); - applyRules.addClickHandler(event -> { - DisposalRuleActions.applyDisposalRulesAction(); - }); - panel.add(applyRules); - ret = true; - } - - return ret; - } - - private void initSidebar() { - if (initSidebarButtons(sidebarButtonsPanel)) { - SidebarUtils.showSidebar(contentFlowPanel, sidebarFlowPanel); - } else { - SidebarUtils.hideSidebar(contentFlowPanel, sidebarFlowPanel); - } - } - - private DisposalPolicy() { - this.disposalPolicySchedulesPanel = new DisposalPolicySchedulesPanel(); - this.disposalPolicyHoldsPanel = new DisposalPolicyHoldsPanel(); - this.disposalPolicyRulesPanel = new DisposalPolicyRulesPanel(); - initWidget(uiBinder.createAndBindUi(this)); - initSidebar(); - disposalPolicyDescription.add(new HTMLWidgetWrapper("DisposalPolicyDescription.html")); - } - public void resolve(List historyTokens, AsyncCallback callback) { if (historyTokens.isEmpty()) { DisposalPolicy disposalPolicy = new DisposalPolicy(); @@ -293,4 +112,7 @@ public void resolve(List historyTokens, AsyncCallback callback) OrderDisposalRules.RESOLVER.resolve(HistoryUtils.tail(historyTokens), callback); } } + + interface MyUiBinder extends UiBinder { + } } \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/policy/DisposalPolicy.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/policy/DisposalPolicy.ui.xml index 8283f9da43..96bcff695b 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/policy/DisposalPolicy.ui.xml +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/policy/DisposalPolicy.ui.xml @@ -1,29 +1,19 @@ + xmlns:common="urn:import:org.roda.wui.client.common" + xmlns:tabs="urn:import:org.roda.wui.client.browse.tabs"> - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/ChildOfPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/ChildOfPanel.java index 041ad08ec5..b5da653b0e 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/ChildOfPanel.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/ChildOfPanel.java @@ -160,8 +160,18 @@ public Pair getValue() { private Pair getChildOfFields() { Pair childOfFields = new Pair<>(); + childOfFields.setFirst(pluginParameterPanel.getValue()); childOfFields.setSecond(pluginParameterPanel.getAipTitle()); + + if (pluginParameterPanel.getValue() == null) { + childOfFields.setFirst(aipId); + } + + if (pluginParameterPanel.getAipTitle() == null) { + childOfFields.setSecond(aipName); + } + return childOfFields; } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/CreateDisposalRule.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/CreateDisposalRule.java index af3901dc8b..2bacb3e9d9 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/CreateDisposalRule.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/CreateDisposalRule.java @@ -1,57 +1,58 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE file at the root of the source - * tree and available online at - * - * https://github.com/keeps/roda - */ package org.roda.wui.client.disposal.rule; -import java.util.List; - +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.FocusPanel; +import com.google.gwt.user.client.ui.Widget; +import config.i18n.client.ClientMessages; import org.roda.core.data.v2.disposal.rule.DisposalRule; -import org.roda.core.data.v2.disposal.rule.DisposalRules; import org.roda.core.data.v2.disposal.schedule.DisposalSchedules; +import org.roda.core.data.v2.generics.LongResponse; +import org.roda.core.data.v2.index.CountRequest; +import org.roda.core.data.v2.index.filter.AllFilterParameter; +import org.roda.core.data.v2.index.filter.Filter; +import org.roda.wui.client.common.CreateOrUpdateDisposalRuleActionsToolbar; +import org.roda.wui.client.common.NavigationToolbar; +import org.roda.wui.client.common.TitlePanel; import org.roda.wui.client.common.UserLogin; +import org.roda.wui.client.common.actions.Actionable; import org.roda.wui.client.common.utils.AsyncCallbackUtils; import org.roda.wui.client.disposal.policy.DisposalPolicy; -import org.roda.wui.client.services.DisposalRuleRestService; +import org.roda.wui.client.main.BreadcrumbUtils; import org.roda.wui.client.services.DisposalScheduleRestService; import org.roda.wui.client.services.Services; import org.roda.wui.common.client.HistoryResolver; -import org.roda.wui.common.client.tools.HistoryUtils; import org.roda.wui.common.client.tools.ListUtils; -import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.uibinder.client.UiBinder; -import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.uibinder.client.UiHandler; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.Button; -import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.Widget; - -import config.i18n.client.ClientMessages; +import java.util.List; /** - * @author Tiago Fraga + * @author Miguel Guimarães */ -public class CreateDisposalRule extends Composite { +public class CreateDisposalRule extends Composite { public static final HistoryResolver RESOLVER = new HistoryResolver() { @Override public void resolve(List historyTokens, final AsyncCallback callback) { Services services = new Services("List disposal schedules", "get"); + CountRequest request = new CountRequest(); + Filter filter = new Filter(); + + filter.add(new AllFilterParameter()); + request.setFilter(filter); services.disposalScheduleResource(DisposalScheduleRestService::listDisposalSchedules) - .thenCompose(disposalSchedules -> services.disposalRuleResource(DisposalRuleRestService::listDisposalRules) - .whenComplete((disposalRulesResult, throwable) -> { + .thenCompose(disposalSchedules -> services.disposalRuleResource(s -> s.count(request)) + .whenComplete((disposalRulesCount, throwable) -> { if (throwable != null) { AsyncCallbackUtils.defaultFailureTreatment(throwable); } else { - CreateDisposalRule createDisposalRule = new CreateDisposalRule(new DisposalRule(), disposalSchedules, - disposalRulesResult); + CreateDisposalRule createDisposalRule = new CreateDisposalRule(new DisposalRule(), + disposalSchedules, disposalRulesCount); callback.onSuccess(createDisposalRule); } })); @@ -72,46 +73,58 @@ public String getHistoryToken() { return "create_disposal_rule"; } }; + private static final ClientMessages messages = GWT.create(ClientMessages.class); - private static CreateDisposalRule.MyUiBinder uiBinder = GWT.create(CreateDisposalRule.MyUiBinder.class); + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); + + @UiField + FocusPanel keyboardFocus; + @UiField + NavigationToolbar navigationToolbar; @UiField - Button buttonSave; + CreateOrUpdateDisposalRuleActionsToolbar actionsToolbar; @UiField - Button buttonCancel; - @UiField(provided = true) - DisposalRuleDataPanel disposalRuleDataPanel; - private DisposalRule disposalRule; - private DisposalRules disposalRules; + TitlePanel title; + @UiField + FlowPanel disposalRuleDataPanel; public CreateDisposalRule(DisposalRule disposalRule, DisposalSchedules disposalSchedules, - DisposalRules disposalRules) { - this.disposalRule = disposalRule; - this.disposalRules = disposalRules; - - this.disposalRuleDataPanel = new DisposalRuleDataPanel(disposalRule, disposalSchedules, false); + LongResponse disposalRulesCount) { initWidget(uiBinder.createAndBindUi(this)); - } + disposalRule.setOrder(disposalRulesCount.getResult().intValue()); - @UiHandler("buttonSave") - void buttonApplyHandler(ClickEvent e) { - if (disposalRuleDataPanel.isValid()) { - disposalRule = disposalRuleDataPanel.getDisposalRule(); - disposalRule.setOrder(disposalRules.getObjects().size()); - Services services = new Services("Create disposal rule", "create"); - services.disposalRuleResource(s -> s.createDisposalRule(disposalRule)).whenComplete((result, throwable) -> { - if (throwable != null) { - AsyncCallbackUtils.defaultFailureTreatment(throwable); - } else { - HistoryUtils.newHistory(DisposalPolicy.RESOLVER); - } - }); - } - } + // 1. Create the panel and keep a reference + DisposalRuleDataPanel dataPanel = new DisposalRuleDataPanel(disposalRule, disposalSchedules, false); + disposalRuleDataPanel.add(dataPanel); + + navigationToolbar.withoutButtons().build(); + navigationToolbar.updateBreadcrumbPath(BreadcrumbUtils.getCreateDisposalRuleBreadcrumbs()); + + actionsToolbar.setLabel(messages.showDisposalRuleTitle()); + + // 2. Give the toolbar the panel so it can check validity + actionsToolbar.setDataPanel(dataPanel); + actionsToolbar.setIsCreate(true); + + // 3. Pass the shared object + actionsToolbar.setObjectAndBuild(disposalRule, null, new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + AsyncCallbackUtils.defaultFailureTreatment(caught); + } + + @Override + public void onSuccess(Actionable.ActionImpact result) { + // Redirect user or show success message if result == ActionImpact.UPDATED + } + }); + + title.setText(messages.newDisposalRuleTitle()); + title.setIconClass("DisposalRule"); - @UiHandler("buttonCancel") - void buttonCancelHandler(ClickEvent e) { - HistoryUtils.newHistory(DisposalPolicy.RESOLVER); + keyboardFocus.setFocus(true); + keyboardFocus.addStyleName("browse"); } interface MyUiBinder extends UiBinder { diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/CreateDisposalRule.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/CreateDisposalRule.ui.xml index a549e1a800..666a8d6d95 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/CreateDisposalRule.ui.xml +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/CreateDisposalRule.ui.xml @@ -1,33 +1,21 @@ - + - + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/DisposalRuleDataPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/DisposalRuleDataPanel.java index c70b352bb1..58aa1412ba 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/DisposalRuleDataPanel.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/DisposalRuleDataPanel.java @@ -22,6 +22,7 @@ import org.roda.core.data.v2.index.filter.SimpleFilterParameter; import org.roda.core.data.v2.ip.AIPState; import org.roda.core.data.v2.ip.IndexedAIP; +import org.roda.wui.client.GenericDataPanel; import org.roda.wui.client.common.lists.utils.AsyncTableCellOptions; import org.roda.wui.client.common.lists.utils.ConfigurableAsyncTableCell; import org.roda.wui.client.common.lists.utils.ListBuilder; @@ -53,7 +54,7 @@ /** * @author Tiago Fraga */ -public class DisposalRuleDataPanel extends Composite implements HasValueChangeHandlers { +public class DisposalRuleDataPanel extends Composite implements GenericDataPanel, HasValueChangeHandlers { private static final ClientMessages messages = GWT.create(ClientMessages.class); private static DisposalRuleDataPanel.MyUiBinder uiBinder = GWT.create(DisposalRuleDataPanel.MyUiBinder.class); @@ -301,7 +302,7 @@ public DisposalRule getDisposalRule() { public boolean isValid() { List errorList = new ArrayList<>(); - if (title.getText().length() == 0) { + if (title.getText().isEmpty()) { title.addStyleName("isWrong"); titleError.setText(messages.mandatoryField()); titleError.setVisible(true); @@ -312,7 +313,7 @@ public boolean isValid() { titleError.setVisible(false); } - if (disposalSchedulesList.getSelectedValue().length() == 0) { + if (disposalSchedulesList.getSelectedValue().isEmpty()) { disposalSchedulesList.addStyleName("isWrong"); disposalSchedulesListError.setText(messages.mandatoryField()); disposalSchedulesListError.setVisible(true); @@ -323,7 +324,7 @@ public boolean isValid() { disposalSchedulesListError.setVisible(false); } - if (conditionTypeList.getSelectedValue().length() == 0) { + if (conditionTypeList.getSelectedValue().isEmpty()) { conditionTypeList.addStyleName("isWrong"); conditionTypeListError.setText(messages.mandatoryField()); conditionTypeListError.setVisible(true); diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/DisposalRuleDataPanel.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/DisposalRuleDataPanel.ui.xml index eb73192ba9..8f2644dbff 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/DisposalRuleDataPanel.ui.xml +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/DisposalRuleDataPanel.ui.xml @@ -58,8 +58,7 @@ - + diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/EditDisposalRule.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/EditDisposalRule.java index 6b7d23a218..6f8aec41b3 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/EditDisposalRule.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/EditDisposalRule.java @@ -9,27 +9,27 @@ import java.util.List; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.FocusPanel; import org.roda.core.data.v2.disposal.rule.DisposalRule; import org.roda.core.data.v2.disposal.schedule.DisposalSchedules; -import org.roda.wui.client.common.NoAsyncCallback; +import org.roda.wui.client.common.CreateOrUpdateDisposalRuleActionsToolbar; +import org.roda.wui.client.common.NavigationToolbar; +import org.roda.wui.client.common.TitlePanel; import org.roda.wui.client.common.UserLogin; -import org.roda.wui.client.common.actions.DisposalRuleActions; -import org.roda.wui.client.common.dialogs.Dialogs; +import org.roda.wui.client.common.actions.Actionable; import org.roda.wui.client.common.utils.AsyncCallbackUtils; import org.roda.wui.client.disposal.policy.DisposalPolicy; +import org.roda.wui.client.main.BreadcrumbUtils; import org.roda.wui.client.services.DisposalScheduleRestService; import org.roda.wui.client.services.Services; import org.roda.wui.common.client.HistoryResolver; -import org.roda.wui.common.client.tools.HistoryUtils; import org.roda.wui.common.client.tools.ListUtils; import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.uibinder.client.UiHandler; import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Widget; @@ -45,7 +45,6 @@ public class EditDisposalRule extends Composite { @Override public void resolve(List historyTokens, final AsyncCallback callback) { if (historyTokens.size() == 1) { - Services services = new Services("List disposal schedules", "get"); services.disposalRuleResource(s -> s.retrieveDisposalRule(historyTokens.get(0))) .thenCompose(rule -> services.disposalScheduleResource(DisposalScheduleRestService::listDisposalSchedules) @@ -75,106 +74,56 @@ public String getHistoryToken() { return "edit_disposal_rule"; } }; + private static final ClientMessages messages = GWT.create(ClientMessages.class); - private static EditDisposalRule instance = null; + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); - private static EditDisposalRule.MyUiBinder uiBinder = GWT.create(EditDisposalRule.MyUiBinder.class); @UiField - Button buttonApply; + FocusPanel keyboardFocus; @UiField - Button buttonCancel; - @UiField(provided = true) - DisposalRuleDataPanel disposalRuleDataPanel; - private DisposalRule disposalRule; - - public EditDisposalRule() { - initWidget(uiBinder.createAndBindUi(this)); - } + NavigationToolbar navigationToolbar; + @UiField + CreateOrUpdateDisposalRuleActionsToolbar actionsToolbar; + @UiField + TitlePanel title; + @UiField + FlowPanel disposalRuleDataPanel; public EditDisposalRule(DisposalRule disposalRule, DisposalSchedules disposalSchedules) { - this.disposalRule = disposalRule; - this.disposalRuleDataPanel = new DisposalRuleDataPanel(disposalRule, disposalSchedules, true); initWidget(uiBinder.createAndBindUi(this)); - } - public static EditDisposalRule getInstance() { - if (instance == null) { - instance = new EditDisposalRule(); - } - return instance; - } + // 1. Create the panel and keep a reference + DisposalRuleDataPanel dataPanel = new DisposalRuleDataPanel(disposalRule, disposalSchedules, true); + disposalRuleDataPanel.add(dataPanel); - @UiHandler("buttonApply") - void buttonApplyHandler(ClickEvent e) { - if (disposalRuleDataPanel.isChanged() && disposalRuleDataPanel.isValid()) { - boolean runApplyRulesPlugin = false; - DisposalRule disposalRuleUpdated = disposalRuleDataPanel.getDisposalRule(); - disposalRule.setTitle(disposalRuleUpdated.getTitle()); - disposalRule.setDescription(disposalRuleUpdated.getDescription()); - - if (!disposalRule.getDisposalScheduleId().equals(disposalRuleUpdated.getDisposalScheduleId()) - && !disposalRule.getDisposalScheduleName().equals(disposalRuleUpdated.getDisposalScheduleName())) { - runApplyRulesPlugin = true; - } - disposalRule.setDisposalScheduleId(disposalRuleUpdated.getDisposalScheduleId()); - disposalRule.setDisposalScheduleName(disposalRuleUpdated.getDisposalScheduleName()); + navigationToolbar.withoutButtons().build(); + navigationToolbar.updateBreadcrumbPath(BreadcrumbUtils.getEditDisposalRuleBreadcrumbs(disposalRule)); - if (!disposalRule.getType().equals(disposalRuleUpdated.getType())) { - runApplyRulesPlugin = true; - } - disposalRule.setType(disposalRuleUpdated.getType()); + actionsToolbar.setLabel(messages.showDisposalRuleTitle()); - if (disposalRuleUpdated.getConditionKey() != null) { - if (!disposalRule.getConditionKey().equals(disposalRuleUpdated.getConditionKey())) { - runApplyRulesPlugin = true; - } - disposalRule.setConditionKey(disposalRuleUpdated.getConditionKey()); - } + // 2. Give the toolbar the panel so it can check validity + actionsToolbar.setDataPanel(dataPanel); + actionsToolbar.setIsCreate(false); - if (disposalRuleUpdated.getConditionValue() != null) { - if (!disposalRule.getConditionValue().equals(disposalRuleUpdated.getConditionValue())) { - runApplyRulesPlugin = true; - } - disposalRule.setConditionValue(disposalRuleUpdated.getConditionValue()); + // 3. Pass the shared object + actionsToolbar.setObjectAndBuild(disposalRule, null, new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + AsyncCallbackUtils.defaultFailureTreatment(caught); } - if (!runApplyRulesPlugin) { - Services services = new Services("Update disposal rule", "update"); - services.disposalRuleResource(s -> s.updateDisposalRule(disposalRule)).whenComplete((result, throwable) -> { - if (throwable != null) { - AsyncCallbackUtils.defaultFailureTreatment(throwable); - } else { - HistoryUtils.newHistory(ShowDisposalRule.RESOLVER, result.getId()); - } - }); - } else { - Dialogs.showConfirmDialog(messages.saveButton(), messages.confirmEditRuleMessage(), messages.dialogNo(), - messages.dialogYes(), new NoAsyncCallback() { - @Override - public void onSuccess(Boolean confirm) { - if (confirm) { - Services services = new Services("Update disposal rule", "update"); - services.disposalRuleResource(s -> s.updateDisposalRule(disposalRule)) - .whenComplete((result, throwable) -> { - if (throwable != null) { - AsyncCallbackUtils.defaultFailureTreatment(throwable); - } else { - DisposalRuleActions.applyDisposalRulesAction(); - } - }); - } - } - }); + @Override + public void onSuccess(Actionable.ActionImpact result) { + // Redirect user or show success message if result == ActionImpact.UPDATED } - } else { - HistoryUtils.newHistory(ShowDisposalRule.RESOLVER, disposalRule.getId()); - } - } + }); + + title.setText(messages.editDisposalRuleTitle()); + title.setIconClass("DisposalRule"); - @UiHandler("buttonCancel") - void buttonCancelHandler(ClickEvent e) { - HistoryUtils.newHistory(ShowDisposalRule.RESOLVER, disposalRule.getId()); + keyboardFocus.setFocus(true); + keyboardFocus.addStyleName("browse"); } interface MyUiBinder extends UiBinder { diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/EditDisposalRule.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/EditDisposalRule.ui.xml index 0e077c494b..eb26d06c78 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/EditDisposalRule.ui.xml +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/EditDisposalRule.ui.xml @@ -1,32 +1,21 @@ - + - + + + - - - - - - + - - - - - - - - - - - - - + + + + - + diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/ShowDisposalRule.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/ShowDisposalRule.java index eb2b7723ae..94850ee5b0 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/ShowDisposalRule.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/ShowDisposalRule.java @@ -7,41 +7,32 @@ */ package org.roda.wui.client.disposal.rule; +import java.util.HashMap; import java.util.List; +import java.util.Map; -import org.roda.core.data.common.RodaConstants; -import org.roda.core.data.v2.disposal.rule.ConditionType; +import com.google.gwt.user.client.ui.FocusPanel; import org.roda.core.data.v2.disposal.rule.DisposalRule; -import org.roda.wui.client.browse.BrowseTop; +import org.roda.wui.client.browse.tabs.DisposalRuleTabs; +import org.roda.wui.client.common.DisposalRuleActionsToolbar; +import org.roda.wui.client.common.NavigationToolbar; import org.roda.wui.client.common.NoAsyncCallback; import org.roda.wui.client.common.TitlePanel; import org.roda.wui.client.common.UserLogin; -import org.roda.wui.client.common.actions.DisposalRuleActions; -import org.roda.wui.client.common.dialogs.Dialogs; +import org.roda.wui.client.common.actions.Actionable; import org.roda.wui.client.common.utils.AsyncCallbackUtils; -import org.roda.wui.client.common.utils.PermissionClientUtils; import org.roda.wui.client.disposal.policy.DisposalPolicy; -import org.roda.wui.client.disposal.schedule.ShowDisposalSchedule; +import org.roda.wui.client.main.BreadcrumbUtils; import org.roda.wui.client.services.Services; import org.roda.wui.common.client.HistoryResolver; import org.roda.wui.common.client.tools.HistoryUtils; -import org.roda.wui.common.client.tools.Humanize; import org.roda.wui.common.client.tools.ListUtils; -import org.roda.wui.common.client.tools.StringUtils; -import org.roda.wui.common.client.widgets.Toast; import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.safehtml.shared.SafeHtmlUtils; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Widget; import config.i18n.client.ClientMessages; @@ -50,13 +41,24 @@ * @author Tiago Fraga */ public class ShowDisposalRule extends Composite { - private static final ClientMessages messages = GWT.create(ClientMessages.class); - private static ShowDisposalRule instance = null; public static final HistoryResolver RESOLVER = new HistoryResolver() { @Override public void resolve(List historyTokens, final AsyncCallback callback) { - getInstance().resolve(historyTokens, callback); + if (historyTokens.size() == 1) { + Services services = new Services("Retrieve disposal rule", "get"); + services.disposalRuleResource(s -> s.retrieveDisposalRule(historyTokens.get(0))) + .whenComplete((rule, throwable) -> { + if (throwable != null) { + // TODO: REVIEW defaultFailureTreatment + AsyncCallbackUtils.defaultFailureTreatment(throwable); + HistoryUtils.newHistory(DisposalPolicy.RESOLVER.getHistoryPath()); + } else { + ShowDisposalRule panel = new ShowDisposalRule(rule); + callback.onSuccess(panel); + } + }); + } } @Override @@ -74,175 +76,61 @@ public String getHistoryToken() { return "disposal_rule"; } }; - private static ShowDisposalRule.MyUiBinder uiBinder = GWT.create(ShowDisposalRule.MyUiBinder.class); - @UiField - Label disposalRuleId; - @UiField - Label dateCreated, dateUpdated; - @UiField - TitlePanel title; - @UiField - Label disposalRuleDescriptionLabel; - @UiField - HTML disposalRuleDescription; - @UiField - Label disposalRuleScheduleLabel; - @UiField - HTML disposalRuleScheduleName; - @UiField - Label disposalRuleTypeLabel; - @UiField - HTML disposalRuleType; - @UiField - Label conditionsLabel; - @UiField - FlowPanel conditionsPanel; + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); - // Conditions @UiField - FlowPanel buttonsPanel; - private DisposalRule disposalRule; + FocusPanel keyboardFocus; - // Sidebar + @UiField + NavigationToolbar navigationToolbar; - public ShowDisposalRule() { - this.disposalRule = new DisposalRule(); - } + @UiField + DisposalRuleActionsToolbar actionsToolbar; - public ShowDisposalRule(final DisposalRule disposalRule) { - instance = this; - this.disposalRule = disposalRule; + @UiField + TitlePanel title; - initWidget(uiBinder.createAndBindUi(this)); - initElements(); - initButtons(); - } + @UiField + DisposalRuleTabs browseTab; - public static ShowDisposalRule getInstance() { - if (instance == null) { - instance = new ShowDisposalRule(); + private Map handlers = new HashMap<>(); + private AsyncCallback handler = new NoAsyncCallback() { + @Override + public void onSuccess(Actionable.ActionImpact result) { + if (handlers.containsKey(result)) { + handlers.get(result).run(); + } } - return instance; - } - - public void initElements() { - title.setText(disposalRule.getTitle()); - - disposalRuleId.setText(messages.disposalRuleIdentifier() + ": " + disposalRule.getId()); + }; - if (disposalRule.getCreatedOn() != null && StringUtils.isNotBlank(disposalRule.getCreatedBy())) { - dateCreated.setText( - messages.dateCreated(Humanize.formatDateTime(disposalRule.getCreatedOn()), disposalRule.getCreatedBy())); - } + public ShowDisposalRule(final DisposalRule rule) { + initWidget(uiBinder.createAndBindUi(this)); - if (disposalRule.getUpdatedOn() != null && StringUtils.isNotBlank(disposalRule.getUpdatedBy())) { - dateUpdated.setText( - messages.dateUpdated(Humanize.formatDateTime(disposalRule.getUpdatedOn()), disposalRule.getUpdatedBy())); - } + initHandlers(); - disposalRuleDescription.setHTML(SafeHtmlUtils.fromString(disposalRule.getDescription())); - disposalRuleDescriptionLabel.setVisible(StringUtils.isNotBlank(disposalRule.getDescription())); + navigationToolbar.withObject(rule); + navigationToolbar.updateBreadcrumbPath(BreadcrumbUtils.getDisposalRuleBreadcrumbs(rule)); + navigationToolbar.build(); - disposalRuleScheduleName.setHTML(SafeHtmlUtils.fromString(disposalRule.getDisposalScheduleName())); - disposalRuleScheduleLabel.setVisible(StringUtils.isNotBlank(disposalRule.getDisposalScheduleName())); - disposalRuleScheduleName.addStyleName("btn-link addCursorPointer"); - disposalRuleScheduleName.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent clickEvent) { - HistoryUtils.newHistory(ShowDisposalSchedule.RESOLVER, disposalRule.getDisposalScheduleId()); - } - }); - - disposalRuleType - .setHTML(SafeHtmlUtils.fromString(messages.disposalRuleTypeValue(disposalRule.getType().toString()))); - disposalRuleTypeLabel.setVisible(StringUtils.isNotBlank(disposalRule.getType().toString())); - - conditionsLabel.setVisible(true); - HTML condition = new HTML(); - if (disposalRule.getType().equals(ConditionType.IS_CHILD_OF)) { - String conditionTxt = messages.disposalRuleTypeValue(disposalRule.getType().toString()) + " " - + disposalRule.getConditionValue() + " (" + disposalRule.getConditionKey() + ")"; - condition.setHTML(SafeHtmlUtils.fromString(conditionTxt)); - condition.addStyleName("btn-link addCursorPointer"); - condition.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent clickEvent) { - HistoryUtils.newHistory(BrowseTop.RESOLVER, disposalRule.getConditionKey()); - } - }); - conditionsPanel.add(condition); - } else if (disposalRule.getType().equals(ConditionType.METADATA_FIELD)) { - String conditionTxt = disposalRule.getConditionKey() + " " + messages.disposalRuleConditionOperator() + " " - + disposalRule.getConditionValue(); - condition.setHTML(SafeHtmlUtils.fromString(conditionTxt)); - conditionsPanel.add(condition); - } - } + actionsToolbar.setLabel(messages.showDisposalRuleTitle()); + actionsToolbar.setObjectAndBuild(rule, null, handler); - public void initButtons() { - - if (PermissionClientUtils.hasPermissions(RodaConstants.PERMISSION_METHOD_UPDATE_DISPOSAL_RULE)) { - Button editRuleBtn = new Button(); - editRuleBtn.addStyleName("btn btn-block btn-edit"); - editRuleBtn.setText(messages.editButton()); - editRuleBtn.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent clickEvent) { - HistoryUtils.newHistory(EditDisposalRule.RESOLVER, disposalRule.getId()); - } - }); - - buttonsPanel.add(editRuleBtn); - - Button removeRuleBtn = new Button(); - removeRuleBtn.addStyleName("btn btn-block btn-danger btn-ban"); - removeRuleBtn.setText(messages.removeButton()); - removeRuleBtn.addClickHandler(clickEvent -> Dialogs.showConfirmDialog(messages.deleteDisposalRuleDialogTitle(), - messages.deleteDisposalRuleDialogMessage(disposalRule.getTitle()), messages.dialogNo(), messages.dialogYes(), - new NoAsyncCallback() { - @Override - public void onSuccess(Boolean confirm) { - if (confirm) { - Services services = new Services("Delete disposal rule", "deletion"); - services.disposalRuleResource(s -> s.deleteDisposalRule(disposalRule.getId())) - .whenComplete((unused, throwable) -> { - if (throwable != null) { - AsyncCallbackUtils.defaultFailureTreatment(throwable); - } else { - Toast.showInfo(messages.deleteDisposalRuleSuccessTitle(), - messages.deleteDisposalRuleSuccessMessage(disposalRule.getTitle())); - DisposalRuleActions.applyDisposalRulesAction(); - } - }); - } - } - })); + title.setText(rule.getTitle()); + title.setIconClass("DisposalRule"); - buttonsPanel.add(removeRuleBtn); - } + keyboardFocus.setFocus(true); + keyboardFocus.addStyleName("browse"); - Button backBtn = new Button(); - backBtn.setText(messages.backButton()); - backBtn.addStyleName("btn btn-block btn-default btn-times-circle"); - backBtn.addClickHandler(event -> HistoryUtils.newHistory(DisposalPolicy.RESOLVER)); - buttonsPanel.add(backBtn); + browseTab.init(rule, handler); } - public void resolve(List historyTokens, final AsyncCallback callback) { - if (historyTokens.size() == 1) { - Services services = new Services("Retrieve disposal rule", "get"); - services.disposalRuleResource(s -> s.retrieveDisposalRule(historyTokens.get(0))) - .whenComplete((result, throwable) -> { - if (throwable != null) { - AsyncCallbackUtils.defaultFailureTreatment(throwable); - } else { - ShowDisposalRule panel = new ShowDisposalRule(result); - callback.onSuccess(panel); - } - }); - } + private void initHandlers() { + handlers.put(Actionable.ActionImpact.DESTROYED, + () -> HistoryUtils.newHistory(DisposalPolicy.RESOLVER.getHistoryPath())); } interface MyUiBinder extends UiBinder { } + } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/ShowDisposalRule.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/ShowDisposalRule.ui.xml index 1c76906f03..a2546915a7 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/ShowDisposalRule.ui.xml +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/ShowDisposalRule.ui.xml @@ -1,65 +1,23 @@ - + - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - - + + \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/tabs/DisposalRuleDetailsPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/tabs/DisposalRuleDetailsPanel.java new file mode 100644 index 0000000000..c338cfabe6 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/tabs/DisposalRuleDetailsPanel.java @@ -0,0 +1,130 @@ +package org.roda.wui.client.disposal.rule.tabs; + +import java.util.List; + +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.safehtml.shared.SafeHtmlUtils; +import org.roda.core.data.v2.disposal.rule.ConditionType; +import org.roda.core.data.v2.disposal.rule.DisposalRule; +import org.roda.wui.client.browse.BrowseTop; +import org.roda.wui.client.common.ActionsToolbar; +import org.roda.wui.client.common.actions.Actionable; +import org.roda.wui.client.common.actions.DisposalRuleAction; +import org.roda.wui.client.common.actions.DisposalRuleToolbarActions; +import org.roda.wui.client.common.actions.model.ActionableObject; +import org.roda.wui.client.common.actions.widgets.ActionableWidgetBuilder; +import org.roda.wui.client.common.utils.FormUtilities; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.Widget; + +import config.i18n.client.ClientMessages; +import org.roda.wui.client.common.utils.HtmlSnippetUtils; +import org.roda.wui.client.disposal.schedule.ShowDisposalSchedule; +import org.roda.wui.common.client.tools.HistoryUtils; + +/** + * @author Miguel Guimarães + */ + +public class DisposalRuleDetailsPanel extends Composite { + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); + + @UiField + ActionsToolbar actionsToolbar; + + @UiField + FlowPanel detailsPanel; + + private DisposalRule rule; + private AsyncCallback localCallback; + + public DisposalRuleDetailsPanel(DisposalRule disposalRule, AsyncCallback actionCallback) { + initWidget(uiBinder.createAndBindUi(this)); + + // Promote localCallback to an instance variable so refresh() can use it + this.localCallback = new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + actionCallback.onFailure(caught); + } + + @Override + public void onSuccess(Actionable.ActionImpact result) { + if (Actionable.ActionImpact.UPDATED.equals(result)) { + actionCallback.onSuccess(result); + } + } + }; + + actionsToolbar.setLabelVisible(false); + actionsToolbar.setTagsVisible(false); + + // Initial load + refresh(disposalRule); + } + + // Update the method signature to accept the new rule + public void refresh(DisposalRule newDisposalRule) { + this.rule = newDisposalRule; + + // 1. Clear out the old details + clear(); + + // 2. Re-populate text fields with the new data + init(this.rule); + + // 3. Re-bind the actions toolbar with the new rule object + actionsToolbar.setActionableMenu(new ActionableWidgetBuilder(DisposalRuleToolbarActions.get()) + .withActionCallback(localCallback).buildGroupedListWithObjects(new ActionableObject<>(rule), + List.of(DisposalRuleAction.EDIT), List.of(DisposalRuleAction.EDIT)), + true); + } + + private void init(DisposalRule rule) { + FormUtilities.addIfNotBlank(detailsPanel, messages.disposalRuleTitle(), rule.getTitle()); + FormUtilities.addIfNotBlank(detailsPanel, messages.disposalRuleDescription(), rule.getDescription()); + FormUtilities.addIfNotBlank(detailsPanel, messages.disposalRuleScheduleName(), rule.getDisposalScheduleName(), + "btn-link addCursorPointer", new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + HistoryUtils.newHistory(ShowDisposalSchedule.RESOLVER, rule.getDisposalScheduleId()); + } + }); + FormUtilities.addIfNotBlank(detailsPanel, messages.showUserStatusLabel(), HtmlSnippetUtils.getDisposalRuleTypeHtml(rule)); + + + if (rule.getType().equals(ConditionType.IS_CHILD_OF)) { + String conditionTxt = messages.disposalRuleTypeValue(rule.getType().toString()) + " " + rule.getConditionValue() + + " (" + rule.getConditionKey() + ")"; + FormUtilities.addIfNotBlank(detailsPanel, messages.disposalRuleCondition(), conditionTxt, + "btn-link addCursorPointer", new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + HistoryUtils.newHistory(BrowseTop.RESOLVER, rule.getConditionKey()); + } + }); + } else if (rule.getType().equals(ConditionType.METADATA_FIELD)) { + + String conditionTxt = rule.getConditionKey() + " " + messages.disposalRuleConditionOperator() + " " + + rule.getConditionValue(); + FormUtilities.addIfNotBlank(detailsPanel, messages.disposalRuleType(), SafeHtmlUtils.fromString(conditionTxt)); + } + } + + public void clear() { + detailsPanel.clear(); + } + + interface MyUiBinder extends UiBinder { + Widget createAndBindUi(DisposalRuleDetailsPanel detailsPanel); + } + +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/tabs/DisposalRuleDetailsPanel.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/tabs/DisposalRuleDetailsPanel.ui.xml new file mode 100644 index 0000000000..ea70d608e6 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/tabs/DisposalRuleDetailsPanel.ui.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/CreateDisposalSchedule.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/CreateDisposalSchedule.java index ba51fb7ab8..e36b6346f4 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/CreateDisposalSchedule.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/CreateDisposalSchedule.java @@ -9,22 +9,25 @@ import java.util.List; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.FocusPanel; +import org.roda.core.data.v2.disposal.rule.DisposalRule; import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; +import org.roda.wui.client.common.CreateOrUpdateDisposalScheduleActionsToolbar; +import org.roda.wui.client.common.NavigationToolbar; +import org.roda.wui.client.common.TitlePanel; import org.roda.wui.client.common.UserLogin; +import org.roda.wui.client.common.actions.Actionable; import org.roda.wui.client.common.utils.AsyncCallbackUtils; import org.roda.wui.client.disposal.policy.DisposalPolicy; -import org.roda.wui.client.services.Services; +import org.roda.wui.client.main.BreadcrumbUtils; import org.roda.wui.common.client.HistoryResolver; -import org.roda.wui.common.client.tools.HistoryUtils; import org.roda.wui.common.client.tools.ListUtils; import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.uibinder.client.UiHandler; import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Widget; @@ -39,7 +42,7 @@ public class CreateDisposalSchedule extends Composite { @Override public void resolve(List historyTokens, final AsyncCallback callback) { - CreateDisposalSchedule createDisposalSchedule = new CreateDisposalSchedule(new DisposalSchedule()); + CreateDisposalSchedule createDisposalSchedule = new CreateDisposalSchedule(); callback.onSuccess(createDisposalSchedule); } @@ -59,48 +62,52 @@ public String getHistoryToken() { } }; private static final ClientMessages messages = GWT.create(ClientMessages.class); - private static CreateDisposalSchedule.MyUiBinder uiBinder = GWT.create(CreateDisposalSchedule.MyUiBinder.class); + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); + + @UiField + FocusPanel keyboardFocus; + @UiField + NavigationToolbar navigationToolbar; + @UiField + CreateOrUpdateDisposalScheduleActionsToolbar actionsToolbar; @UiField - Button buttonSave; + TitlePanel title; @UiField - Button buttonCancel; - @UiField(provided = true) - DisposalScheduleDataPanel disposalScheduleDataPanel; - private DisposalSchedule disposalSchedule; + FlowPanel disposalScheduleDataPanel; - public CreateDisposalSchedule(DisposalSchedule disposalSchedule) { - this.disposalSchedule = disposalSchedule; + public CreateDisposalSchedule() { + initWidget(uiBinder.createAndBindUi(this)); - this.disposalScheduleDataPanel = new DisposalScheduleDataPanel(disposalSchedule, false); - this.disposalScheduleDataPanel.setDisposalSchedule(disposalSchedule); + DisposalScheduleDataPanel dataPanel = new DisposalScheduleDataPanel(new DisposalSchedule(), false); + disposalScheduleDataPanel.add(dataPanel); - initWidget(uiBinder.createAndBindUi(this)); - } + navigationToolbar.withoutButtons().build(); + navigationToolbar.updateBreadcrumbPath(BreadcrumbUtils.getCreateDisposalScheduleBreadcrumbs()); - @UiHandler("buttonSave") - void buttonApplyHandler(ClickEvent e) { - if (disposalScheduleDataPanel.isValid()) { - disposalSchedule = disposalScheduleDataPanel.getDisposalSchedule(); - - Services services = new Services("Create disposal schedule", "create"); - services.disposalScheduleResource(s -> s.createDisposalSchedule(disposalSchedule)) - .whenComplete((created, throwable) -> { - if (throwable != null) { - AsyncCallbackUtils.defaultFailureTreatment(throwable); - } else { - HistoryUtils.newHistory(DisposalPolicy.RESOLVER); - } - }); - } - } + actionsToolbar.setLabel(messages.showDisposalScheduleTitle()); - @UiHandler("buttonCancel") - void buttonCancelHandler(ClickEvent e) { - cancel(); - } + // 2. Give the toolbar the panel so it can check validity + actionsToolbar.setDataPanel(dataPanel); + actionsToolbar.setIsCreate(true); + + // 3. Pass the shared object + actionsToolbar.setObjectAndBuild(new DisposalSchedule(), null, new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + AsyncCallbackUtils.defaultFailureTreatment(caught); + } + + @Override + public void onSuccess(Actionable.ActionImpact result) { + // Redirect user or show success message if result == ActionImpact.UPDATED + } + }); + + title.setText(messages.newDisposalScheduleTitle()); + title.setIconClass("DisposalSchedule"); - private void cancel() { - HistoryUtils.newHistory(DisposalPolicy.RESOLVER); + keyboardFocus.setFocus(true); + keyboardFocus.addStyleName("browse"); } interface MyUiBinder extends UiBinder { diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/CreateDisposalSchedule.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/CreateDisposalSchedule.ui.xml index a2661ae4e2..0eda97f005 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/CreateDisposalSchedule.ui.xml +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/CreateDisposalSchedule.ui.xml @@ -1,31 +1,21 @@ - + - + + + - - - - - - - - - - - - - - - - - - - + + + + + + - + diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/DisposalScheduleDataPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/DisposalScheduleDataPanel.java index edff46c59b..9338e52582 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/DisposalScheduleDataPanel.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/DisposalScheduleDataPanel.java @@ -15,6 +15,7 @@ import org.roda.core.data.v2.disposal.schedule.DisposalActionCode; import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; import org.roda.core.data.v2.disposal.schedule.RetentionPeriodIntervalCode; +import org.roda.wui.client.GenericDataPanel; import org.roda.wui.common.client.tools.StringUtils; import com.google.gwt.core.client.GWT; @@ -42,7 +43,7 @@ /** * @author Tiago Fraga */ -public class DisposalScheduleDataPanel extends Composite implements HasValueChangeHandlers { +public class DisposalScheduleDataPanel extends Composite implements GenericDataPanel, HasValueChangeHandlers { public static final String IS_WRONG = "isWrong"; private static final ClientMessages messages = GWT.create(ClientMessages.class); @@ -300,6 +301,7 @@ private RetentionPeriodIntervalCode getRetentionPeriodIntervalCode(String string * * @return true if valid */ + @Override public boolean isValid() { List errorList = new ArrayList<>(); @@ -441,6 +443,7 @@ protected void onChange() { ValueChangeEvent.fire(this, getValue()); } + @Override public DisposalSchedule getValue() { return getDisposalSchedule(); } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/EditDisposalSchedule.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/EditDisposalSchedule.java index 5bba92fedf..3868cb413e 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/EditDisposalSchedule.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/EditDisposalSchedule.java @@ -9,11 +9,21 @@ import java.util.List; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.FocusPanel; +import org.roda.core.data.v2.disposal.rule.DisposalRule; import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; import org.roda.core.data.v2.disposal.schedule.DisposalScheduleState; +import org.roda.wui.client.common.CreateOrUpdateDisposalRuleActionsToolbar; +import org.roda.wui.client.common.CreateOrUpdateDisposalScheduleActionsToolbar; +import org.roda.wui.client.common.NavigationToolbar; +import org.roda.wui.client.common.TitlePanel; import org.roda.wui.client.common.UserLogin; +import org.roda.wui.client.common.actions.Actionable; import org.roda.wui.client.common.utils.AsyncCallbackUtils; import org.roda.wui.client.disposal.policy.DisposalPolicy; +import org.roda.wui.client.disposal.rule.DisposalRuleDataPanel; +import org.roda.wui.client.main.BreadcrumbUtils; import org.roda.wui.client.services.Services; import org.roda.wui.common.client.HistoryResolver; import org.roda.wui.common.client.tools.HistoryUtils; @@ -73,65 +83,61 @@ public String getHistoryToken() { return "edit_disposal_schedule"; } }; + private static final ClientMessages messages = GWT.create(ClientMessages.class); - private static EditDisposalSchedule instance = null; + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); - private static EditDisposalSchedule.MyUiBinder uiBinder = GWT.create(EditDisposalSchedule.MyUiBinder.class); @UiField - Button buttonApply; + FocusPanel keyboardFocus; + @UiField + NavigationToolbar navigationToolbar; + @UiField + CreateOrUpdateDisposalScheduleActionsToolbar actionsToolbar; @UiField - Button buttonCancel; - @UiField(provided = true) - DisposalScheduleDataPanel disposalScheduleDataPanel; - private DisposalSchedule disposalSchedule; + TitlePanel title; + @UiField + FlowPanel disposalScheduleDataPanel; public EditDisposalSchedule() { initWidget(uiBinder.createAndBindUi(this)); } public EditDisposalSchedule(DisposalSchedule disposalSchedule) { - this.disposalSchedule = disposalSchedule; - this.disposalScheduleDataPanel = new DisposalScheduleDataPanel(disposalSchedule, true); - initWidget(uiBinder.createAndBindUi(this)); - } - public static EditDisposalSchedule getInstance() { - if (instance == null) { - instance = new EditDisposalSchedule(); - } - return instance; - } + // 1. Create the panel and keep a reference + DisposalScheduleDataPanel dataPanel = new DisposalScheduleDataPanel(disposalSchedule, true); + disposalScheduleDataPanel.add(dataPanel); - @UiHandler("buttonApply") - void buttonApplyHandler(ClickEvent e) { - if (disposalScheduleDataPanel.isChanged() && disposalScheduleDataPanel.isValid()) { - DisposalSchedule disposalScheduleUpdated = disposalScheduleDataPanel.getDisposalSchedule(); - disposalSchedule.setTitle(disposalScheduleUpdated.getTitle()); - disposalSchedule.setMandate(disposalScheduleUpdated.getMandate()); - disposalSchedule.setDescription(disposalScheduleUpdated.getDescription()); - disposalSchedule.setScopeNotes(disposalScheduleUpdated.getScopeNotes()); - - Services services = new Services("Update disposal schedule", "update"); - services.disposalScheduleResource(s -> s.updateDisposalSchedule(disposalSchedule)) - .whenComplete((updatedDisposalSchedule, throwable) -> { - if (throwable != null) { - AsyncCallbackUtils.defaultFailureTreatment(throwable); - } else { - HistoryUtils.newHistory(ShowDisposalSchedule.RESOLVER, updatedDisposalSchedule.getId()); - } - }); - } else { - HistoryUtils.newHistory(ShowDisposalSchedule.RESOLVER, disposalSchedule.getId()); - } - } + navigationToolbar.withoutButtons().build(); + navigationToolbar.updateBreadcrumbPath(BreadcrumbUtils.getEditDisposalScheduleBreadcrumbs(disposalSchedule)); + + actionsToolbar.setLabel(messages.showDisposalRuleTitle()); + + // 2. Give the toolbar the panel so it can check validity + actionsToolbar.setDataPanel(dataPanel); + actionsToolbar.setIsCreate(false); + + // 3. Pass the shared object + actionsToolbar.setObjectAndBuild(disposalSchedule, null, new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + AsyncCallbackUtils.defaultFailureTreatment(caught); + } - @UiHandler("buttonCancel") - void buttonCancelHandler(ClickEvent e) { - HistoryUtils.newHistory(ShowDisposalSchedule.RESOLVER, disposalSchedule.getId()); + @Override + public void onSuccess(Actionable.ActionImpact result) { + // Redirect user or show success message if result == ActionImpact.UPDATED + } + }); + + title.setText(messages.editDisposalScheduleTitle()); + title.setIconClass("DisposalSchedule"); + + keyboardFocus.setFocus(true); + keyboardFocus.addStyleName("browse"); } interface MyUiBinder extends UiBinder { } - } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/EditDisposalSchedule.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/EditDisposalSchedule.ui.xml index 428787e514..798e308add 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/EditDisposalSchedule.ui.xml +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/EditDisposalSchedule.ui.xml @@ -1,32 +1,21 @@ - + - + + + - - - - - - + - - - - - - - - - - - - - + + + + - + diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/ShowDisposalSchedule.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/ShowDisposalSchedule.java index d141006ebe..a24e88608f 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/ShowDisposalSchedule.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/ShowDisposalSchedule.java @@ -7,50 +7,32 @@ */ package org.roda.wui.client.disposal.schedule; +import java.util.HashMap; import java.util.List; +import java.util.Map; -import org.roda.core.data.common.RodaConstants; -import org.roda.core.data.v2.disposal.rule.DisposalRule; -import org.roda.core.data.v2.disposal.rule.DisposalRules; +import com.google.gwt.user.client.ui.FocusPanel; import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; -import org.roda.core.data.v2.disposal.schedule.DisposalScheduleState; -import org.roda.core.data.v2.disposal.schedule.RetentionPeriodIntervalCode; -import org.roda.core.data.v2.index.filter.Filter; -import org.roda.core.data.v2.index.filter.SimpleFilterParameter; -import org.roda.core.data.v2.ip.AIPState; -import org.roda.core.data.v2.ip.IndexedAIP; -import org.roda.wui.client.common.DisposalPolicySummaryPanel; +import org.roda.wui.client.browse.tabs.DisposalScheduleTabs; +import org.roda.wui.client.common.DisposalScheduleActionsToolbar; +import org.roda.wui.client.common.NavigationToolbar; +import org.roda.wui.client.common.NoAsyncCallback; import org.roda.wui.client.common.TitlePanel; import org.roda.wui.client.common.UserLogin; -import org.roda.wui.client.common.actions.DisposalScheduleActions; -import org.roda.wui.client.common.lists.utils.AsyncTableCellOptions; -import org.roda.wui.client.common.lists.utils.ConfigurableAsyncTableCell; -import org.roda.wui.client.common.lists.utils.ListBuilder; -import org.roda.wui.client.common.search.SearchWrapper; +import org.roda.wui.client.common.actions.Actionable; import org.roda.wui.client.common.utils.AsyncCallbackUtils; -import org.roda.wui.client.common.utils.HtmlSnippetUtils; -import org.roda.wui.client.common.utils.PermissionClientUtils; import org.roda.wui.client.disposal.policy.DisposalPolicy; -import org.roda.wui.client.services.DisposalRuleRestService; +import org.roda.wui.client.main.BreadcrumbUtils; import org.roda.wui.client.services.Services; import org.roda.wui.common.client.HistoryResolver; import org.roda.wui.common.client.tools.HistoryUtils; -import org.roda.wui.common.client.tools.Humanize; import org.roda.wui.common.client.tools.ListUtils; -import org.roda.wui.common.client.tools.StringUtils; -import org.roda.wui.common.client.widgets.Toast; import com.google.gwt.core.client.GWT; -import com.google.gwt.safehtml.shared.SafeHtmlUtils; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.Widget; import config.i18n.client.ClientMessages; @@ -59,13 +41,22 @@ * @author Tiago Fraga */ public class ShowDisposalSchedule extends Composite { - private static final ClientMessages messages = GWT.create(ClientMessages.class); - private static ShowDisposalSchedule instance = null; public static final HistoryResolver RESOLVER = new HistoryResolver() { @Override public void resolve(List historyTokens, final AsyncCallback callback) { - getInstance().resolve(historyTokens, callback); + if (historyTokens.size() == 1) { + Services services = new Services("Retrieve disposal schedule", "get"); + services.disposalScheduleResource(s -> s.retrieveDisposalSchedule(historyTokens.get(0))) + .whenComplete((schedule, throwable) -> { + if (throwable != null) { + AsyncCallbackUtils.defaultFailureTreatment(throwable); + } else { + ShowDisposalSchedule panel = new ShowDisposalSchedule(schedule); + callback.onSuccess(panel); + } + }); + } } @Override @@ -83,249 +74,83 @@ public String getHistoryToken() { return "disposal_schedule"; } }; - private static ShowDisposalSchedule.MyUiBinder uiBinder = GWT.create(ShowDisposalSchedule.MyUiBinder.class); - @UiField - TitlePanel title; - @UiField - DisposalPolicySummaryPanel usedInRulePanel; - @UiField - Label disposalScheduleId; - @UiField - Label dateCreated, dateUpdated; - @UiField - Label descriptionLabel; - @UiField - HTML descriptionValue; - @UiField - Label mandateLabel; - @UiField - HTML mandateValue; - @UiField - Label notesLabel; - @UiField - HTML notesValue; - @UiField - Label disposalActionsLabel; - @UiField - HTML disposalActionsValue; - @UiField - Label retentionTriggersLabel; - @UiField - HTML retentionTriggersValue; + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); - // disposal actions - @UiField - Label retentionPeriodLabel; @UiField - HTML retentionPeriodValue; + FocusPanel keyboardFocus; - // retention triggers @UiField - Label stateLabel; - @UiField - HTML stateValue; + NavigationToolbar navigationToolbar; - // retention period - @UiField - FlowPanel buttonsPanel; @UiField - FlowPanel aipListTitle; - @UiField - SimplePanel aipsListCard; - private DisposalSchedule disposalSchedule; - private DisposalRules disposalRules; - - public ShowDisposalSchedule() { - this.disposalSchedule = new DisposalSchedule(); - } - - public ShowDisposalSchedule(final DisposalSchedule disposalSchedule, final DisposalRules disposalRules) { - instance = this; - this.disposalSchedule = disposalSchedule; - this.disposalRules = disposalRules; - - initWidget(uiBinder.createAndBindUi(this)); - initElements(); - initButtons(); - } - - public static ShowDisposalSchedule getInstance() { - if (instance == null) { - instance = new ShowDisposalSchedule(); - } - return instance; - } - - public void initElements() { - title.setText(disposalSchedule.getTitle()); - - if (isScheduleInRule()) { - usedInRulePanel.setIcon("fas fa-info-circle"); - usedInRulePanel.setText(messages.disposalScheduleUsedInRule()); - } else { - usedInRulePanel.setVisible(false); - } + DisposalScheduleActionsToolbar actionsToolbar; - disposalScheduleId.setText(messages.disposalScheduleIdentifier() + ": " + disposalSchedule.getId()); + @UiField + TitlePanel title; - if (disposalSchedule.getCreatedOn() != null && StringUtils.isNotBlank(disposalSchedule.getCreatedBy())) { - dateCreated.setText(messages.dateCreated(Humanize.formatDateTime(disposalSchedule.getCreatedOn()), - disposalSchedule.getCreatedBy())); - } + @UiField + DisposalScheduleTabs browseTab; - if (disposalSchedule.getUpdatedOn() != null && StringUtils.isNotBlank(disposalSchedule.getUpdatedBy())) { - dateUpdated.setText(messages.dateUpdated(Humanize.formatDateTime(disposalSchedule.getUpdatedOn()), - disposalSchedule.getUpdatedBy())); + private Map handlers = new HashMap<>(); + private AsyncCallback handler = new NoAsyncCallback() { + @Override + public void onSuccess(Actionable.ActionImpact result) { + if (handlers.containsKey(result)) { + handlers.get(result).run(); + } } + }; + private DisposalSchedule disposalSchedule; - descriptionValue.setHTML(SafeHtmlUtils.fromString(disposalSchedule.getDescription())); - descriptionLabel.setVisible(StringUtils.isNotBlank(disposalSchedule.getDescription())); - - mandateValue.setHTML(SafeHtmlUtils.fromString(disposalSchedule.getMandate())); - mandateLabel.setVisible(StringUtils.isNotBlank(disposalSchedule.getMandate())); - - notesValue.setHTML(SafeHtmlUtils.fromString(disposalSchedule.getScopeNotes())); - notesLabel.setVisible(StringUtils.isNotBlank(disposalSchedule.getScopeNotes())); - - disposalActionsValue.setHTML(messages.disposalScheduleAction(disposalSchedule.getActionCode().toString())); - disposalActionsLabel.setVisible(StringUtils.isNotBlank(disposalSchedule.getActionCode().toString())); + public ShowDisposalSchedule(final DisposalSchedule schedule) { + this.disposalSchedule = schedule; - retentionTriggersValue.setHTML( - DisposalScheduleUtils.getI18nRetentionTriggerIdentifier(disposalSchedule.getRetentionTriggerElementId())); - retentionTriggersLabel.setVisible(StringUtils.isNotBlank(disposalSchedule.getRetentionTriggerElementId())); + initWidget(uiBinder.createAndBindUi(this)); - if (disposalSchedule.getRetentionPeriodIntervalCode() == null) { - retentionPeriodValue.setHTML(""); - retentionPeriodLabel.setVisible(false); - } else if (disposalSchedule.getRetentionPeriodIntervalCode() - .equals(RetentionPeriodIntervalCode.NO_RETENTION_PERIOD)) { - String retentionPeriod = messages.retentionPeriod(0, - disposalSchedule.getRetentionPeriodIntervalCode().toString()); - retentionPeriodValue.setHTML(retentionPeriod); - retentionPeriodLabel.setVisible(true); - } else { - String retentionPeriod = messages.retentionPeriod(disposalSchedule.getRetentionPeriodDuration(), - disposalSchedule.getRetentionPeriodIntervalCode().toString()); - retentionPeriodValue.setHTML(retentionPeriod); - retentionPeriodLabel.setVisible(true); - } + initHandlers(schedule); - stateValue.setHTML(HtmlSnippetUtils.getDisposalScheduleStateHtml(disposalSchedule)); + navigationToolbar.withObject(schedule).build(); + navigationToolbar.updateBreadcrumbPath(BreadcrumbUtils.getDisposalScheduleBreadcrumbs(schedule)); - // Records with this schedule + actionsToolbar.setLabel(messages.showDisposalScheduleTitle()); + actionsToolbar.setObjectAndBuild(schedule, null, handler); - if (disposalSchedule.getState().equals(DisposalScheduleState.ACTIVE) - && PermissionClientUtils.hasPermissions(RodaConstants.PERMISSION_METHOD_FIND_AIP)) { - Label aipTitle = new Label(); - aipTitle.addStyleName("h5"); - aipTitle.setText(messages.disposalScheduleListAips()); - aipListTitle.add(aipTitle); + title.setText(schedule.getTitle()); + title.setIconClass("DisposalSchedule"); - ListBuilder aipsListBuilder = new ListBuilder<>(() -> new ConfigurableAsyncTableCell<>(), - new AsyncTableCellOptions<>(IndexedAIP.class, "ShowDisposalSchedule_aips") - .withFilter( - new Filter(new SimpleFilterParameter(RodaConstants.AIP_DISPOSAL_SCHEDULE_ID, disposalSchedule.getId()), - new SimpleFilterParameter(RodaConstants.AIP_STATE, AIPState.ACTIVE.name()))) - .withActionable(DisposalScheduleActions.get(disposalSchedule.getId())).withSummary(messages.listOfAIPs()) - .bindOpener()); + keyboardFocus.setFocus(true); + keyboardFocus.addStyleName("browse"); - SearchWrapper aipsSearchWrapper = new SearchWrapper(false).createListAndSearchPanel(aipsListBuilder); - aipsListCard.setWidget(aipsSearchWrapper); - aipsListCard.setVisible(true); - } else { - aipsListCard.setVisible(false); - } + browseTab.init(schedule, handler); } - public void initButtons() { - - if (disposalSchedule.getState().equals(DisposalScheduleState.ACTIVE)) { - Button editScheduleBtn = new Button(); - editScheduleBtn.addStyleName("btn btn-block btn-edit"); - editScheduleBtn.setText(messages.editButton()); - if (PermissionClientUtils.hasPermissions(RodaConstants.PERMISSION_METHOD_UPDATE_DISPOSAL_SCHEDULE)) { - editScheduleBtn.addClickHandler( - clickEvent -> HistoryUtils.newHistory(EditDisposalSchedule.RESOLVER, disposalSchedule.getId())); - } - buttonsPanel.add(editScheduleBtn); - - if (!isScheduleInRule() && disposalSchedule.getFirstTimeUsed() != null - && PermissionClientUtils.hasPermissions(RodaConstants.PERMISSION_METHOD_DELETE_DISPOSAL_SCHEDULE)) { - // Change the state to inactive - Button deactivateScheduleButton = new Button(); - deactivateScheduleButton.addStyleName("btn btn-block btn-danger btn-ban"); - deactivateScheduleButton.setText(messages.deactivateButton()); - deactivateScheduleButton.addClickHandler(clickEvent -> { - disposalSchedule.setState(DisposalScheduleState.INACTIVE); - Services services = new Services("Update disposal schedule", "update"); - services.disposalScheduleResource(s -> s.updateDisposalSchedule(disposalSchedule)) - .whenComplete((disposalSchedule1, throwable) -> { - if (throwable != null) { - AsyncCallbackUtils.defaultRestErrorTreatment(throwable); - } else { - HistoryUtils.newHistory(DisposalPolicy.RESOLVER); - } - }); - }); - buttonsPanel.add(deactivateScheduleButton); - } - - if (!isScheduleInRule() && disposalSchedule.getFirstTimeUsed() == null - && PermissionClientUtils.hasPermissions(RodaConstants.PERMISSION_METHOD_DELETE_DISPOSAL_SCHEDULE)) { - // Delete the disposal schedule - Button deleteDisposalSchedule = new Button(); - deleteDisposalSchedule.addStyleName("btn btn-block btn-danger btn-delete"); - deleteDisposalSchedule.setText(messages.removeButton()); - deleteDisposalSchedule.addClickHandler(clickEvent -> { - Services services = new Services("Delete disposal schedule", "delete"); - services.disposalScheduleResource(s -> s.deleteDisposalSchedule(disposalSchedule.getId())) - .whenComplete((unused, throwable) -> { - if (throwable != null) { - AsyncCallbackUtils.defaultRestErrorTreatment(throwable); - } else { - Toast.showInfo(messages.disposalSchedulesTitle(), - messages.deleteDisposalSchedule(disposalSchedule.getTitle())); - HistoryUtils.newHistory(DisposalPolicy.RESOLVER); - } - }); + private void initHandlers(DisposalSchedule schedule) { + handlers.put(Actionable.ActionImpact.DESTROYED, + () -> HistoryUtils.newHistory(DisposalPolicy.RESOLVER.getHistoryPath())); + + // Use the DOM Swap refresh method instead of HistoryUtils + handlers.put(Actionable.ActionImpact.UPDATED, () -> { + Services services = new Services("Retrieve updated disposal schedule", "get"); + services.disposalScheduleResource(s -> s.retrieveDisposalSchedule(disposalSchedule.getId())) + .whenComplete((updatedSchedule, throwable) -> { + if (throwable != null) { + AsyncCallbackUtils.defaultFailureTreatment(throwable); + } else { + // 1. Update local reference + this.disposalSchedule = updatedSchedule; + + // 2. Update the main TitlePanel + title.setText(updatedSchedule.getTitle()); + + navigationToolbar.updateBreadcrumbPath(BreadcrumbUtils.getDisposalScheduleBreadcrumbs(updatedSchedule)); + + // 4. Re-initialize tabs (this recreates the details panel with the new + // schedule) + browseTab.init(updatedSchedule, handler); + } }); - buttonsPanel.add(deleteDisposalSchedule); - } - } - - Button backBtn = new Button(); - backBtn.setText(messages.backButton()); - backBtn.addStyleName("btn btn-block btn-default btn-times-circle"); - backBtn.addClickHandler(clickEvent -> HistoryUtils.newHistory(DisposalPolicy.RESOLVER)); - buttonsPanel.add(backBtn); - - } - - private boolean isScheduleInRule() { - boolean ret = false; - for (DisposalRule rule : disposalRules.getObjects()) { - if (rule.getDisposalScheduleId().equals(disposalSchedule.getId())) { - ret = true; - break; - } - } - return ret; - } - - void resolve(List historyTokens, final AsyncCallback callback) { - if (historyTokens.size() == 1) { - Services services = new Services("Retrieve disposal schedule", "get"); - services.disposalScheduleResource(s -> s.retrieveDisposalSchedule(historyTokens.get(0))) - .thenCompose(schedule -> services.disposalRuleResource(DisposalRuleRestService::listDisposalRules) - .whenComplete((rules, throwable) -> { - if (throwable != null) { - AsyncCallbackUtils.defaultFailureTreatment(throwable); - } else { - ShowDisposalSchedule panel = new ShowDisposalSchedule(schedule, rules); - callback.onSuccess(panel); - } - })); - } + }); } interface MyUiBinder extends UiBinder { diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/ShowDisposalSchedule.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/ShowDisposalSchedule.ui.xml index 69d879fa1a..30b1b14673 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/ShowDisposalSchedule.ui.xml +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/ShowDisposalSchedule.ui.xml @@ -1,87 +1,23 @@ - + - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - + diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/tabs/DisposalScheduleDetailsPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/tabs/DisposalScheduleDetailsPanel.java new file mode 100644 index 0000000000..7f8b89a77a --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/tabs/DisposalScheduleDetailsPanel.java @@ -0,0 +1,120 @@ +package org.roda.wui.client.disposal.schedule.tabs; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.Widget; +import config.i18n.client.ClientMessages; +import org.roda.core.data.v2.disposal.schedule.DisposalActionCode; +import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; +import org.roda.core.data.v2.disposal.schedule.RetentionPeriodIntervalCode; +import org.roda.wui.client.common.ActionsToolbar; +import org.roda.wui.client.common.actions.Actionable; +import org.roda.wui.client.common.actions.DisposalScheduleAction; +import org.roda.wui.client.common.actions.DisposalScheduleToolbarActions; +import org.roda.wui.client.common.actions.model.ActionableObject; +import org.roda.wui.client.common.actions.widgets.ActionableWidgetBuilder; +import org.roda.wui.client.common.utils.FormUtilities; +import org.roda.wui.client.common.utils.HtmlSnippetUtils; +import org.roda.wui.client.disposal.schedule.DisposalScheduleUtils; + +import java.util.List; + +/** + * @author Miguel Guimarães + */ + +public class DisposalScheduleDetailsPanel extends Composite { + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); + + @UiField + ActionsToolbar actionsToolbar; + + @UiField + FlowPanel detailsPanel; + + private DisposalSchedule schedule; + private AsyncCallback localCallback; + + public DisposalScheduleDetailsPanel(DisposalSchedule disposalSchedule, + AsyncCallback actionCallback) { + initWidget(uiBinder.createAndBindUi(this)); + + // Promote localCallback to an instance variable so refresh() can use it + this.localCallback = new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + actionCallback.onFailure(caught); + } + + @Override + public void onSuccess(Actionable.ActionImpact result) { + if (Actionable.ActionImpact.UPDATED.equals(result)) { + actionCallback.onSuccess(result); + } + } + }; + + actionsToolbar.setLabelVisible(false); + actionsToolbar.setTagsVisible(false); + + // Initial load + refresh(disposalSchedule); + } + + // Update the method signature to accept the new schedule + public void refresh(DisposalSchedule newSchedule) { + this.schedule = newSchedule; + + // 1. Clear out the old details + clear(); + + // 2. Re-populate text fields with the new data + init(this.schedule); + + // 3. Re-bind the actions toolbar with the new schedule object + actionsToolbar.setActionableMenu(new ActionableWidgetBuilder(DisposalScheduleToolbarActions.get()) + .withActionCallback(localCallback).buildGroupedListWithObjects(new ActionableObject<>(schedule), + List.of(DisposalScheduleAction.EDIT), List.of(DisposalScheduleAction.EDIT)), + true); + } + + private void init(DisposalSchedule schedule) { + FormUtilities.addIfNotBlank(detailsPanel, messages.disposalScheduleTitle(), schedule.getTitle()); + FormUtilities.addIfNotBlank(detailsPanel, messages.disposalScheduleDescription(), schedule.getDescription()); + FormUtilities.addIfNotBlank(detailsPanel, messages.disposalScheduleMandate(), schedule.getMandate()); + FormUtilities.addIfNotBlank(detailsPanel, messages.disposalScheduleNotes(), schedule.getScopeNotes()); + FormUtilities.addIfNotBlank(detailsPanel, messages.disposalScheduleActionCol(), + messages.disposalScheduleActionCode(schedule.getActionCode().toString())); + FormUtilities.addIfNotBlank(detailsPanel, messages.disposalScheduleRetentionTriggerElementId(), + DisposalScheduleUtils.getI18nRetentionTriggerIdentifier(schedule.getRetentionTriggerElementId())); + + if (!DisposalActionCode.RETAIN_PERMANENTLY.equals(schedule.getActionCode())) { + String retentionPeriod; + if (schedule.getRetentionPeriodIntervalCode().equals(RetentionPeriodIntervalCode.NO_RETENTION_PERIOD)) { + retentionPeriod = messages.retentionPeriod(0, schedule.getRetentionPeriodIntervalCode().toString()); + } else { + retentionPeriod = messages.retentionPeriod(schedule.getRetentionPeriodDuration(), + schedule.getRetentionPeriodIntervalCode().toString()); + } + FormUtilities.addIfNotBlank(detailsPanel, messages.disposalScheduleRetentionPeriodDuration(), retentionPeriod); + } + + FormUtilities.addIfNotBlank(detailsPanel, messages.showUserStatusLabel(), HtmlSnippetUtils.getDisposalScheduleStateHtml(schedule)); + } + + public void clear() { + detailsPanel.clear(); + } + + interface MyUiBinder extends UiBinder { + Widget createAndBindUi(DisposalScheduleDetailsPanel detailsPanel); + } + +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/tabs/DisposalScheduleDetailsPanel.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/tabs/DisposalScheduleDetailsPanel.ui.xml new file mode 100644 index 0000000000..fb3e0cf09d --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/tabs/DisposalScheduleDetailsPanel.ui.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java index f19c170c03..f829d2c88d 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java @@ -12,6 +12,9 @@ import java.util.List; import org.roda.core.data.common.RodaConstants; +import org.roda.core.data.v2.disposal.hold.DisposalHold; +import org.roda.core.data.v2.disposal.rule.DisposalRule; +import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; import org.roda.core.data.v2.ip.AIPState; import org.roda.core.data.v2.ip.DIPFile; import org.roda.core.data.v2.ip.IndexedAIP; @@ -25,12 +28,22 @@ import org.roda.core.data.v2.ri.RepresentationInformation; import org.roda.core.data.v2.risks.IndexedRisk; import org.roda.wui.client.browse.BrowseTop; +import org.roda.wui.client.disposal.hold.CreateDisposalHold; +import org.roda.wui.client.disposal.hold.EditDisposalHold; +import org.roda.wui.client.disposal.hold.ShowDisposalHold; +import org.roda.wui.client.disposal.rule.CreateDisposalRule; +import org.roda.wui.client.disposal.rule.EditDisposalRule; +import org.roda.wui.client.disposal.rule.ShowDisposalRule; +import org.roda.wui.client.disposal.schedule.CreateDisposalSchedule; +import org.roda.wui.client.disposal.schedule.EditDisposalSchedule; import org.roda.wui.client.management.NotificationRegister; import org.roda.wui.client.management.ShowLogEntry; import org.roda.wui.client.management.ShowNotification; import org.roda.wui.client.management.UserLog; import org.roda.wui.client.browse.PreservationEvents; import org.roda.wui.client.disposal.DisposalDestroyedRecords; +import org.roda.wui.client.disposal.policy.DisposalPolicy; +import org.roda.wui.client.disposal.schedule.ShowDisposalSchedule; import org.roda.wui.client.ingest.appraisal.IngestAppraisal; import org.roda.wui.client.ingest.transfer.IngestTransfer; import org.roda.wui.client.planning.RiskRegister; @@ -527,6 +540,136 @@ public static List getRODAMemberBreadcrumbs(RODAMember user) { return ret; } + public static List getDisposalPolicyBreadcrumbs() { + List ret = new ArrayList<>(); + ret.add(new BreadcrumbItem(SafeHtmlUtils.fromSafeConstant(messages.disposalPolicyTitle()), + messages.disposalPolicyTitle(), DisposalPolicy.RESOLVER.getHistoryPath())); + + return ret; + } + + public static List getCreateDisposalScheduleBreadcrumbs() { + List ret = getDisposalPolicyBreadcrumbs(); + + ret.add(new BreadcrumbItem(SafeHtmlUtils.fromSafeConstant(messages.newDisposalScheduleTitle()), + messages.newDisposalScheduleTitle(), CreateDisposalSchedule.RESOLVER.getHistoryPath())); + + return ret; + } + + public static List getEditDisposalScheduleBreadcrumbs(DisposalSchedule schedule) { + List ret = getDisposalScheduleBreadcrumbs(schedule); + + List path = new ArrayList<>(EditDisposalSchedule.RESOLVER.getHistoryPath()); + path.add(schedule.getId()); + + ret.add(new BreadcrumbItem(SafeHtmlUtils.fromSafeConstant(messages.editDisposalScheduleTitle()), + messages.editDisposalScheduleTitle(), path)); + + return ret; + } + + public static List getDisposalScheduleBreadcrumbs(DisposalSchedule schedule) { + List ret = new ArrayList<>(); + ret.add(new BreadcrumbItem(SafeHtmlUtils.fromSafeConstant(messages.disposalPolicyTitle()), + messages.disposalPolicyTitle(), DisposalPolicy.RESOLVER.getHistoryPath())); + + if (schedule != null) { + StringBuilder b = new StringBuilder(); + b.append(""); + b.append(" "); + b.append(schedule.getTitle()); + SafeHtml safeHtml = SafeHtmlUtils.fromSafeConstant(b.toString()); + + List path = new ArrayList<>(ShowDisposalSchedule.RESOLVER.getHistoryPath()); + path.add(schedule.getId()); + String label = schedule.getTitle(); + ret.add(new BreadcrumbItem(safeHtml, label, path)); + } + + return ret; + } + + public static List getCreateDisposalHoldBreadcrumbs() { + List ret = getDisposalPolicyBreadcrumbs(); + + ret.add(new BreadcrumbItem(SafeHtmlUtils.fromSafeConstant(messages.newDisposalHoldTitle()), + messages.newDisposalHoldTitle(), CreateDisposalHold.RESOLVER.getHistoryPath())); + + return ret; + } + + public static List getDisposalHoldBreadcrumbs(DisposalHold hold) { + List ret = getDisposalPolicyBreadcrumbs(); + + if (hold != null) { + StringBuilder b = new StringBuilder(); + b.append(""); + b.append(" "); + b.append(hold.getTitle()); + SafeHtml safeHtml = SafeHtmlUtils.fromSafeConstant(b.toString()); + + List path = new ArrayList<>(ShowDisposalHold.RESOLVER.getHistoryPath()); + path.add(hold.getId()); + String label = hold.getTitle(); + ret.add(new BreadcrumbItem(safeHtml, label, path)); + } + + return ret; + } + + public static List getEditDisposalHoldBreadcrumbs(DisposalHold hold) { + List ret = getDisposalHoldBreadcrumbs(hold); + + List path = new ArrayList<>(EditDisposalHold.RESOLVER.getHistoryPath()); + path.add(hold.getId()); + + ret.add(new BreadcrumbItem(SafeHtmlUtils.fromSafeConstant(messages.editDisposalHoldTitle()), + messages.editDisposalHoldTitle(), path)); + + return ret; + } + + public static List getDisposalRuleBreadcrumbs(DisposalRule rule) { + List ret = getDisposalPolicyBreadcrumbs(); + + if (rule != null) { + StringBuilder b = new StringBuilder(); + b.append(""); + b.append(" "); + b.append(rule.getTitle()); + SafeHtml safeHtml = SafeHtmlUtils.fromSafeConstant(b.toString()); + + List path = new ArrayList<>(ShowDisposalRule.RESOLVER.getHistoryPath()); + path.add(rule.getId()); + String label = rule.getTitle(); + ret.add(new BreadcrumbItem(safeHtml, label, path)); + } + + return ret; + } + + public static List getEditDisposalRuleBreadcrumbs(DisposalRule rule) { + List ret = getDisposalRuleBreadcrumbs(rule); + + List path = new ArrayList<>(EditDisposalRule.RESOLVER.getHistoryPath()); + path.add(rule.getId()); + + ret.add(new BreadcrumbItem(SafeHtmlUtils.fromSafeConstant(messages.editDisposalRuleTitle()), + messages.editDisposalRuleTitle(), path)); + + return ret; + } + + public static List getCreateDisposalRuleBreadcrumbs() { + List ret = getDisposalPolicyBreadcrumbs(); + + ret.add(new BreadcrumbItem(SafeHtmlUtils.fromSafeConstant(messages.newDisposalRuleTitle()), + messages.newDisposalRuleTitle(), CreateDisposalRule.RESOLVER.getHistoryPath())); + + return ret; + } + private static String getBreadcrumbTitle(IndexedAIP aip) { String title; if (aip.getGhost()) { diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/MemberManagement.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/MemberManagement.java index 5d08ebe4b1..462efa6fd9 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/MemberManagement.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/MemberManagement.java @@ -66,7 +66,7 @@ public MemberManagement() { ListBuilder rodaMemberListBuilder = new ListBuilder<>(() -> new RodaMemberList(), new AsyncTableCellOptions<>(RODAMember.class, "MemberManagement_rodaMembers") .withSummary(messages.usersAndGroupsTitle()).bindOpener() - .withSearchPlaceholder(messages.usersAndGroupsSearchPlaceHolder()).withActionable(RODAMemberActions.get())); + .withActionable(RODAMemberActions.get())); searchWrapper = new SearchWrapper(false).createListAndSearchPanel(rodaMemberListBuilder); @@ -114,7 +114,4 @@ public void resolve(List historyTokens, AsyncCallback callback) interface MyUiBinder extends UiBinder { } - - - } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/services/DisposalHoldRestService.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/services/DisposalHoldRestService.java index 50e677c9dd..6d59c55662 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/services/DisposalHoldRestService.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/services/DisposalHoldRestService.java @@ -7,7 +7,6 @@ */ package org.roda.wui.client.services; -import org.fusesource.restygwt.client.DirectRestService; import org.roda.core.data.v2.disposal.hold.DisposalHold; import org.roda.core.data.v2.disposal.hold.DisposalHolds; import org.roda.core.data.v2.disposal.metadata.DisposalHoldsAIPMetadata; @@ -39,7 +38,7 @@ @Tag(name = "Disposal holds") @RequestMapping(path = "../api/v2/disposal/holds") -public interface DisposalHoldRestService extends DirectRestService { +public interface DisposalHoldRestService extends RODAEntityRestService { @RequestMapping(method = RequestMethod.GET, path = "", produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "List disposal holds", responses = { diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/services/DisposalRuleRestService.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/services/DisposalRuleRestService.java index 1c83206a23..459bfdd929 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/services/DisposalRuleRestService.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/services/DisposalRuleRestService.java @@ -8,9 +8,10 @@ package org.roda.wui.client.services; import io.swagger.v3.oas.annotations.tags.Tag; -import org.fusesource.restygwt.client.DirectRestService; +import org.roda.core.data.v2.disposal.rule.ChangeOrderRequest; import org.roda.core.data.v2.disposal.rule.DisposalRule; import org.roda.core.data.v2.disposal.rule.DisposalRules; +import org.roda.core.data.v2.ip.metadata.DescriptiveMetadataPreviewRequest; import org.roda.core.data.v2.jobs.Job; import org.roda.wui.api.v2.exceptions.model.ErrorResponseMessage; import org.springframework.http.HttpStatus; @@ -33,7 +34,7 @@ */ @Tag(name = "Disposal rules") @RequestMapping(path = "../api/v2/disposal/rules") -public interface DisposalRuleRestService extends DirectRestService { +public interface DisposalRuleRestService extends RODAEntityRestService { @RequestMapping(method = RequestMethod.GET, path = "", produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "List disposal rules", responses = { @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = DisposalRules.class)))}) @@ -74,4 +75,11 @@ Void deleteDisposalRule( @ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(schema = @Schema(implementation = ErrorResponseMessage.class)))}) Job applyDisposalRules( @Parameter(description = "If true, overrides manually associated disposal schedules.", content = @Content(schema = @Schema(implementation = boolean.class, defaultValue = "false"))) @RequestParam(name = "overrideManualAssociations", required = false, defaultValue = "false") boolean overrideManualAssociations); + + @RequestMapping(method = RequestMethod.POST, path = "/updateOrder") + @ResponseStatus(HttpStatus.NO_CONTENT) + @Operation(summary = "Updates disposal rule order", description = "Updates disposal rule order", responses = { + @ApiResponse(responseCode = "204", description = "Resource updated")}) + Void changeDisposalRuleOrder( + @Parameter(name = "changeOrderRequest", required = true, content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)) ChangeOrderRequest request); } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/services/DisposalScheduleRestService.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/services/DisposalScheduleRestService.java index db7f198456..32df13b0ac 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/services/DisposalScheduleRestService.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/services/DisposalScheduleRestService.java @@ -7,7 +7,6 @@ */ package org.roda.wui.client.services; -import org.fusesource.restygwt.client.DirectRestService; import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; import org.roda.core.data.v2.disposal.schedule.DisposalSchedules; import org.roda.core.data.v2.generics.select.SelectedItemsRequest; @@ -33,7 +32,7 @@ */ @Tag(name = "Disposal schedules") @RequestMapping(path = "../api/v2/disposal/schedules") -public interface DisposalScheduleRestService extends DirectRestService { +public interface DisposalScheduleRestService extends RODAEntityRestService { @RequestMapping(method = RequestMethod.GET, path = "", produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "List disposal schedules", responses = { diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/services/Services.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/services/Services.java index 37940e2f7b..bcac9be260 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/services/Services.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/services/Services.java @@ -16,6 +16,9 @@ import org.fusesource.restygwt.client.REST; import org.roda.core.data.exceptions.RODAException; import org.roda.core.data.v2.disposal.confirmation.DisposalConfirmation; +import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; +import org.roda.core.data.v2.disposal.hold.DisposalHold; +import org.roda.core.data.v2.disposal.rule.DisposalRule; import org.roda.core.data.v2.index.IsIndexed; import org.roda.core.data.v2.ip.AIP; import org.roda.core.data.v2.ip.DIPFile; @@ -171,6 +174,12 @@ private , O extends IsIndexed, T> Completable service = GWT.create(AuditLogRestService.class); } else if (DIPFile.class.getName().equals(objectClassString)) { service = GWT.create(DIPFileRestService.class); + } else if (DisposalSchedule.class.getName().equals(objectClassString)) { + service = GWT.create(DisposalScheduleRestService.class); + } else if (DisposalHold.class.getName().equals(objectClassString)) { + service = GWT.create(DisposalHoldRestService.class); + } else if (DisposalRule.class.getName().equals(objectClassString)) { + service = GWT.create(DisposalRuleRestService.class); } else { String error = objectClassString + " not supported"; Toast.showError(error); diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/common/client/tools/HistoryUtils.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/common/client/tools/HistoryUtils.java index caf4d1b8bb..bffb487c1a 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/common/client/tools/HistoryUtils.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/common/client/tools/HistoryUtils.java @@ -14,9 +14,13 @@ import java.util.Arrays; import java.util.List; +import org.apache.commons.codec.language.bm.Rule; import org.roda.core.data.common.RodaConstants; import org.roda.core.data.utils.RepresentationInformationUtils; import org.roda.core.data.v2.disposal.confirmation.DisposalConfirmation; +import org.roda.core.data.v2.disposal.hold.DisposalHold; +import org.roda.core.data.v2.disposal.rule.DisposalRule; +import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; import org.roda.core.data.v2.index.IsIndexed; import org.roda.core.data.v2.ip.DIPFile; import org.roda.core.data.v2.ip.IndexedAIP; @@ -41,6 +45,9 @@ import org.roda.wui.client.browse.ShowPreservationEvent; import org.roda.wui.client.common.utils.AsyncCallbackUtils; import org.roda.wui.client.disposal.confirmations.ShowDisposalConfirmation; +import org.roda.wui.client.disposal.hold.ShowDisposalHold; +import org.roda.wui.client.disposal.rule.ShowDisposalRule; +import org.roda.wui.client.disposal.schedule.ShowDisposalSchedule; import org.roda.wui.client.ingest.process.ShowJob; import org.roda.wui.client.ingest.process.ShowJobReport; import org.roda.wui.client.ingest.transfer.IngestTransfer; @@ -441,6 +448,15 @@ public static void resolve(T object, boolean replace) { DisposalConfirmation confirmationMetadata = (DisposalConfirmation) object; path = HistoryUtils.getHistory(ShowDisposalConfirmation.RESOLVER.getHistoryPath(), confirmationMetadata.getUUID()); + } else if (object instanceof DisposalSchedule) { + DisposalSchedule schedule = (DisposalSchedule) object; + path = HistoryUtils.getHistory(ShowDisposalSchedule.RESOLVER.getHistoryPath(), schedule.getUUID()); + } else if (object instanceof DisposalHold) { + DisposalHold hold = (DisposalHold) object; + path = HistoryUtils.getHistory(ShowDisposalHold.RESOLVER.getHistoryPath(), hold.getUUID()); + } else if (object instanceof DisposalRule) { + DisposalRule rule = (DisposalRule) object; + path = HistoryUtils.getHistory(ShowDisposalRule.RESOLVER.getHistoryPath(), rule.getUUID()); } else { Toast.showError("Resolve of class not supported: " + object.getClass().getName()); } diff --git a/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages.properties b/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages.properties index 241f18e234..875b2871d6 100644 --- a/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages.properties +++ b/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages.properties @@ -31,7 +31,7 @@ ui.search.fields.IndexedRepresentation.representationStates=Status ui.search.fields.IndexedFile.uuid=Identifier ui.search.fields.IndexedFile.filename=Filename -ui.search.fields.IndexedFile.width=Width +ui.search.fields.IndexedFile.width=Width ui.search.fields.IndexedFile.height=Height ui.search.fields.IndexedFile.compression=Lossless compression ui.search.fields.IndexedFile.path=Path @@ -229,6 +229,29 @@ ui.facets.IndexedJob.hasSkipped.false: without skipped ui.facets.IndexedJob.instanceName: Instance ui.facets.LogEntry.actionComponent: Components + +ui.facets.DisposalSchedule.state: State +ui.facets.DisposalSchedule.state.ACTIVE: Active +ui.facets.DisposalSchedule.state.INACTIVE: Inactive +ui.facets.DisposalSchedule.action: Action +ui.facets.DisposalSchedule.action.DESTROY: Destroy +ui.facets.DisposalSchedule.action.REVIEW: Review +ui.facets.DisposalSchedule.action.RETAIN_PERMANENTLY: Retain permanently +ui.facets.DisposalSchedule.retentionPeriodDuration: Retention period duration +ui.facets.DisposalSchedule.retentionPeriodIntervalCode: Retention period interval +ui.facets.DisposalSchedule.retentionPeriodIntervalCode.DAYS: Days +ui.facets.DisposalSchedule.retentionPeriodIntervalCode.WEEKS: Weeks +ui.facets.DisposalSchedule.retentionPeriodIntervalCode.MONTHS: Months +ui.facets.DisposalSchedule.retentionPeriodIntervalCode.YEARS: Years +ui.facets.DisposalSchedule.retentionPeriodIntervalCode.NO_RETENTION_PERIOD: No retention period + +ui.facets.DisposalHold.state: State +ui.facets.DisposalHold.state.ACTIVE: Active +ui.facets.DisposalHold.state.LIFTED: Lifted + +ui.facets.DisposalRule.selectionMethod: Selection method +ui.facets.DisposalRule.selectionMethod.IS_CHILD_OF: Child of +ui.facets.DisposalRule.selectionMethod.METADATA_FIELD: Metadata field # V2 ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.DisposalHoldController = Disposal holds ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.DisposalRuleController = Disposal rules diff --git a/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_pt_PT.properties b/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_pt_PT.properties index 6a341d6579..7fef8a4fee 100644 --- a/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_pt_PT.properties +++ b/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_pt_PT.properties @@ -229,6 +229,30 @@ ui.facets.IndexedJob.hasSkipped.false: sem ignorados ui.facets.IndexedJob.instanceName: Instância ui.facets.LogEntry.actionComponent: Componentes + +ui.facets.DisposalSchedule.state: Estado +ui.facets.DisposalSchedule.state.ACTIVE: Ativo +ui.facets.DisposalSchedule.state.INACTIVE: Inativo +ui.facets.DisposalSchedule.action: Ação +ui.facets.DisposalSchedule.action.DESTROY: Elminar +ui.facets.DisposalSchedule.action.REVIEW: Rever +ui.facets.DisposalSchedule.action.RETAIN_PERMANENTLY: Conservação permanente +ui.facets.DisposalSchedule.retentionPeriodDuration: Período de retenção +ui.facets.DisposalSchedule.retentionPeriodIntervalCode: Unidade de tempo do período de retenção +ui.facets.DisposalSchedule.retentionPeriodIntervalCode.DAYS: Dias +ui.facets.DisposalSchedule.retentionPeriodIntervalCode.WEEKS: Semanas +ui.facets.DisposalSchedule.retentionPeriodIntervalCode.MONTHS: Meses +ui.facets.DisposalSchedule.retentionPeriodIntervalCode.YEARS: Anos +ui.facets.DisposalSchedule.retentionPeriodIntervalCode.NO_RETENTION_PERIOD: Sem período de retenção + +ui.facets.DisposalHold.state: Estado +ui.facets.DisposalHold.state.ACTIVE: Ativo +ui.facets.DisposalHold.state.LIFTED: Levantado + +ui.facets.DisposalRule.selectionMethod: Método de seleção +ui.facets.DisposalRule.selectionMethod.IS_CHILD_OF: Filho de +ui.facets.DisposalRule.selectionMethod.METADATA_VALUE: Campo de metainformação + # V2 ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.DisposalHoldController = Suspensões de eliminação ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.DisposalRuleController = Condições de eliminação diff --git a/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages.properties b/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages.properties index ceba63c65c..07a65eefce 100644 --- a/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages.properties +++ b/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages.properties @@ -228,10 +228,10 @@ someOfAObject[org.roda.core.data.v2.formats.Format]:formats someOfAObject[org.roda.core.data.v2.ip.metadata.IndexedPreservationEvent]:preservation events someOfAObject[org.roda.core.data.v2.ip.metadata.IndexedPreservationAgent]:preservation agents someOfAObject[org.roda.core.data.v2.Void]:No input objects -someOfAObject[org.roda.core.data.v2.disposal.schedule.DisposalSchedules]:disposal schedules -someOfAObject[org.roda.core.data.v2.disposal.hold.DisposalHolds]:disposal holds +someOfAObject[org.roda.core.data.v2.disposal.schedule.DisposalSchedule]:disposal schedules +someOfAObject[org.roda.core.data.v2.disposal.hold.DisposalHold]:disposal holds someOfAObject[org.roda.core.data.v2.disposal.confirmation.DisposalConfirmation]:disposal confirmation -someOfAObject[org.roda.core.data.v2.disposal.rule.DisposalRules]:disposal rules +someOfAObject[org.roda.core.data.v2.disposal.rule.DisposalRule]:disposal rules someOfAObject[org.roda.core.data.v2.synchronization.central.DistributedInstances]:distributed instances someOfAObject[org.roda.core.data.v2.accessKey.AccessKeys]:access tokens someOfAObject[org.roda.core.data.v2.user.Group]:groups @@ -525,6 +525,9 @@ searchDropdownLabels[org.roda.core.data.v2.ip.metadata.IndexedPreservationEvent] searchDropdownLabels[org.roda.core.data.v2.formats.Format]:formats searchDropdownLabels[org.roda.core.data.v2.Void]:No input objects searchDropdownLabels[org.roda.core.data.v2.disposal.confirmation.DisposalConfirmation]:disposal confirmations +searchDropdownLabels[org.roda.core.data.v2.disposal.schedule.DisposalSchedule]:Disposal schedules +searchDropdownLabels[org.roda.core.data.v2.disposal.hold.DisposalHold]:Disposal holds +searchDropdownLabels[org.roda.core.data.v2.disposal.rule.DisposalRule]:Disposal rules searchListBoxItems:Intellectual entities searchListBoxRepresentations:Representations searchListBoxFiles:Files @@ -1459,7 +1462,10 @@ disposalScheduleStateCol:State disposalScheduleState:{0} disposalScheduleState[ACTIVE]:Active disposalScheduleState[INACTIVE]:Inactive -disposalScheduleUsedInRule:Is being used in a disposal rule +disposalScheduleUsedInRule:in use by a disposal rule +disposalScheduleRemoveConfirmDialogTitle: Remove disposal schedule +disposalScheduleRemoveConfirmDialogMessage: Are you sure you want to remove the disposal schedule? +disposalScheduleRemovedWithSuccess: Disposal schedule removed with success disposalHoldTitle:Title disposalHoldIdentifier:Identifier disposalHoldDescription:Description @@ -1505,7 +1511,7 @@ newDisposalConfirmationButton:New confirmation disassociateDisposalScheduleButton:Disassociate schedule disassociateDisposalHoldButton:Disassociate hold associateDisposalScheduleButton:Associate disposal schedule -createDisposalScheduleButton:Create schedule +createDisposalScheduleButton:Create disposal schedule disposalScheduleSelectionDialogTitle:Change disposal schedule changeDisposalScheduleActionTitle:Change disposal schedule createDisposalConfirmationActionTitle:Create disposal confirmation @@ -1522,7 +1528,7 @@ disposalActionLabel:Disposal action holdStatusLabel:Hold status disposalOnHoldStatusLabel:On hold disposalClearStatusLabel:Not on hold -disposalScheduleListAips:Records with this schedule +disposalScheduleListAips:Intellectual entities associated disposalHoldListAips:Records with this hold deleteConfirmationReportDialogTitle:Confirm disposal confirmation deletion deleteConfirmationReportDialogMessage:Are you sure you want to withdraw the intellectual entities from this disposal confirmation? @@ -1548,7 +1554,7 @@ disposalRulePreviewHelpText:Select a selection method and add the criteria. Clic disposalRulePreviewButtonText:Preview disposalTitle:Disposal conditionActualParent:Actual parent -editRulesOrder:Edit rules order +editRulesOrder:Change order editRulesOrderTop:Top editRulesOrderUp:Up editRulesOrderDown:Down @@ -1806,6 +1812,7 @@ reasonPluginIsNotIngest: The plugin is not an ingest plugin reasonRiskHasNoHistory: The risk has no history reasonCantActOnUser: Cannot execute on user reasonCantActOnGroup: Cannot execute on group +reasonHoldAlreadyLifted: The disposal hold is lifted # Details page detailsAIP: AIP @@ -1873,3 +1880,17 @@ emailViewerExternalImagesTitle=External images blocked emailViewerExternalImagesMessage=Images from the following domains are blocked to protect your privacy: emailViewerLoadImagesOnce=Load images once emailViewerAlwaysTrustSender=Always trust sender + +disposalHoldSuccessfullyCreated: Disposal hold successfully created +disposalHoldSuccessfullyUpdated: Disposal hold successfully updated + +updateDisposalRuleOrderSuccessMessage: Disposal rules order successfully updated + +moveToTop: Move to the top +moveToBottom: Move to the bottom +moveToPositionNumber: Move to position #: + +disposalRuleChangeOrderMessage: Where would you like to move them? + +disposalRuleSuccessfullyCreated: Disposal rule successfully created +disposalRuleSuccessfullyUpdated: Disposal rule successfully updated diff --git a/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_de_AT.properties b/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_de_AT.properties index 0d4b365cdb..4203bcf7b6 100644 --- a/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_de_AT.properties +++ b/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_de_AT.properties @@ -225,10 +225,10 @@ someOfAObject[org.roda.core.data.v2.formats.Format]:Formate someOfAObject[org.roda.core.data.v2.ip.metadata.IndexedPreservationEvent]:Erhaltungsmaßnahmen someOfAObject[org.roda.core.data.v2.ip.metadata.IndexedPreservationAgent]:Preservation-Werkzeuge someOfAObject[org.roda.core.data.v2.Void]:Keine Eingabeobjekte -someOfAObject[org.roda.core.data.v2.disposal.schedule.DisposalSchedules]:Entsorgungspläne -someOfAObject[org.roda.core.data.v2.disposal.hold.DisposalHolds]:Entsorgungssperren +someOfAObject[org.roda.core.data.v2.disposal.schedule.DisposalSchedule]:Entsorgungspläne +someOfAObject[org.roda.core.data.v2.disposal.hold.DisposalHold]:Entsorgungssperren someOfAObject[org.roda.core.data.v2.disposal.confirmation.DisposalConfirmation]:Entsorgungsbestätigung -someOfAObject[org.roda.core.data.v2.disposal.rule.DisposalRules]:Entsorgungsregeln +someOfAObject[org.roda.core.data.v2.disposal.rule.DisposalRule]:Entsorgungsregeln someOfAObject[org.roda.core.data.v2.synchronization.central.DistributedInstances]:verteilte Instanzen someOfAObject[org.roda.core.data.v2.accessKey.AccessKeys]:Zugangstoken oneOfAObject:{0} diff --git a/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_de_DE.properties b/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_de_DE.properties index aa64179729..58308e8e32 100644 --- a/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_de_DE.properties +++ b/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_de_DE.properties @@ -225,10 +225,10 @@ someOfAObject[org.roda.core.data.v2.formats.Format]:Formate someOfAObject[org.roda.core.data.v2.ip.metadata.IndexedPreservationEvent]:Erhaltungsereignisse someOfAObject[org.roda.core.data.v2.ip.metadata.IndexedPreservationAgent]:Erhaltungsmittel someOfAObject[org.roda.core.data.v2.Void]:Keine Eingabeobjekte -someOfAObject[org.roda.core.data.v2.disposal.schedule.DisposalSchedules]:Kassationspläne -someOfAObject[org.roda.core.data.v2.disposal.hold.DisposalHolds]:Kassationssperren +someOfAObject[org.roda.core.data.v2.disposal.schedule.DisposalSchedule]:Kassationspläne +someOfAObject[org.roda.core.data.v2.disposal.hold.DisposalHold]:Kassationssperren someOfAObject[org.roda.core.data.v2.disposal.confirmation.DisposalConfirmation]:Kassationsbestätigung -someOfAObject[org.roda.core.data.v2.disposal.rule.DisposalRules]:Kassationsregeln +someOfAObject[org.roda.core.data.v2.disposal.rule.DisposalRule]:Kassationsregeln someOfAObject[org.roda.core.data.v2.synchronization.central.DistributedInstances]:verteilte Instanzen someOfAObject[org.roda.core.data.v2.accessKey.AccessKeys]:Zugangstoken oneOfAObject:{0} diff --git a/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_es.properties b/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_es.properties index 7433cbc9b0..16ea5b2a27 100644 --- a/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_es.properties +++ b/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_es.properties @@ -225,10 +225,10 @@ someOfAObject[org.roda.core.data.v2.formats.Format]:formats someOfAObject[org.roda.core.data.v2.ip.metadata.IndexedPreservationEvent]:preservation events someOfAObject[org.roda.core.data.v2.ip.metadata.IndexedPreservationAgent]:preservation agents someOfAObject[org.roda.core.data.v2.Void]:No hay objetos de entrada -someOfAObject[org.roda.core.data.v2.disposal.schedule.DisposalSchedules]:disposal schedules -someOfAObject[org.roda.core.data.v2.disposal.hold.DisposalHolds]:disposal holds -someOfAObject[org.roda.core.data.v2.disposal.confirmation.DisposalConfirmation]:disposal confirmation -someOfAObject[org.roda.core.data.v2.disposal.rule.DisposalRules]:disposal rules +someOfAObject[org.roda.core.data.v2.disposal.schedule.DisposalSchedule]:disposal schedules +someOfAObject[org.roda.core.data.v2.disposal.hold.DisposalHold]:disposal holds +someOfAObject[org.roda.core.data.v2.disposal.confirmation.DisposalConfirmation]:disposal confirmations +someOfAObject[org.roda.core.data.v2.disposal.rule.DisposalRule]:disposal rules someOfAObject[org.roda.core.data.v2.synchronization.central.DistributedInstances]:distributed instances someOfAObject[org.roda.core.data.v2.accessKey.AccessKeys]:access tokens oneOfAObject:{0} diff --git a/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_pt_PT.properties b/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_pt_PT.properties index 1b1a887aa4..3d0fb5f840 100644 --- a/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_pt_PT.properties +++ b/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_pt_PT.properties @@ -228,10 +228,10 @@ someOfAObject[org.roda.core.data.v2.formats.Format]:formatos someOfAObject[org.roda.core.data.v2.ip.metadata.IndexedPreservationEvent]:eventos de preservação someOfAObject[org.roda.core.data.v2.ip.metadata.IndexedPreservationAgent]:agentes de preservação someOfAObject[org.roda.core.data.v2.Void]:Sem objetos de entrada -someOfAObject[org.roda.core.data.v2.disposal.schedule.DisposalSchedules]:tabelas de seleção -someOfAObject[org.roda.core.data.v2.disposal.hold.DisposalHolds]:suspensões de eliminação +someOfAObject[org.roda.core.data.v2.disposal.schedule.DisposalSchedule]:tabelas de seleção +someOfAObject[org.roda.core.data.v2.disposal.hold.DisposalHold]:suspensões de eliminação someOfAObject[org.roda.core.data.v2.disposal.confirmation.DisposalConfirmation]:autos de eliminação -someOfAObject[org.roda.core.data.v2.disposal.rule.DisposalRules]:condições de eliminação +someOfAObject[org.roda.core.data.v2.disposal.rule.DisposalRule]:condições de eliminação someOfAObject[org.roda.core.data.v2.synchronization.central.DistributedInstances]:instâncias distribuídas someOfAObject[org.roda.core.data.v2.accessKey.AccessKeys]:chaves de acesso someOfAObject[org.roda.core.data.v2.user.Group]:grupos @@ -525,6 +525,9 @@ searchDropdownLabels[org.roda.core.data.v2.ip.metadata.IndexedPreservationEvent] searchDropdownLabels[org.roda.core.data.v2.formats.Format]:formatos searchDropdownLabels[org.roda.core.data.v2.Void]:Sem objetos de entrada searchDropdownLabels[org.roda.core.data.v2.disposal.confirmation.DisposalConfirmation]:autos de eliminação +searchDropdownLabels[org.roda.core.data.v2.disposal.scheule.DisposalSchedule]:Tabelas de seleção +searchDropdownLabels[org.roda.core.data.v2.disposal.hold.DisposalHold]:Suspensões de eliminação +searchDropdownLabels[org.roda.core.data.v2.disposal.rule.DisposalRule]:Condições de eliminação searchListBoxItems:Entidades intelectuais searchListBoxRepresentations:Representações searchListBoxFiles:Ficheiros @@ -1450,6 +1453,9 @@ disposalScheduleState:{0} disposalScheduleState[ACTIVE]:Ativo disposalScheduleState[INACTIVE]:Inativo disposalScheduleUsedInRule:Está a ser usado numa codição de eliminação +disposalScheduleRemoveConfirmDialogTitle: Eliminar tabela de seleção +disposalScheduleRemoveConfirmDialogMessage: Tem a certeza que deseja eliminar esta tabela de seleção? +disposalScheduleRemovedWithSuccess: Tabela de seleção eliminada com sucesso disposalHoldTitle:Título disposalHoldIdentifier:Identificador disposalHoldDescription:Descrição @@ -1791,6 +1797,7 @@ reasonPluginIsNotIngest: O plugin não é um plugin de ingestão reasonRiskHasNoHistory: O risco não tem histórico reasonCantActOnUser: Não é possível executar num utilizador reasonCantActOnGroup: Não é possível executar num grupo +reasonHoldAlreadyLifted: A suspensão de eliminação foi levantada # Details page detailsAIP: AIP @@ -1839,4 +1846,18 @@ planningGroupLabel: Planeamento disposalGroupLabel: Avaliação e seleção editPermissionsReadOnlyPermissionsText: Leitura permissionsUpdateWithSuccess: Permissões atualizadas com sucesso -editPermissionsModalTitle: Editar permissões \ No newline at end of file +editPermissionsModalTitle: Editar permissões + +disposalHoldSuccessfullyCreated: Suspensão de eliminação criada com sucesso +disposalHoldSuccessfullyUpdated: Suspensão de eliminação atualizada com sucesso + +updateDisposalRuleOrderSuccessMessage: Ordem das condições de eliminação atualizada com sucesso + +moveToTop: Mover para o topo +moveToBottom: Mover para o fundo +moveToPositionNumber: Mover para a posição #: + +disposalRuleChangeOrderMessage: Para onde os quer mover? + +disposalRuleSuccessfullyCreated: Condição de eliminação criada com sucesso +disposalRuleSuccessfullyUpdated: Condição de eliminação atualizada com sucesso diff --git a/roda-ui/roda-wui/src/main/resources/config/roda-wui.properties b/roda-ui/roda-wui/src/main/resources/config/roda-wui.properties index 972891acbc..92f3a6e920 100644 --- a/roda-ui/roda-wui/src/main/resources/config/roda-wui.properties +++ b/roda-ui/roda-wui/src/main/resources/config/roda-wui.properties @@ -2038,6 +2038,30 @@ ui.lists.Disposal_confirmations.facets.parameters.state.sort = COUNT ui.lists.Disposal_confirmations.facets.parameters.createdBy.type = SimpleFacetParameter ui.lists.Disposal_confirmations.facets.parameters.createdBy.sort = COUNT +# Disposal Schedule on PolicyPage +ui.lists.DisposalPolicyPage_disposalSchedules.facets.parameters = state +ui.lists.DisposalPolicyPage_disposalSchedules.facets.parameters = action +ui.lists.DisposalPolicyPage_disposalSchedules.facets.parameters = retentionPeriodDuration +ui.lists.DisposalPolicyPage_disposalSchedules.facets.parameters = retentionPeriodIntervalCode +ui.lists.DisposalPolicyPage_disposalSchedules.facets.parameters.state.type = SimpleFacetParameter +ui.lists.DisposalPolicyPage_disposalSchedules.facets.parameters.state.sort = COUNT +ui.lists.DisposalPolicyPage_disposalSchedules.facets.parameters.action.type = SimpleFacetParameter +ui.lists.DisposalPolicyPage_disposalSchedules.facets.parameters.action.sort = COUNT +ui.lists.DisposalPolicyPage_disposalSchedules.facets.parameters.retentionPeriodDuration.type = SimpleFacetParameter +ui.lists.DisposalPolicyPage_disposalSchedules.facets.parameters.retentionPeriodDuration.sort = COUNT +ui.lists.DisposalPolicyPage_disposalSchedules.facets.parameters.retentionPeriodIntervalCode.type = SimpleFacetParameter +ui.lists.DisposalPolicyPage_disposalSchedules.facets.parameters.retentionPeriodIntervalCode.sort = COUNT + +# Disposal Hold on PolicyPage +ui.lists.DisposalPolicyPage_disposalHolds.facets.parameters = state +ui.lists.DisposalPolicyPage_disposalHolds.facets.parameters.state.type = SimpleFacetParameter +ui.lists.DisposalPolicyPage_disposalHolds.facets.parameters.state.sort = COUNT + +# Disposal Rule on PolicyPage +ui.lists.DisposalPolicyPage_disposalRules.facets.parameters = selectionMethod +ui.lists.DisposalPolicyPage_disposalRules.facets.parameters.selectionMethod.type = SimpleFacetParameter +ui.lists.DisposalPolicyPage_disposalRules.facets.parameters.selectionMethod.sort = COUNT + ########################################################################## # List configuration # From 03a9d0910833105f6b2ed1d62c9df548d1ec9248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Guimar=C3=A3es?= Date: Wed, 29 Apr 2026 21:11:34 +0100 Subject: [PATCH 09/37] Update create/edit user and group forms to use the new generic form --- .../resources/config/roda-roles.properties | 1 + .../config/i18n/client/ClientMessages.java | 12 + .../api/v2/controller/MembersController.java | 36 +- .../wui/api/v2/services/MembersService.java | 5 +- .../common/BrowseObjectActionsToolbar.java | 3 +- .../wui/client/common/NoActionsToolbar.java | 33 ++ .../common/actions/RODAMemberAction.java | 1 + .../common/actions/RODAMemberActions.java | 479 ++++++++++-------- .../RODAMemberSearchWrapperActions.java | 155 ++++++ .../actions/RODAMemberToolbarActions.java | 308 +---------- .../common/dialogs/RODAMembersDialogs.java | 332 +++--------- .../client/common/forms/GenericDataForm.java | 380 ++++++++++++++ .../common/forms/GenericDataForm.ui.xml | 14 + .../{ => common/forms}/GenericDataPanel.java | 2 +- .../client/common/lists/RodaMemberList.java | 2 +- .../pagination/ListSelectionStateMappers.java | 2 +- .../roda/wui/client/common/resources/main.gss | 144 +++++- .../common/search/SearchFieldPanel.java | 1 - .../client/common/utils/FormUtilities.java | 131 +++-- .../client/common/utils/HtmlSnippetUtils.java | 4 + .../disposal/hold/DisposalHoldDataPanel.java | 3 +- .../disposal/rule/DisposalRuleDataPanel.java | 2 +- .../schedule/DisposalScheduleDataPanel.java | 2 +- .../roda/wui/client/main/BreadcrumbUtils.java | 51 +- .../org/roda/wui/client/main/UserMenu.java | 2 +- .../management/members/CreateGroup.java | 123 +++++ .../management/members/CreateGroup.ui.xml | 23 + .../client/management/members/CreateUser.java | 131 +++++ .../management/members/CreateUser.ui.xml | 21 + .../management/members/CreateUserPanel.java | 303 ----------- .../management/members/CreateUserPanel.ui.xml | 49 -- .../client/management/members/EditGroup.java | 125 +++++ .../management/members/EditGroup.ui.xml | 24 + .../client/management/members/EditUser.java | 133 +++++ .../client/management/members/EditUser.ui.xml | 23 + .../management/members/GroupDataPanel.java | 194 ------- .../management/members/GroupDataPanel.ui.xml | 28 - .../management/members/GroupSelect.java | 284 ----------- .../management/members/MemberManagement.java | 29 +- .../management/members/PasswordPanel.java | 45 +- .../management/members/PermissionsPanel.java | 291 ----------- .../client/management/members/Profile.java | 2 +- .../client/management/members/Register.java | 2 +- .../client/management/members/ShowMember.java | 6 +- .../management/members/UserDataPanel.java | 331 +++--------- .../management/members/UserDetailsPanel.java | 8 - .../members/data/panels/GroupDataPanel.java | 109 ++++ .../data/panels/PasswordDataPanel.java | 189 +++++++ .../members/data/panels/UserDataPanel.java | 183 +++++++ .../members/tabs/PermissionsPanel.java | 2 +- .../members/tabs/RODAMemberDetailsPanel.java | 48 +- .../tabs/RODAMemberDetailsPanel.ui.xml | 2 +- .../client/services/MembersRestService.java | 10 +- .../i18n/client/ClientMessages.properties | 9 +- .../client/ClientMessages_pt_PT.properties | 7 +- 55 files changed, 2512 insertions(+), 2327 deletions(-) create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/NoActionsToolbar.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberSearchWrapperActions.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/forms/GenericDataForm.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/forms/GenericDataForm.ui.xml rename roda-ui/roda-wui/src/main/java/org/roda/wui/client/{ => common/forms}/GenericDataPanel.java (77%) create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateGroup.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateGroup.ui.xml create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateUser.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateUser.ui.xml delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateUserPanel.java delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateUserPanel.ui.xml create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/EditGroup.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/EditGroup.ui.xml create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/EditUser.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/EditUser.ui.xml delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/GroupDataPanel.java delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/GroupDataPanel.ui.xml delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/GroupSelect.java delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/PermissionsPanel.java delete mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/UserDetailsPanel.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/data/panels/GroupDataPanel.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/data/panels/PasswordDataPanel.java create mode 100644 roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/data/panels/UserDataPanel.java diff --git a/roda-core/roda-core/src/main/resources/config/roda-roles.properties b/roda-core/roda-core/src/main/resources/config/roda-roles.properties index 519beb7efd..4f9b9b786d 100644 --- a/roda-core/roda-core/src/main/resources/config/roda-roles.properties +++ b/roda-core/roda-core/src/main/resources/config/roda-roles.properties @@ -94,6 +94,7 @@ core.roles.org.roda.wui.api.v2.controller.JobsController.createJob = job.manage core.roles.org.roda.wui.api.v2.controller.JobsController.obtainJobCommand = job.manage # Member roles +core.roles.org.roda.wui.api.v2.controller.MembersController.getMember = member.read core.roles.org.roda.wui.api.v2.controller.MembersController.getUser = member.read core.roles.org.roda.wui.api.v2.controller.MembersController.getGroup = member.read core.roles.org.roda.wui.api.v2.controller.MembersController.deleteMultipleMembers = member.manage diff --git a/roda-ui/roda-wui/src/main/java/config/i18n/client/ClientMessages.java b/roda-ui/roda-wui/src/main/java/config/i18n/client/ClientMessages.java index ee0e07f230..ed183a59c2 100644 --- a/roda-ui/roda-wui/src/main/java/config/i18n/client/ClientMessages.java +++ b/roda-ui/roda-wui/src/main/java/config/i18n/client/ClientMessages.java @@ -2773,4 +2773,16 @@ SafeHtml representationInformationAssociatedWithDescription(String field, String String disposalRuleSuccessfullyCreated(); String disposalRuleSuccessfullyUpdated(); + + String userSuccessfullyCreated(); + + String userSuccessfullyUpdated(); + + String groupSuccessfullyCreated(); + + String groupSuccessfullyUpdated(); + + String failedToUpdateGroup(); + + String failedToUpdateUser(); } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/controller/MembersController.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/controller/MembersController.java index 5c81bcfc11..45632027a2 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/controller/MembersController.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/controller/MembersController.java @@ -79,7 +79,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.ldap.NamingException; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -274,7 +273,7 @@ private static Set mapCasGroupstoRODAGroups(Set casGroups) { } @Override - public RODAMember getUser(String name) { + public RODAMember getMember(String name) { RequestContext requestContext = RequestUtils.parseHTTPRequest(request); ControllerAssistant controllerAssistant = new ControllerAssistant() {}; LogEntryState state = LogEntryState.SUCCESS; @@ -298,6 +297,26 @@ public RODAMember getUser(String name) { } } + @Override + public User getUser(String name) { + RequestContext requestContext = RequestUtils.parseHTTPRequest(request); + ControllerAssistant controllerAssistant = new ControllerAssistant() {}; + LogEntryState state = LogEntryState.SUCCESS; + + try { + // check user permissions + controllerAssistant.checkRoles(requestContext.getUser()); + + return membersService.retrieveUser(name); + } catch (RODAException e) { + state = LogEntryState.FAILURE; + throw new RESTException(e); + } finally { + // register action + controllerAssistant.registerAction(requestContext, state, RodaConstants.CONTROLLER_USERNAME_PARAM, name); + } + } + @Override public User getAuthenticatedUser() { User user = UserUtility.getUser(request); @@ -617,7 +636,7 @@ public Group createGroup(@RequestBody CreateGroupRequest groupRequest) { } @Override - public Void updateGroup(@RequestBody Group modifiedGroup) { + public Group updateGroup(@RequestBody Group modifiedGroup) { ControllerAssistant controllerAssistant = new ControllerAssistant() {}; RequestContext requestContext = RequestUtils.parseHTTPRequest(request); LogEntryState state = LogEntryState.SUCCESS; @@ -626,7 +645,7 @@ public Void updateGroup(@RequestBody Group modifiedGroup) { // check user permissions controllerAssistant.checkRoles(requestContext.getUser()); // delegate - membersService.updateGroup(modifiedGroup); + return membersService.updateGroup(modifiedGroup); } catch (RODAException e) { state = LogEntryState.FAILURE; throw new RESTException(e); @@ -634,7 +653,6 @@ public Void updateGroup(@RequestBody Group modifiedGroup) { // register action controllerAssistant.registerAction(requestContext, state, RodaConstants.CONTROLLER_GROUP_PARAM, modifiedGroup); } - return null; } @Override @@ -721,7 +739,7 @@ public Group removeMembersFromGroup(String id, String userId) { } finally { // register action controllerAssistant.registerAction(requestContext, state, RodaConstants.CONTROLLER_GROUP_PARAM, id, - RodaConstants.CONTROLLER_USER_PARAM, userId); + RodaConstants.CONTROLLER_USER_PARAM, userId); } } @@ -786,7 +804,7 @@ public User updateUser(@RequestBody UpdateUserRequest userRequest) { } finally { // register action controllerAssistant.registerAction(requestContext, state, RodaConstants.CONTROLLER_USER_PARAM, - requestContext.getUser()); + requestContext.getUser().getFullName()); } } @@ -808,7 +826,7 @@ public User updateMyUser(@RequestBody UpdateUserRequest userOperations) { } finally { // register action controllerAssistant.registerAction(requestContext, state, RodaConstants.CONTROLLER_USER_PARAM, - userOperations.getUser()); + userOperations.getUser().getFullName()); } } @@ -997,7 +1015,7 @@ public LongResponse count(@RequestBody CountRequest countRequest) { } @Override - public List suggest(SuggestRequest suggestRequest) { + public List suggest(@RequestBody SuggestRequest suggestRequest) { return indexService.suggest(suggestRequest, RODAMember.class); } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/services/MembersService.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/services/MembersService.java index b81eaf2149..8ef3124a3a 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/services/MembersService.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/api/v2/services/MembersService.java @@ -271,9 +271,10 @@ public Group createGroup(Group group) throws GenericException, AlreadyExistsExce return newGroup; } - public void updateGroup(Group group) throws GenericException, NotFoundException, AuthorizationDeniedException { - RodaCoreFactory.getModelService().updateGroup(group, true); + public Group updateGroup(Group group) throws GenericException, NotFoundException, AuthorizationDeniedException { + Group updatedGroup = RodaCoreFactory.getModelService().updateGroup(group, true); RodaCoreFactory.getIndexService().commit(true, RODAMember.class); + return updatedGroup; } public void deleteGroup(String groupname) throws GenericException, AuthorizationDeniedException { diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/BrowseObjectActionsToolbar.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/BrowseObjectActionsToolbar.java index 9f59bb8e96..2171a95656 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/BrowseObjectActionsToolbar.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/BrowseObjectActionsToolbar.java @@ -13,12 +13,11 @@ import org.roda.core.data.v2.index.IsIndexed; import org.roda.core.data.v2.ip.AIPState; import org.roda.core.data.v2.ip.Permissions; -import org.roda.wui.client.GenericDataPanel; +import org.roda.wui.client.common.forms.GenericDataPanel; import org.roda.wui.client.common.actions.Actionable; import org.roda.wui.client.common.labels.Tag; import com.google.gwt.user.client.rpc.AsyncCallback; -import org.roda.wui.client.disposal.rule.DisposalRuleDataPanel; /** * diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/NoActionsToolbar.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/NoActionsToolbar.java new file mode 100644 index 0000000000..d34e7c773c --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/NoActionsToolbar.java @@ -0,0 +1,33 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE file at the root of the source + * tree and available online at + * + * https://github.com/keeps/roda + */ +package org.roda.wui.client.common; + +import org.roda.core.data.v2.user.RODAMember; + +/** + * + * @author Miguel Guimarães + */ +public class NoActionsToolbar extends BrowseObjectActionsToolbar { + + public void build() { + setObjectAndBuild(null, null, null); + } + + public void buildIcon() { + // do nothing + } + + public void buildTags() { + // do nothing + } + + public void buildActions() { + this.actions.clear(); + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberAction.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberAction.java index 1ed5b7418b..8d49a19c1d 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberAction.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberAction.java @@ -13,6 +13,7 @@ public enum RODAMemberAction implements Actionable.Action { NEW_USER(RodaConstants.PERMISSION_METHOD_CREATE_USER), NEW_GROUP(RodaConstants.PERMISSION_METHOD_CREATE_GROUP), ACTIVATE(RodaConstants.PERMISSION_METHOD_UPDATE_USER), DEACTIVATE(RodaConstants.PERMISSION_METHOD_UPDATE_USER), + CHANGE_PASSWORD(RodaConstants.PERMISSION_METHOD_UPDATE_USER), EDIT(RodaConstants.PERMISSION_METHOD_UPDATE_USER), REMOVE(RodaConstants.PERMISSION_METHOD_DELETE_USER), ADD_NEW_GROUP(RodaConstants.PERMISSION_METHOD_UPDATE_USER), ADD_NEW_MEMBER(RodaConstants.PERMISSION_METHOD_UPDATE_USER), diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberActions.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberActions.java index f0f50550aa..27ff71bdc4 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberActions.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberActions.java @@ -1,203 +1,75 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE file at the root of the source - * tree and available online at - * - * https://github.com/keeps/roda - */ package org.roda.wui.client.common.actions; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - -import com.google.gwt.i18n.client.LocaleInfo; +import com.google.gwt.core.client.GWT; +import com.google.gwt.safehtml.shared.SafeHtmlUtils; +import com.google.gwt.user.client.rpc.AsyncCallback; +import config.i18n.client.ClientMessages; +import org.roda.core.common.pekko.messages.events.EventUserUpdated; +import org.roda.core.data.common.RodaConstants; +import org.roda.core.data.common.SecureString; import org.roda.core.data.utils.SelectedItemsUtils; +import org.roda.core.data.v2.accessKey.CreateAccessKeyRequest; +import org.roda.core.data.v2.index.filter.BasicSearchFilterParameter; +import org.roda.core.data.v2.index.filter.Filter; +import org.roda.core.data.v2.index.filter.NotSimpleFilterParameter; import org.roda.core.data.v2.index.select.SelectedItems; +import org.roda.core.data.v2.user.Group; import org.roda.core.data.v2.user.RODAMember; +import org.roda.core.data.v2.user.User; import org.roda.core.data.v2.user.requests.ChangeUserStatusRequest; -import org.roda.core.data.v2.user.requests.CreateGroupRequest; -import org.roda.core.data.v2.user.requests.CreateUserRequest; +import org.roda.core.data.v2.user.requests.UpdateUserRequest; +import org.roda.wui.client.common.NoAsyncCallback; import org.roda.wui.client.common.actions.callbacks.ActionAsyncCallback; -import org.roda.wui.client.common.actions.model.ActionableBundle; -import org.roda.wui.client.common.actions.model.ActionableGroup; +import org.roda.wui.client.common.actions.callbacks.ActionNoAsyncCallback; +import org.roda.wui.client.common.dialogs.AccessKeyDialogs; import org.roda.wui.client.common.dialogs.Dialogs; import org.roda.wui.client.common.dialogs.RODAMembersDialogs; import org.roda.wui.client.ingest.process.ShowJob; +import org.roda.wui.client.management.members.CreateGroup; +import org.roda.wui.client.management.members.CreateUser; +import org.roda.wui.client.management.members.EditGroup; +import org.roda.wui.client.management.members.EditUser; import org.roda.wui.client.management.members.MemberManagement; +import org.roda.wui.client.management.members.tabs.PermissionsPanel; import org.roda.wui.client.services.Services; import org.roda.wui.common.client.tools.HistoryUtils; import org.roda.wui.common.client.widgets.Toast; -import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.rpc.AsyncCallback; - -import config.i18n.client.ClientMessages; +import java.util.HashSet; +import java.util.List; /** - * @author Bruno Ferreira + * @author Miguel Guimarães */ -public class RODAMemberActions extends AbstractActionable { - private static final ClientMessages messages = GWT.create(ClientMessages.class); - private static final RODAMemberActions GENERAL_INSTANCE = new RODAMemberActions(); - - private static final Set POSSIBLE_ACTIONS_WITHOUT_MEMBER = new HashSet<>( - Arrays.asList(RODAMemberAction.NEW_USER, RODAMemberAction.NEW_GROUP)); - private static final Set POSSIBLE_ACTIONS_ON_USER = new HashSet<>( - Arrays.asList(RODAMemberAction.ACTIVATE, RODAMemberAction.DEACTIVATE, RODAMemberAction.REMOVE)); - - private static final Set POSSIBLE_ACTIONS_ON_GROUP = new HashSet<>( - Arrays.asList(RODAMemberAction.REMOVE)); - - private static final Set POSSIBLE_ACTIONS_ON_MEMBERS = new HashSet<>( - Arrays.asList(RODAMemberAction.ACTIVATE, RODAMemberAction.DEACTIVATE, RODAMemberAction.REMOVE)); +public class RODAMemberActions { + private static final ClientMessages messages = GWT.create(ClientMessages.class); private RODAMemberActions() { - // do nothing - } - - public static RODAMemberActions get() { - return GENERAL_INSTANCE; - } - - @Override - public RODAMemberAction[] getActions() { - return RODAMemberAction.values(); - } - - @Override - public RODAMemberAction actionForName(String name) { - return RODAMemberAction.valueOf(name); - } - - @Override - public CanActResult userCanAct(Action action) { - return new CanActResult(hasPermissions(action), CanActResult.Reason.USER, messages.reasonUserLacksPermission()); - } - - @Override - public CanActResult contextCanAct(Action action) { - return new CanActResult(POSSIBLE_ACTIONS_WITHOUT_MEMBER.contains(action), CanActResult.Reason.CONTEXT, - messages.reasonNoObjectSelected()); - } - - @Override - public CanActResult userCanAct(Action action, RODAMember object) { - return new CanActResult(hasPermissions(action), CanActResult.Reason.USER, messages.reasonUserLacksPermission()); - } - - @Override - public CanActResult contextCanAct(Action action, RODAMember object) { - if (object.isUser()) { - return new CanActResult((action.equals(RODAMemberAction.DEACTIVATE) && object.isActive()) - || (action.equals(RODAMemberAction.ACTIVATE) && !object.isActive()) || (action.equals(RODAMemberAction.REMOVE)), - CanActResult.Reason.CONTEXT, messages.reasonCantActOnUser()); - } else { - return new CanActResult(POSSIBLE_ACTIONS_ON_GROUP.contains(action), CanActResult.Reason.CONTEXT, - messages.reasonCantActOnGroup()); - } - } - - @Override - public CanActResult userCanAct(Action action, SelectedItems objects) { - return new CanActResult(hasPermissions(action), CanActResult.Reason.CONTEXT, messages.reasonCantActOnGroup()); - } - - @Override - public CanActResult contextCanAct(Action action, SelectedItems objects) { - return new CanActResult(POSSIBLE_ACTIONS_ON_MEMBERS.contains(action), CanActResult.Reason.CONTEXT, - messages.reasonCantActOnMultipleObjects()); + // private constructor } - @Override - public void act(Action action, AsyncCallback callback) { - if (action.equals(RODAMemberAction.NEW_USER)) { - createUser(callback); - } else if (action.equals(RODAMemberAction.NEW_GROUP)) { - createGroup(callback); - } else { - unsupportedAction(action, callback); - } + public static void createUser(AsyncCallback callback) { + HistoryUtils.newHistory(CreateUser.RESOLVER.getHistoryPath()); + callback.onSuccess(Actionable.ActionImpact.UPDATED); } - @Override - public void act(Action action, RODAMember object, AsyncCallback callback) { - if (action.equals(RODAMemberAction.ACTIVATE)) { - activate(objectToSelectedItems(object, RODAMember.class), callback); - } else if (action.equals(RODAMemberAction.DEACTIVATE)) { - deactivate(objectToSelectedItems(object, RODAMember.class), callback); - } else if (action.equals(RODAMemberAction.REMOVE)) { - remove(objectToSelectedItems(object, RODAMember.class), callback); - } else { - unsupportedAction(action, callback); - } + public static void createGroup(AsyncCallback callback) { + HistoryUtils.newHistory(CreateGroup.RESOLVER.getHistoryPath()); + callback.onSuccess(Actionable.ActionImpact.UPDATED); } - @Override - public void act(Action action, SelectedItems objects, AsyncCallback callback) { - if (action.equals(RODAMemberAction.ACTIVATE)) { - activate(objects, callback); - } else if (action.equals(RODAMemberAction.DEACTIVATE)) { - deactivate(objects, callback); - } else if (action.equals(RODAMemberAction.REMOVE)) { - remove(objects, callback); + public static void edit(RODAMember member, AsyncCallback callback) { + if (member.isUser()) { + HistoryUtils.newHistory(EditUser.RESOLVER, member.getId()); + callback.onSuccess(Actionable.ActionImpact.UPDATED); } else { - unsupportedAction(action, callback); + HistoryUtils.newHistory(EditGroup.RESOLVER, member.getId()); + callback.onSuccess(Actionable.ActionImpact.UPDATED); } } - private void activate(SelectedItems objects, AsyncCallback callback) { - Services services = new Services("Activate RODA member", "activate"); - ChangeUserStatusRequest request = new ChangeUserStatusRequest(SelectedItemsUtils.convertToRESTRequest(objects), - true); - services.membersResource(s -> s.changeActive(request)).whenComplete((res, error) -> { - if (error == null) { - Toast.showInfo(messages.runningInBackgroundTitle(), messages.runningInBackgroundDescription()); - Dialogs.showJobRedirectDialog(messages.jobCreatedMessage(), new AsyncCallback() { - - @Override - public void onFailure(Throwable caught) { - callback.onSuccess(Actionable.ActionImpact.UPDATED); - HistoryUtils.newHistory(MemberManagement.RESOLVER.getHistoryPath()); - } - - @Override - public void onSuccess(final Void nothing) { - callback.onSuccess(ActionImpact.NONE); - HistoryUtils.newHistory(ShowJob.RESOLVER, res.getId()); - } - }); - } - }); - } - - private void deactivate(SelectedItems objects, AsyncCallback callback) { - Services services = new Services("Deactivate RODA member", "deactivate"); - ChangeUserStatusRequest request = new ChangeUserStatusRequest(SelectedItemsUtils.convertToRESTRequest(objects), - false); - services.membersResource(s -> s.changeActive(request)).whenComplete((res, error) -> { - if (error == null) { - Toast.showInfo(messages.runningInBackgroundTitle(), messages.runningInBackgroundDescription()); - Dialogs.showJobRedirectDialog(messages.jobCreatedMessage(), new AsyncCallback() { - - @Override - public void onFailure(Throwable caught) { - callback.onSuccess(Actionable.ActionImpact.UPDATED); - HistoryUtils.newHistory(MemberManagement.RESOLVER.getHistoryPath()); - } - - @Override - public void onSuccess(final Void nothing) { - callback.onSuccess(ActionImpact.NONE); - HistoryUtils.newHistory(ShowJob.RESOLVER, res.getId()); - } - }); - } - }); - } - - private void remove(SelectedItems objects, AsyncCallback callback) { + public static void remove(SelectedItems objects, AsyncCallback callback) { Dialogs.showConfirmDialog(messages.userRemoveConfirmDialogTitle(), messages.userRemoveConfirmDialogMessage(), messages.dialogNo(), messages.dialogYes(), new AsyncCallback() { @Override @@ -221,71 +93,256 @@ public void onFailure(Throwable caught) { }); } - private void createUser(AsyncCallback callback) { - RODAMembersDialogs.createUser(messages.addUserButton(), messages.cancelButton(), messages.saveButton(), - new ActionAsyncCallback(callback) { + public static void remove(SelectedItems items, RODAMember object, + AsyncCallback callback) { + Dialogs.showConfirmDialog( + object.isUser() ? messages.singleUserRemoveConfirmDialogTitle() : messages.singleGroupRemoveConfirmDialogTitle(), + object.isUser() ? messages.singleUserRemoveConfirmDialogMessage(object.getId()) + : messages.singleGroupRemoveConfirmDialogMessage(object.getId()), + messages.dialogNo(), messages.dialogYes(), new ActionAsyncCallback(callback) { @Override - public void onSuccess(CreateUserRequest result) { - Services services = new Services("Create User", "create"); - services.membersResource(s -> s.createUser(result, LocaleInfo.getCurrentLocale().getLocaleName())) - .whenComplete((res, error) -> { + public void onSuccess(Boolean confirmed) { + if (confirmed) { + Services services = new Services("Remove RODA members", "remove"); + services.membersResource(s -> s.deleteMultipleMembers(SelectedItemsUtils.convertToRESTRequest(items))) + .whenComplete((res, error) -> { + if (error == null) { + callback.onSuccess(Actionable.ActionImpact.DESTROYED); + } else { + Toast.showError(error.getMessage()); + callback.onSuccess(Actionable.ActionImpact.NONE); + } + }); + } else { + doActionCallbackNone(); + } + } + + @Override + public void onFailure(Throwable caught) { + callback.onFailure(caught); + } + }); + } + + public static void activate(SelectedItems objects, AsyncCallback callback) { + Dialogs.showConfirmDialog(messages.activateUserTitle(), messages.activateUserConfirmationMessage(), + messages.dialogNo(), messages.dialogYes(), new ActionAsyncCallback(callback) { + @Override + public void onSuccess(Boolean confirmed) { + if (confirmed) { + Services services = new Services("Activate RODA member", "activate"); + ChangeUserStatusRequest request = new ChangeUserStatusRequest( + SelectedItemsUtils.convertToRESTRequest(objects), true); + services.membersResource(s -> s.changeActive(request)).whenComplete((res, error) -> { if (error == null) { - Toast.showInfo(messages.users(), "User successfully created"); - doActionCallbackUpdated(); - } else { - doActionCallbackNone(); + Toast.showInfo(messages.runningInBackgroundTitle(), messages.runningInBackgroundDescription()); + Dialogs.showJobRedirectDialog(messages.jobCreatedMessage(), new AsyncCallback() { + + @Override + public void onFailure(Throwable caught) { + callback.onSuccess(Actionable.ActionImpact.UPDATED); + HistoryUtils.newHistory(MemberManagement.RESOLVER.getHistoryPath()); + } + + @Override + public void onSuccess(final Void nothing) { + callback.onSuccess(Actionable.ActionImpact.NONE); + HistoryUtils.newHistory(ShowJob.RESOLVER, res.getId()); + } + }); } }); + } else { + doActionCallbackNone(); + } } @Override public void onFailure(Throwable caught) { - doActionCallbackNone(); + callback.onFailure(caught); } }); } - private void createGroup(AsyncCallback callback) { - RODAMembersDialogs.createGroup(messages.addGroupButton(), messages.cancelButton(), messages.saveButton(), - new ActionAsyncCallback(callback) { - + public static void deactivate(SelectedItems objects, AsyncCallback callback) { + Dialogs.showConfirmDialog(messages.deactivateUserTitle(), messages.deactivateUserConfirmationMessage(), + messages.dialogNo(), messages.dialogYes(), new ActionAsyncCallback(callback) { @Override - public void onSuccess(CreateGroupRequest createGroupRequest) { - Services services = new Services("Create Group", "create"); - services.membersResource(s -> s.createGroup(createGroupRequest)).whenComplete((res, error) -> { - if (error == null) { - Toast.showInfo(messages.groups(), "Group successfully created"); - doActionCallbackUpdated(); - } else { - doActionCallbackNone(); - } - }); + public void onSuccess(Boolean confirmed) { + if (confirmed) { + + Services services = new Services("Deactivate RODA member", "deactivate"); + ChangeUserStatusRequest request = new ChangeUserStatusRequest( + SelectedItemsUtils.convertToRESTRequest(objects), false); + services.membersResource(s -> s.changeActive(request)).whenComplete((res, error) -> { + if (error == null) { + Toast.showInfo(messages.runningInBackgroundTitle(), messages.runningInBackgroundDescription()); + Dialogs.showJobRedirectDialog(messages.jobCreatedMessage(), new AsyncCallback() { + + @Override + public void onFailure(Throwable caught) { + callback.onSuccess(Actionable.ActionImpact.UPDATED); + HistoryUtils.newHistory(MemberManagement.RESOLVER.getHistoryPath()); + } + + @Override + public void onSuccess(final Void nothing) { + callback.onSuccess(Actionable.ActionImpact.NONE); + HistoryUtils.newHistory(ShowJob.RESOLVER, res.getId()); + } + }); + } + }); + } else { + doActionCallbackNone(); + } } @Override public void onFailure(Throwable caught) { doActionCallbackNone(); + callback.onFailure(caught); + } + }); + } + + public static void addNewMember(RODAMember object, AsyncCallback callback) { + Filter filter = new Filter(new BasicSearchFilterParameter(RodaConstants.MEMBERS_IS_USER, "true")); + ((Group) object).getUsers() + .forEach(user -> filter.add(new NotSimpleFilterParameter(RodaConstants.MEMBERS_NAME, user))); + + RODAMembersDialogs.showAddGroupsToRODAMember(SafeHtmlUtils.fromSafeConstant(messages.addNewMemberToGroupTitle()), + messages.cancelButton(), messages.confirmButton(), filter, + new ActionAsyncCallback>(callback) { + @Override + public void onSuccess(SelectedItems result) { + Services services = new Services("Add member to RODA group", "update"); + services + .membersResource(s -> s.addMembersToGroup(object.getId(), SelectedItemsUtils.convertToRESTRequest(result))) + .whenComplete((group, error) -> { + if (error != null) { + doActionCallbackUpdated(); + Toast.showError(error.getMessage()); + } else { + doActionCallbackUpdated(); + Toast.showInfo(messages.membersTabTitle(), messages.memberSuccessfullyAdded()); + } + }); + } + }); + } + + public static void addNewGroup(RODAMember object, AsyncCallback callback) { + Filter filter = new Filter(new BasicSearchFilterParameter(RodaConstants.MEMBERS_IS_USER, "false")); + ((User) object).getGroups() + .forEach(group -> filter.add(new NotSimpleFilterParameter(RodaConstants.MEMBERS_NAME, group))); + + RODAMembersDialogs.showAddGroupsToRODAMember(SafeHtmlUtils.fromSafeConstant(messages.addNewGroupModalTitle()), + messages.cancelButton(), messages.confirmButton(), filter, + new ActionAsyncCallback>(callback) { + @Override + public void onSuccess(SelectedItems result) { + Services services = new Services("Add groups to RODA member", "update"); + services + .membersResource(s -> s.addGroupsToUser(object.getId(), SelectedItemsUtils.convertToRESTRequest(result))) + .whenComplete((res, error) -> { + if (error != null) { + doActionCallbackUpdated(); + Toast.showError(error.getMessage()); + } else { + doActionCallbackUpdated(); + Toast.showInfo(messages.groups(), messages.groupSuccessfullyAdded()); + } + }); } }); } - @Override - public ActionableBundle createActionsBundle() { - ActionableBundle transferredResourcesActionableBundle = new ActionableBundle<>(); + public static void createNewAccessKey(AsyncCallback callback, RODAMember user) { + callback.onSuccess(Actionable.ActionImpact.NONE); + AccessKeyDialogs.createAccessKeyDialog(messages.createAccessKeyTitle(), null, true, + new ActionNoAsyncCallback(callback) { + @Override + public void onSuccess(CreateAccessKeyRequest keyRequest) { + Services services = new Services("Create access key", "create"); + CreateAccessKeyRequest createAccessKeyRequest = new CreateAccessKeyRequest(keyRequest.getName(), + keyRequest.getExpirationDate()); + services.membersResource(s -> s.createAccessKey(user.getId(), createAccessKeyRequest)) + .whenComplete((response, error) -> { + if (response != null) { + AccessKeyDialogs.showAccessKeyDialog(messages.accessKeyLabel(), response, + new NoAsyncCallback() { + @Override + public void onSuccess(Boolean result) { + callback.onSuccess(Actionable.ActionImpact.UPDATED); + } + }); + } + }); + } + }); + } + + public static void changePassword(RODAMember object, AsyncCallback callback) { + RODAMembersDialogs.setUserPassword(messages.userDataChangePassword(), new ActionAsyncCallback(callback) { + @Override + public void onSuccess(String result) { + UpdateUserRequest request = new UpdateUserRequest(); + request.setUser((User) object); + request.setPassword(new SecureString(result.toCharArray())); + request.setValues(((User) object).getExtra()); + + Services services = new Services("Update user password", "update"); + services.membersResource(s -> s.updateUser(request)).whenComplete((res, error) -> { + if (error != null) { + callback.onSuccess(Actionable.ActionImpact.NONE); + } else { + callback.onSuccess(Actionable.ActionImpact.NONE); + Toast.showError(messages.editUserFailure(object.getFullName(), error.getMessage())); + } + }); + } + }); + } - // ACTIONS - ActionableGroup actionableGroup = new ActionableGroup<>(messages.sidebarActionsTitle()); - actionableGroup.addButton(messages.addUserButton(), RODAMemberAction.NEW_USER, ActionImpact.UPDATED, - "btn-plus-circle"); - actionableGroup.addButton(messages.addGroupButton(), RODAMemberAction.NEW_GROUP, ActionImpact.UPDATED, - "btn-plus-circle"); - actionableGroup.addButton(messages.editUserActivate(), RODAMemberAction.ACTIVATE, ActionImpact.UPDATED, - "btn-enable-user"); - actionableGroup.addButton(messages.editUserDeactivate(), RODAMemberAction.DEACTIVATE, ActionImpact.UPDATED, - "btn-disable-user"); - actionableGroup.addButton(messages.editUserRemove(), RODAMemberAction.REMOVE, ActionImpact.DESTROYED, "btn-ban"); + public static void editPermissions(RODAMember object, AsyncCallback callback) { + PermissionsPanel permissionsPanel = new PermissionsPanel(object, false, true); - transferredResourcesActionableBundle.addGroup(actionableGroup); - return transferredResourcesActionableBundle; + RODAMembersDialogs.showEditRODAMemberPermissionsPanel(messages.editPermissionsModalTitle(), messages.cancelButton(), + messages.updateButton(), permissionsPanel, new ActionAsyncCallback>(callback) { + @Override + public void onSuccess(List result) { + Services services = new Services("Edit RODA member permissions", "update"); + if (object.isUser()) { + UpdateUserRequest request = new UpdateUserRequest(); + User user = (User) object; + user.setDirectRoles(new HashSet<>(result)); + request.setPassword(null); + request.setUser(user); + services.membersResource(s -> s.updateUser(request)).whenComplete((res, error) -> { + if (error != null) { + doActionCallbackUpdated(); + Toast.showError(error.getMessage()); + } else { + doActionCallbackUpdated(); + Toast.showInfo(messages.userPermissions(), messages.permissionsUpdateWithSuccess()); + } + }); + } else { + Group group = (Group) object; + group.setDirectRoles(new HashSet<>(result)); + services.membersResource(s -> s.updateGroup(group)).whenComplete((res, error) -> { + if (error != null) { + doActionCallbackUpdated(); + Toast.showError(error.getMessage()); + } else { + doActionCallbackUpdated(); + Toast.showInfo(messages.groupPermissions(), messages.permissionsUpdateWithSuccess()); + } + }); + } + } + }); } } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberSearchWrapperActions.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberSearchWrapperActions.java new file mode 100644 index 0000000000..7c57436c0f --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberSearchWrapperActions.java @@ -0,0 +1,155 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE file at the root of the source + * tree and available online at + * + * https://github.com/keeps/roda + */ +package org.roda.wui.client.common.actions; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.roda.core.data.v2.index.select.SelectedItems; +import org.roda.core.data.v2.user.RODAMember; +import org.roda.wui.client.common.actions.model.ActionableBundle; +import org.roda.wui.client.common.actions.model.ActionableGroup; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.rpc.AsyncCallback; + +import config.i18n.client.ClientMessages; + +/** + * @author Bruno Ferreira + */ +public class RODAMemberSearchWrapperActions extends AbstractActionable { + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private static final RODAMemberSearchWrapperActions GENERAL_INSTANCE = new RODAMemberSearchWrapperActions(); + + private static final Set POSSIBLE_ACTIONS_WITHOUT_MEMBER = new HashSet<>( + Arrays.asList(RODAMemberAction.NEW_USER, RODAMemberAction.NEW_GROUP)); + + private static final Set POSSIBLE_ACTIONS_ON_GROUP = new HashSet<>( + Arrays.asList(RODAMemberAction.REMOVE)); + + private static final Set POSSIBLE_ACTIONS_ON_MEMBERS = new HashSet<>( + Arrays.asList(RODAMemberAction.ACTIVATE, RODAMemberAction.DEACTIVATE, RODAMemberAction.REMOVE)); + + private RODAMemberSearchWrapperActions() { + // do nothing + } + + public static RODAMemberSearchWrapperActions get() { + return GENERAL_INSTANCE; + } + + @Override + public RODAMemberAction[] getActions() { + return RODAMemberAction.values(); + } + + @Override + public RODAMemberAction actionForName(String name) { + return RODAMemberAction.valueOf(name); + } + + @Override + public CanActResult userCanAct(Action action) { + return new CanActResult(hasPermissions(action), CanActResult.Reason.USER, messages.reasonUserLacksPermission()); + } + + @Override + public CanActResult contextCanAct(Action action) { + return new CanActResult(POSSIBLE_ACTIONS_WITHOUT_MEMBER.contains(action), CanActResult.Reason.CONTEXT, + messages.reasonNoObjectSelected()); + } + + @Override + public CanActResult userCanAct(Action action, RODAMember object) { + return new CanActResult(hasPermissions(action), CanActResult.Reason.USER, messages.reasonUserLacksPermission()); + } + + @Override + public CanActResult contextCanAct(Action action, RODAMember object) { + if (object.isUser()) { + return new CanActResult((action.equals(RODAMemberAction.DEACTIVATE) && object.isActive()) + || (action.equals(RODAMemberAction.ACTIVATE) && !object.isActive()) || (action.equals(RODAMemberAction.REMOVE)), + CanActResult.Reason.CONTEXT, messages.reasonCantActOnUser()); + } else { + return new CanActResult(POSSIBLE_ACTIONS_ON_GROUP.contains(action), CanActResult.Reason.CONTEXT, + messages.reasonCantActOnGroup()); + } + } + + @Override + public CanActResult userCanAct(Action action, SelectedItems objects) { + return new CanActResult(hasPermissions(action), CanActResult.Reason.CONTEXT, messages.reasonCantActOnGroup()); + } + + @Override + public CanActResult contextCanAct(Action action, SelectedItems objects) { + return new CanActResult(POSSIBLE_ACTIONS_ON_MEMBERS.contains(action), CanActResult.Reason.CONTEXT, + messages.reasonCantActOnMultipleObjects()); + } + + @Override + public void act(Action action, AsyncCallback callback) { + if (action.equals(RODAMemberAction.NEW_USER)) { + RODAMemberActions.createUser(callback); + } else if (action.equals(RODAMemberAction.NEW_GROUP)) { + RODAMemberActions.createGroup(callback); + } else { + unsupportedAction(action, callback); + } + } + + @Override + public void act(Action action, RODAMember object, AsyncCallback callback) { + if (action.equals(RODAMemberAction.ACTIVATE)) { + RODAMemberActions.activate(objectToSelectedItems(object, RODAMember.class), callback); + } else if (action.equals(RODAMemberAction.DEACTIVATE)) { + RODAMemberActions.deactivate(objectToSelectedItems(object, RODAMember.class), callback); + } else if (action.equals(RODAMemberAction.REMOVE)) { + RODAMemberActions.remove(objectToSelectedItems(object, RODAMember.class), callback); + } else if (action.equals(RODAMemberAction.EDIT)) { + RODAMemberActions.edit(object, callback); + } else { + unsupportedAction(action, callback); + } + } + + @Override + public void act(Action action, SelectedItems objects, AsyncCallback callback) { + if (action.equals(RODAMemberAction.ACTIVATE)) { + RODAMemberActions.activate(objects, callback); + } else if (action.equals(RODAMemberAction.DEACTIVATE)) { + RODAMemberActions.deactivate(objects, callback); + } else if (action.equals(RODAMemberAction.REMOVE)) { + RODAMemberActions.remove(objects, callback); + } else { + unsupportedAction(action, callback); + } + } + + @Override + public ActionableBundle createActionsBundle() { + ActionableBundle transferredResourcesActionableBundle = new ActionableBundle<>(); + + // ACTIONS + ActionableGroup actionableGroup = new ActionableGroup<>(messages.sidebarActionsTitle()); + actionableGroup.addButton(messages.addUserButton(), RODAMemberAction.NEW_USER, ActionImpact.UPDATED, + "btn-plus-circle"); + actionableGroup.addButton(messages.addGroupButton(), RODAMemberAction.NEW_GROUP, ActionImpact.UPDATED, + "btn-plus-circle"); + actionableGroup.addButton(messages.editUserActivate(), RODAMemberAction.ACTIVATE, ActionImpact.UPDATED, + "btn-enable-user"); + actionableGroup.addButton(messages.editUserDeactivate(), RODAMemberAction.DEACTIVATE, ActionImpact.UPDATED, + "btn-disable-user"); + actionableGroup.addButton(messages.editUserRemove(), RODAMemberAction.REMOVE, ActionImpact.DESTROYED, "btn-ban"); + + transferredResourcesActionableBundle.addGroup(actionableGroup); + return transferredResourcesActionableBundle; + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberToolbarActions.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberToolbarActions.java index dcb8f6350c..427c9481c3 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberToolbarActions.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/actions/RODAMemberToolbarActions.java @@ -1,39 +1,15 @@ package org.roda.wui.client.common.actions; import com.google.gwt.core.client.GWT; -import com.google.gwt.safehtml.shared.SafeHtmlUtils; import com.google.gwt.user.client.rpc.AsyncCallback; import config.i18n.client.ClientMessages; -import org.roda.core.data.common.RodaConstants; -import org.roda.core.data.utils.SelectedItemsUtils; -import org.roda.core.data.v2.accessKey.CreateAccessKeyRequest; -import org.roda.core.data.v2.index.filter.BasicSearchFilterParameter; -import org.roda.core.data.v2.index.filter.Filter; -import org.roda.core.data.v2.index.filter.NotSimpleFilterParameter; -import org.roda.core.data.v2.index.select.SelectedItems; -import org.roda.core.data.v2.user.Group; import org.roda.core.data.v2.user.RODAMember; -import org.roda.core.data.v2.user.User; -import org.roda.core.data.v2.user.requests.ChangeUserStatusRequest; -import org.roda.core.data.v2.user.requests.UpdateUserRequest; -import org.roda.wui.client.common.NoAsyncCallback; -import org.roda.wui.client.common.actions.callbacks.ActionAsyncCallback; -import org.roda.wui.client.common.actions.callbacks.ActionNoAsyncCallback; import org.roda.wui.client.common.actions.model.ActionableBundle; import org.roda.wui.client.common.actions.model.ActionableGroup; -import org.roda.wui.client.common.dialogs.AccessKeyDialogs; -import org.roda.wui.client.common.dialogs.Dialogs; -import org.roda.wui.client.common.dialogs.RODAMembersDialogs; -import org.roda.wui.client.ingest.process.ShowJob; -import org.roda.wui.client.management.members.tabs.PermissionsPanel; -import org.roda.wui.client.services.Services; -import org.roda.wui.common.client.tools.HistoryUtils; -import org.roda.wui.common.client.widgets.Toast; import java.util.Arrays; import java.util.HashSet; -import java.util.List; import java.util.Set; /** @@ -77,7 +53,7 @@ public CanActResult contextCanAct(Action action, RODAMember object) return new CanActResult( (action.equals(RODAMemberAction.DEACTIVATE) && object.isActive()) || (action.equals(RODAMemberAction.ACTIVATE) && !object.isActive()) - || (action.equals(RODAMemberAction.REMOVE) + || (action.equals(RODAMemberAction.REMOVE) || (action.equals(RODAMemberAction.CHANGE_PASSWORD)) || (action.equals(RODAMemberAction.EDIT) || action.equals(RODAMemberAction.EDIT_PERMISSIONS) || (action.equals(RODAMemberAction.ADD_NEW_GROUP) && object.isUser()) || (action.equals(RODAMemberAction.NEW_ACCESS_KEY) && object.isUser()))), @@ -91,289 +67,28 @@ public CanActResult contextCanAct(Action action, RODAMember object) @Override public void act(Action action, RODAMember object, AsyncCallback callback) { if (action.equals(RODAMemberAction.ACTIVATE)) { - activate(object, callback); + RODAMemberActions.activate(objectToSelectedItems(object, RODAMember.class), callback); } else if (action.equals(RODAMemberAction.DEACTIVATE)) { - deactivate(object, callback); + RODAMemberActions.deactivate(objectToSelectedItems(object, RODAMember.class), callback); } else if (action.equals(RODAMemberAction.EDIT)) { - edit(object, callback); + RODAMemberActions.edit(object, callback); } else if (action.equals(RODAMemberAction.REMOVE)) { - remove(object, callback); + RODAMemberActions.remove(objectToSelectedItems(object, RODAMember.class), object, callback); } else if (action.equals(RODAMemberAction.NEW_ACCESS_KEY)) { - createNewAccessKey(callback, object); + RODAMemberActions.createNewAccessKey(callback, object); } else if (action.equals(RODAMemberAction.ADD_NEW_GROUP)) { - addNewGroup(object, callback); + RODAMemberActions.addNewGroup(object, callback); } else if (action.equals(RODAMemberAction.ADD_NEW_MEMBER)) { - addNewMember(object, callback); + RODAMemberActions.addNewMember(object, callback); } else if (action.equals(RODAMemberAction.EDIT_PERMISSIONS)) { - editPermissions(object, callback); + RODAMemberActions.editPermissions(object, callback); + } else if (action.equals(RODAMemberAction.CHANGE_PASSWORD)) { + RODAMemberActions.changePassword(object, callback); } else { unsupportedAction(action, callback); } } - private void editPermissions(RODAMember object, AsyncCallback callback) { - PermissionsPanel permissionsPanel = new PermissionsPanel(object, false, true); - - RODAMembersDialogs.showEditRODAMemberPermissionsPanel(messages.editPermissionsModalTitle(), messages.cancelButton(), - messages.updateButton(), permissionsPanel, new ActionAsyncCallback>(callback) { - @Override - public void onSuccess(List result) { - Services services = new Services("Edit RODA member permissions", "update"); - if (object.isUser()) { - UpdateUserRequest request = new UpdateUserRequest(); - User user = (User) object; - user.setDirectRoles(new HashSet<>(result)); - request.setPassword(null); - request.setUser(user); - services.membersResource(s -> s.updateUser(request)).whenComplete((res, error) -> { - if (error != null) { - doActionCallbackUpdated(); - Toast.showError(error.getMessage()); - } else { - doActionCallbackUpdated(); - Toast.showInfo(messages.userPermissions(), messages.permissionsUpdateWithSuccess()); - } - }); - } else { - Group group = (Group) object; - group.setDirectRoles(new HashSet<>(result)); - services.membersResource(s -> s.updateGroup(group)).whenComplete((res, error) -> { - if (error != null) { - doActionCallbackUpdated(); - Toast.showError(error.getMessage()); - } else { - doActionCallbackUpdated(); - Toast.showInfo(messages.groupPermissions(), messages.permissionsUpdateWithSuccess()); - } - }); - } - } - }); - } - - private void addNewMember(RODAMember object, AsyncCallback callback) { - Filter filter = new Filter(new BasicSearchFilterParameter(RodaConstants.MEMBERS_IS_USER, "true")); - ((Group) object).getUsers() - .forEach(user -> filter.add(new NotSimpleFilterParameter(RodaConstants.MEMBERS_NAME, user))); - - RODAMembersDialogs.showAddGroupsToRODAMember(SafeHtmlUtils.fromSafeConstant(messages.addNewMemberToGroupTitle()), - messages.cancelButton(), messages.confirmButton(), filter, - new ActionAsyncCallback>(callback) { - @Override - public void onSuccess(SelectedItems result) { - Services services = new Services("Add member to RODA group", "update"); - services - .membersResource(s -> s.addMembersToGroup(object.getId(), SelectedItemsUtils.convertToRESTRequest(result))) - .whenComplete((group, error) -> { - if (error != null) { - doActionCallbackUpdated(); - Toast.showError(error.getMessage()); - } else { - doActionCallbackUpdated(); - Toast.showInfo(messages.membersTabTitle(), messages.memberSuccessfullyAdded()); - } - }); - } - }); - } - - private void addNewGroup(RODAMember object, AsyncCallback callback) { - Filter filter = new Filter(new BasicSearchFilterParameter(RodaConstants.MEMBERS_IS_USER, "false")); - ((User) object).getGroups() - .forEach(group -> filter.add(new NotSimpleFilterParameter(RodaConstants.MEMBERS_NAME, group))); - - RODAMembersDialogs.showAddGroupsToRODAMember(SafeHtmlUtils.fromSafeConstant(messages.addNewGroupModalTitle()), - messages.cancelButton(), messages.confirmButton(), filter, - new ActionAsyncCallback>(callback) { - @Override - public void onSuccess(SelectedItems result) { - Services services = new Services("Add groups to RODA member", "update"); - services - .membersResource(s -> s.addGroupsToUser(object.getId(), SelectedItemsUtils.convertToRESTRequest(result))) - .whenComplete((res, error) -> { - if (error != null) { - doActionCallbackUpdated(); - Toast.showError(error.getMessage()); - } else { - doActionCallbackUpdated(); - Toast.showInfo(messages.groups(), messages.groupSuccessfullyAdded()); - } - }); - } - }); - } - - private void activate(RODAMember object, AsyncCallback callback) { - Dialogs.showConfirmDialog(messages.activateUserTitle(), messages.activateUserConfirmationMessage(), - messages.dialogNo(), messages.dialogYes(), new ActionAsyncCallback(callback) { - @Override - public void onSuccess(Boolean confirmed) { - if (confirmed) { - Services services = new Services("Activate RODA member", "activate"); - ChangeUserStatusRequest request = new ChangeUserStatusRequest( - SelectedItemsUtils.convertToRESTRequest(objectToSelectedItems(object, RODAMember.class)), true); - services.membersResource(s -> s.changeActive(request)).whenComplete((res, error) -> { - if (error == null) { - Toast.showInfo(messages.runningInBackgroundTitle(), messages.runningInBackgroundDescription()); - Dialogs.showJobRedirectDialog(messages.jobCreatedMessage(), new AsyncCallback() { - - @Override - public void onFailure(Throwable caught) { - doActionCallbackUpdated(); - } - - @Override - public void onSuccess(final Void nothing) { - callback.onSuccess(ActionImpact.NONE); - HistoryUtils.newHistory(ShowJob.RESOLVER, res.getId()); - } - }); - } - }); - } else { - doActionCallbackNone(); - } - } - - @Override - public void onFailure(Throwable caught) { - callback.onFailure(caught); - } - }); - } - - private void edit(RODAMember object, AsyncCallback callback) { - if (object.isUser()) { - RODAMembersDialogs.editUserInformation(messages.editUserTitle(), messages.cancelButton(), messages.updateButton(), - (User) object, new ActionAsyncCallback(callback) { - @Override - public void onSuccess(UpdateUserRequest result) { - Services services = new Services("Update User", "update"); - services.membersResource(s -> s.updateUser(result)).whenComplete((res, error) -> { - if (error == null) { - Toast.showInfo(messages.users(), "User successfully updated"); - doActionCallbackUpdated(); - } else { - doActionCallbackNone(); - } - }); - } - }); - } else { - RODAMembersDialogs.editGroupInformation(messages.editGroupTitle(), messages.cancelButton(), - messages.updateButton(), (Group) object, new ActionAsyncCallback(callback) { - @Override - public void onSuccess(Group result) { - Services services = new Services("Update group", "update"); - services.membersResource(s -> s.updateGroup(result)).whenComplete((res, error) -> { - if (error == null) { - Toast.showInfo(messages.groups(), "Group successfully updated"); - doActionCallbackUpdated(); - } else { - doActionCallbackNone(); - } - }); - } - }); - } - } - - private void deactivate(RODAMember object, AsyncCallback callback) { - Dialogs.showConfirmDialog(messages.deactivateUserTitle(), messages.deactivateUserConfirmationMessage(), - messages.dialogNo(), messages.dialogYes(), new ActionAsyncCallback(callback) { - @Override - public void onSuccess(Boolean confirmed) { - if (confirmed) { - Services services = new Services("Deactivate RODA member", "deactivate"); - ChangeUserStatusRequest request = new ChangeUserStatusRequest( - SelectedItemsUtils.convertToRESTRequest(objectToSelectedItems(object, RODAMember.class)), false); - services.membersResource(s -> s.changeActive(request)).whenComplete((res, error) -> { - if (error == null) { - Toast.showInfo(messages.runningInBackgroundTitle(), messages.runningInBackgroundDescription()); - Dialogs.showJobRedirectDialog(messages.jobCreatedMessage(), new AsyncCallback() { - - @Override - public void onFailure(Throwable caught) { - doActionCallbackUpdated(); - } - - @Override - public void onSuccess(final Void nothing) { - callback.onSuccess(ActionImpact.NONE); - HistoryUtils.newHistory(ShowJob.RESOLVER, res.getId()); - } - }); - } - }); - } else { - doActionCallbackNone(); - } - } - - @Override - public void onFailure(Throwable caught) { - doActionCallbackNone(); - callback.onFailure(caught); - } - }); - } - - private void remove(RODAMember object, AsyncCallback callback) { - Dialogs.showConfirmDialog(object.isUser() ? messages.singleUserRemoveConfirmDialogTitle() : messages.singleGroupRemoveConfirmDialogTitle(), - object.isUser() ? messages.singleUserRemoveConfirmDialogMessage(object.getId()) : messages.singleGroupRemoveConfirmDialogMessage(object.getId()), messages.dialogNo(), messages.dialogYes(), - new ActionAsyncCallback(callback) { - @Override - public void onSuccess(Boolean confirmed) { - if (confirmed) { - Services services = new Services("Remove RODA members", "remove"); - services - .membersResource(s -> s.deleteMultipleMembers( - SelectedItemsUtils.convertToRESTRequest(objectToSelectedItems(object, RODAMember.class)))) - .whenComplete((res, error) -> { - if (error == null) { - callback.onSuccess(Actionable.ActionImpact.DESTROYED); - } else { - Toast.showError(error.getMessage()); - callback.onSuccess(Actionable.ActionImpact.NONE); - } - }); - } else { - doActionCallbackNone(); - } - } - - @Override - public void onFailure(Throwable caught) { - callback.onFailure(caught); - } - }); - } - - private void createNewAccessKey(AsyncCallback callback, RODAMember user) { - callback.onSuccess(ActionImpact.NONE); - AccessKeyDialogs.createAccessKeyDialog(messages.createAccessKeyTitle(), null, true, - new ActionNoAsyncCallback(callback) { - @Override - public void onSuccess(CreateAccessKeyRequest keyRequest) { - Services services = new Services("Create access key", "create"); - CreateAccessKeyRequest createAccessKeyRequest = new CreateAccessKeyRequest(keyRequest.getName(), - keyRequest.getExpirationDate()); - services.membersResource(s -> s.createAccessKey(user.getId(), createAccessKeyRequest)) - .whenComplete((response, error) -> { - if (response != null) { - AccessKeyDialogs.showAccessKeyDialog(messages.accessKeyLabel(), response, - new NoAsyncCallback() { - @Override - public void onSuccess(Boolean result) { - callback.onSuccess(ActionImpact.UPDATED); - } - }); - } - }); - } - }); - } - @Override public ActionableBundle createActionsBundle() { ActionableBundle actionableBundle = new ActionableBundle<>(); @@ -381,6 +96,7 @@ public ActionableBundle createActionsBundle() { // ACTIONS ActionableGroup actionableGroup = new ActionableGroup<>(messages.manage(), "btn-edit"); actionableGroup.addButton(messages.editUserAction(), RODAMemberAction.EDIT, ActionImpact.UPDATED, "btn-edit"); + actionableGroup.addButton(messages.userDataChangePassword(), RODAMemberAction.CHANGE_PASSWORD, ActionImpact.UPDATED, "btn-password"); actionableGroup.addButton(messages.editUserActivate(), RODAMemberAction.ACTIVATE, ActionImpact.UPDATED, "btn-enable-user"); diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/dialogs/RODAMembersDialogs.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/dialogs/RODAMembersDialogs.java index 470f484abc..00e031f987 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/dialogs/RODAMembersDialogs.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/dialogs/RODAMembersDialogs.java @@ -3,35 +3,35 @@ import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.i18n.client.DateTimeFormat; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.DialogBox; import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.PasswordTextBox; +import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.Widget; +import com.google.gwt.user.datepicker.client.DateBox; import config.i18n.client.ClientMessages; -import org.roda.core.data.common.SecureString; +import org.roda.core.data.v2.accessKey.CreateAccessKeyRequest; import org.roda.core.data.v2.index.filter.Filter; import org.roda.core.data.v2.index.select.SelectedItems; import org.roda.core.data.v2.index.select.SelectedItemsList; import org.roda.core.data.v2.index.select.SelectedItemsNone; -import org.roda.core.data.v2.user.Group; import org.roda.core.data.v2.user.RODAMember; -import org.roda.core.data.v2.user.User; -import org.roda.core.data.v2.user.requests.CreateGroupRequest; -import org.roda.core.data.v2.user.requests.CreateUserRequest; -import org.roda.core.data.v2.user.requests.UpdateUserRequest; import org.roda.wui.client.common.lists.RodaMemberList; import org.roda.wui.client.common.lists.utils.AsyncTableCell; import org.roda.wui.client.common.lists.utils.AsyncTableCellOptions; import org.roda.wui.client.common.lists.utils.ListBuilder; import org.roda.wui.client.common.search.SearchWrapper; -import org.roda.wui.client.management.members.CreateUserPanel; -import org.roda.wui.client.management.members.GroupDataPanel; -import org.roda.wui.client.management.members.UserDataPanel; +import org.roda.wui.client.management.members.PasswordPanel; +import org.roda.wui.client.management.members.data.panels.PasswordDataPanel; import org.roda.wui.client.management.members.tabs.PermissionsPanel; +import org.roda.wui.common.client.tools.StringUtils; -import java.util.HashSet; +import java.util.Date; import java.util.List; /** @@ -45,267 +45,6 @@ private RODAMembersDialogs() { // private method } - public static void createGroup(final String title, final String cancelButtonText, final String saveButtonText, - final AsyncCallback callback) { - final DialogBox dialogBox = new DialogBox(false, true); - dialogBox.setHTML(title); - dialogBox.addStyleName("create-group-dialog"); - - final FlowPanel layout = new FlowPanel(); - - dialogBox.addStyleName("wui-dialog-prompt"); - layout.addStyleName("wui-dialog-layout"); - - final FlowPanel buttonPanel = new FlowPanel(); - final Button cancelButton = new Button(cancelButtonText); - final Button saveButton = new Button(saveButtonText); - buttonPanel.add(cancelButton); - buttonPanel.add(saveButton); - - final FlowPanel content = new FlowPanel(); - GroupDataPanel groupDataPanel = new GroupDataPanel(false); - - content.add(groupDataPanel); - layout.add(content); - layout.add(buttonPanel); - dialogBox.setWidget(layout); - - dialogBox.setGlassEnabled(true); - dialogBox.setAnimationEnabled(false); - - cancelButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - dialogBox.hide(); - callback.onFailure(null); - } - }); - - saveButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - if (!groupDataPanel.isChanged()) { - dialogBox.hide(); - callback.onFailure(null); - } else { - if (groupDataPanel.isValid()) { - dialogBox.hide(); - Group group = groupDataPanel.getGroup(); - CreateGroupRequest createGroupRequest = new CreateGroupRequest(group.getName(), group.getFullName(), - new HashSet<>()); - callback.onSuccess(createGroupRequest); - } - } - } - }); - - cancelButton.addStyleName("btn btn-link"); - saveButton.addStyleName("pull-right btn btn-play"); - - dialogBox.center(); - dialogBox.show(); - } - - public static void createUser(final String title, final String cancelButtonText, final String saveButtonText, - final AsyncCallback callback) { - final DialogBox dialogBox = new DialogBox(false, true); - dialogBox.setHTML(title); - dialogBox.addStyleName("create-user-dialog"); - - final FlowPanel layout = new FlowPanel(); - - dialogBox.addStyleName("wui-dialog-prompt"); - layout.addStyleName("wui-dialog-layout"); - - final FlowPanel buttonPanel = new FlowPanel(); - final Button cancelButton = new Button(cancelButtonText); - final Button saveButton = new Button(saveButtonText); - buttonPanel.add(cancelButton); - buttonPanel.add(saveButton); - - final FlowPanel content = new FlowPanel(); - CreateUserPanel userPanel = new CreateUserPanel(); - - userPanel.setOnFormReadyCallback(new Runnable() { - @Override - public void run() { - dialogBox.center(); - } - }); - - content.add(userPanel); - layout.add(content); - layout.add(buttonPanel); - dialogBox.setWidget(layout); - - dialogBox.setGlassEnabled(true); - dialogBox.setAnimationEnabled(false); - - cancelButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - dialogBox.hide(); - callback.onFailure(null); - } - }); - - saveButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - if (!userPanel.isChanged()) { - dialogBox.hide(); - callback.onFailure(null); - } else { - if (userPanel.isValid()) { - dialogBox.hide(); - User user = userPanel.getUser(); - CreateUserRequest request = new CreateUserRequest(user.getEmail(), user.getName(), user.getFullName(), - new HashSet<>(), new HashSet<>(), user.isGuest(), null, userPanel.getUserExtra()); - callback.onSuccess(request); - } - } - } - }); - - cancelButton.addStyleName("btn btn-link"); - saveButton.addStyleName("pull-right btn btn-play"); - - dialogBox.center(); - dialogBox.show(); - } - - public static void editGroupInformation(final String title, final String cancelButtonText, final String saveButtonText, - Group group, final AsyncCallback callback) { - final DialogBox dialogBox = new DialogBox(false, true); - dialogBox.setHTML(title); - dialogBox.addStyleName("edit-group-information-dialog"); - - final FlowPanel layout = new FlowPanel(); - - dialogBox.addStyleName("wui-dialog-prompt"); - layout.addStyleName("wui-dialog-layout"); - - final FlowPanel buttonPanel = new FlowPanel(); - final Button cancelButton = new Button(cancelButtonText); - final Button saveButton = new Button(saveButtonText); - buttonPanel.add(cancelButton); - buttonPanel.add(saveButton); - - final FlowPanel content = new FlowPanel(); - GroupDataPanel groupPanel = new GroupDataPanel(true); - groupPanel.setGroupNameReadOnly(true); - groupPanel.setGroup(group); - - content.add(groupPanel); - layout.add(content); - layout.add(buttonPanel); - dialogBox.setWidget(layout); - - dialogBox.setGlassEnabled(true); - dialogBox.setAnimationEnabled(false); - - cancelButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - dialogBox.hide(); - callback.onFailure(null); - } - }); - - saveButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - if (!groupPanel.isChanged()) { - dialogBox.hide(); - callback.onFailure(null); - } else { - if (groupPanel.isValid()) { - dialogBox.hide(); - callback.onSuccess(groupPanel.getGroup()); - } - } - } - }); - - cancelButton.addStyleName("btn btn-link"); - saveButton.addStyleName("pull-right btn btn-play"); - - dialogBox.center(); - dialogBox.show(); - } - - public static void editUserInformation(final String title, final String cancelButtonText, final String saveButtonText, - User user, final AsyncCallback callback) { - final DialogBox dialogBox = new DialogBox(false, true); - dialogBox.setHTML(title); - dialogBox.addStyleName("edit-user-information-dialog"); - - final FlowPanel layout = new FlowPanel(); - - dialogBox.addStyleName("wui-dialog-prompt"); - layout.addStyleName("wui-dialog-layout"); - - final FlowPanel buttonPanel = new FlowPanel(); - final Button cancelButton = new Button(cancelButtonText); - final Button saveButton = new Button(saveButtonText); - buttonPanel.add(cancelButton); - buttonPanel.add(saveButton); - - final FlowPanel content = new FlowPanel(); - UserDataPanel userPanel = new UserDataPanel(true, true); - userPanel.setUsernameReadOnly(true); - userPanel.setUser(user); - userPanel.setUserExtra(user.getExtra()); - - content.add(userPanel); - layout.add(content); - layout.add(buttonPanel); - dialogBox.setWidget(layout); - - dialogBox.setGlassEnabled(true); - dialogBox.setAnimationEnabled(false); - - cancelButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - dialogBox.hide(); - callback.onFailure(null); - } - }); - - saveButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - if (!userPanel.isChanged()) { - dialogBox.hide(); - callback.onFailure(null); - } else { - if (userPanel.isValid()) { - dialogBox.hide(); - UpdateUserRequest request = new UpdateUserRequest(); - request.setUser(userPanel.getUser()); - request.getUser().setDirectRoles(user.getDirectRoles()); - request.getUser().setGroups(user.getGroups()); - if (userPanel.isPasswordChanged()) { - SecureString securePassword = new SecureString(userPanel.getPassword().toCharArray()); - request.setPassword(securePassword); - } else { - request.setPassword(null); - } - request.setValues(userPanel.getUserExtra()); - callback.onSuccess(request); - } - } - } - }); - - cancelButton.addStyleName("btn btn-link"); - saveButton.addStyleName("pull-right btn btn-play"); - - dialogBox.center(); - dialogBox.show(); - } - public static void showEditRODAMemberPermissionsPanel(final String title, final String cancelButtonText, final String saveButtonText, Widget panel, final AsyncCallback> callback) { final DialogBox dialogBox = new DialogBox(false, true); @@ -442,4 +181,55 @@ public void onSelectionChange(SelectedItems selected) { return container; } + public static void setUserPassword(String title, final AsyncCallback callback) { + final DialogBox dialogBox = new DialogBox(false, true); + final Button cancelButton = new Button(messages.cancelButton()); + final Button confirmButton = new Button(messages.confirmButton()); + final FlowPanel layout = new FlowPanel(); + final FlowPanel header = new FlowPanel(); + final FlowPanel footer = new FlowPanel(); + + PasswordDataPanel passwordDataPanel = new PasswordDataPanel(true); + + dialogBox.setText(title); + layout.add(header); + layout.add(footer); + header.add(passwordDataPanel); + + footer.add(cancelButton); + footer.add(confirmButton); + + confirmButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent clickEvent) { + if (passwordDataPanel.isValid()) { + dialogBox.hide(); + callback.onSuccess(passwordDataPanel.getValue()); + } + } + }); + + cancelButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent clickEvent) { + dialogBox.hide(); + callback.onSuccess(null); + } + }); + + dialogBox.setWidget(layout); + dialogBox.setGlassEnabled(true); + dialogBox.setAnimationEnabled(false); + + dialogBox.addStyleName("wui-dialog-prompt"); + layout.addStyleName("wui-dialog-layout"); + header.addStyleName("wui-dialog-message"); + footer.addStyleName("wui-dialog-layout-footer"); + + confirmButton.addStyleName("btn btn-play"); + cancelButton.addStyleName("btn btn-link"); + + dialogBox.center(); + dialogBox.show(); + } } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/forms/GenericDataForm.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/forms/GenericDataForm.java new file mode 100644 index 0000000000..3a0b9261aa --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/forms/GenericDataForm.java @@ -0,0 +1,380 @@ +package org.roda.wui.client.common.forms; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Function; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.ChangeHandler; +import com.google.gwt.event.dom.client.KeyUpHandler; +import com.google.gwt.event.logical.shared.HasValueChangeHandlers; +import com.google.gwt.event.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.InlineHTML; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.TextBox; +import com.google.gwt.user.client.ui.Widget; + +/** + * @author Miguel Guimarães + */ + +public class GenericDataForm extends Composite implements GenericDataPanel, HasValueChangeHandlers { + + private static GenericDataFormUiBinder uiBinder = GWT.create(GenericDataFormUiBinder.class); + + // Use the new Interface so we can mix TextBoxes and InlineHTML bindings + private final List bindings = new ArrayList<>(); + + @UiField + FlowPanel fieldsContainer; + + @UiField + HTML errors; + + private T model; + private boolean changed = false; + + public GenericDataForm() { + initWidget(uiBinder.createAndBindUi(this)); + } + + /** + * Initializes the form with the object to be edited. + */ + public void setModel(T model) { + this.model = model; + this.changed = false; + refreshUI(); + } + + @Override + public T getValue() { + // Flush UI values back to the model + for (FormBinding binding : bindings) { + binding.flushToModel(model); + } + return model; + } + + @Override + public boolean isValid() { + List errorMessages = new ArrayList<>(); + boolean valid = true; + + for (FormBinding binding : bindings) { + if (!binding.validate()) { + valid = false; + errorMessages.add("Field '" + binding.getLabelText() + "' is invalid or mandatory."); + } + } + + if (!valid) { + StringBuilder sb = new StringBuilder(); + for (String err : errorMessages) { + sb.append("").append(err).append("
"); + } + errors.setHTML(sb.toString()); + errors.setVisible(true); + } else { + errors.setVisible(false); + } + + return valid; + } + + public HTML getErrors() { + return errors; + } + + public boolean isChanged() { + return changed; + } + + public void addCustomWidget(Widget widget) { + fieldsContainer.add(widget); + } + + /** + * Adds a generic read-only field to the form dynamically using InlineHTML. + * + * @param labelText The label to display + * @param getter Function to extract the value from the model + */ + public void addReadOnlyField(String labelText, Function getter) { + addReadOnlyField(labelText, getter, false); + } + + /** + * Adds a generic read-only field to the form dynamically using InlineHTML. + * + * @param labelText The label to display + * @param getter Function to extract the value from the model + * @param mandatory Whether this field is visually marked as required + */ + public void addReadOnlyField(String labelText, Function getter, boolean mandatory) { + FlowPanel searchField = new FlowPanel(); + searchField.addStyleName("generic-form-field"); + + FlowPanel leftPanel = new FlowPanel(); + leftPanel.addStyleName("generic-form-field-left-panel"); + + Label label = new Label(labelText); + if (mandatory) { + label.setText(labelText + "*"); + } + label.addStyleName("form-label"); + + FlowPanel inputPanel = new FlowPanel(); + inputPanel.addStyleName("generic-form-field-input-panel full_width"); + + InlineHTML inlineHTML = new InlineHTML(); + // Use setText instead of setHTML to automatically escape inputs and prevent XSS + inlineHTML.addStyleName("form-readonly-value"); + + // Assemble the DOM + inputPanel.add(inlineHTML); + leftPanel.add(label); + leftPanel.add(inputPanel); + searchField.add(leftPanel); + + // Add to main container + fieldsContainer.add(searchField); + + // Register the read-only binding + bindings.add(new ReadOnlyFieldBinding(labelText, inlineHTML, getter)); + } + + public void addTextField(String labelText, Function getter, BiConsumer setter, + boolean mandatory) { + addTextField(labelText, getter, setter, mandatory, false); + } + + public void addTextField(String labelText, Function getter, BiConsumer setter, + boolean mandatory, boolean readOnly) { + + // Route to the new InlineHTML method if readOnly is true + if (readOnly) { + addReadOnlyField(labelText, getter, mandatory); + return; + } + + FlowPanel searchField = new FlowPanel(); + searchField.addStyleName("generic-form-field"); + + FlowPanel leftPanel = new FlowPanel(); + leftPanel.addStyleName("generic-form-field-left-panel"); + + Label label = new Label(labelText); + if (mandatory) { + label.setText(labelText + "*"); + } + + label.addStyleName("form-label"); + + FlowPanel inputPanel = new FlowPanel(); + inputPanel.addStyleName("generic-form-field-input-panel full_width"); + + TextBox textBox = new TextBox(); + textBox.addStyleName("form-textbox"); + + // Assemble the DOM + inputPanel.add(textBox); + leftPanel.add(label); + leftPanel.add(inputPanel); + searchField.add(leftPanel); + + // Add to main container + fieldsContainer.add(searchField); + + // Setup handlers to track changes + ChangeHandler changeHandler = event -> onChange(); + KeyUpHandler keyUpHandler = event -> onChange(); + textBox.addChangeHandler(changeHandler); + textBox.addKeyUpHandler(keyUpHandler); + + // Register the binding + bindings.add(new TextFieldBinding(labelText, textBox, getter, setter, mandatory, null, null)); + } + + public void addTextField(String labelText, Function getter, BiConsumer setter, + boolean mandatory, boolean readOnly, String regex, String regexErrorMessage) { + + // Route to the new InlineHTML method if readOnly is true + if (readOnly) { + addReadOnlyField(labelText, getter, mandatory); + return; + } + + FlowPanel searchField = new FlowPanel(); + searchField.addStyleName("generic-form-field"); + + FlowPanel leftPanel = new FlowPanel(); + leftPanel.addStyleName("generic-form-field-left-panel"); + + Label label = new Label(labelText); + if (mandatory) { + label.setText(labelText + "*"); + } + label.addStyleName("form-label"); + + FlowPanel inputPanel = new FlowPanel(); + inputPanel.addStyleName("generic-form-field-input-panel full_width"); + + TextBox textBox = new TextBox(); + textBox.addStyleName("form-textbox"); + + inputPanel.add(textBox); + leftPanel.add(label); + leftPanel.add(inputPanel); + searchField.add(leftPanel); + fieldsContainer.add(searchField); + + ChangeHandler changeHandler = event -> onChange(); + KeyUpHandler keyUpHandler = event -> onChange(); + textBox.addChangeHandler(changeHandler); + textBox.addKeyUpHandler(keyUpHandler); + + // Register binding with regex parameters + bindings.add(new TextFieldBinding(labelText, textBox, getter, setter, mandatory, regex, regexErrorMessage)); + } + + protected void onChange() { + changed = true; + ValueChangeEvent.fire(this, getValue()); + } + + private void refreshUI() { + if (model == null) + return; + for (FormBinding binding : bindings) { + binding.refreshFromModel(model); + } + } + + @Override + public HandlerRegistration addValueChangeHandler(ValueChangeHandler handler) { + return addHandler(handler, ValueChangeEvent.getType()); + } + + @SuppressWarnings("rawtypes") + interface GenericDataFormUiBinder extends UiBinder { + } + + /** + * Base interface for all field bindings to allow mixing different UI Widgets. + */ + private interface FormBinding { + String getLabelText(); + void refreshFromModel(T model); + void flushToModel(T model); + boolean validate(); + } + + /** + * Internal class to hold the relationship between the TextBox Widget and the Model data + */ + private class TextFieldBinding implements FormBinding { + private final String labelText; + private final TextBox widget; + private final Function getter; + private final BiConsumer setter; + private final boolean mandatory; + private final String regex; + private final String regexErrorMessage; + + public TextFieldBinding(String labelText, TextBox widget, Function getter, BiConsumer setter, + boolean mandatory, String regex, String regexErrorMessage) { + this.labelText = labelText; + this.widget = widget; + this.getter = getter; + this.setter = setter; + this.mandatory = mandatory; + this.regex = regex; + this.regexErrorMessage = regexErrorMessage; + } + + @Override + public String getLabelText() { + return labelText; + } + + @Override + public void refreshFromModel(T model) { + String value = getter.apply(model); + widget.setText(value != null ? value : ""); + } + + @Override + public void flushToModel(T model) { + setter.accept(model, widget.getText()); + } + + @Override + public boolean validate() { + String text = widget.getText(); + boolean isBlank = (text == null || text.trim().isEmpty()); + + if (mandatory && isBlank) { + widget.addStyleName("isWrong"); + return false; + } + + if (!isBlank && regex != null && !text.matches(regex)) { + widget.addStyleName("isWrong"); + return false; + } + + widget.removeStyleName("isWrong"); + return true; + } + + public String getRegexErrorMessage() { + return regexErrorMessage; + } + } + + /** + * Internal class to hold the relationship between the ReadOnly InlineHTML Widget and the Model data + */ + private class ReadOnlyFieldBinding implements FormBinding { + private final String labelText; + private final InlineHTML widget; + private final Function getter; + + public ReadOnlyFieldBinding(String labelText, InlineHTML widget, Function getter) { + this.labelText = labelText; + this.widget = widget; + this.getter = getter; + } + + @Override + public String getLabelText() { + return labelText; + } + + @Override + public void refreshFromModel(T model) { + String value = getter.apply(model); + widget.setText(value != null ? value : ""); + } + + @Override + public void flushToModel(T model) { + // Read-only fields do not modify the underlying model + } + + @Override + public boolean validate() { + // Read-only fields cannot be invalidly edited by the user + return true; + } + } +} \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/forms/GenericDataForm.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/forms/GenericDataForm.ui.xml new file mode 100644 index 0000000000..c961181c4d --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/forms/GenericDataForm.ui.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/GenericDataPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/forms/GenericDataPanel.java similarity index 77% rename from roda-ui/roda-wui/src/main/java/org/roda/wui/client/GenericDataPanel.java rename to roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/forms/GenericDataPanel.java index f3a438ad8a..545e5a93a9 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/GenericDataPanel.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/forms/GenericDataPanel.java @@ -1,4 +1,4 @@ -package org.roda.wui.client; +package org.roda.wui.client.common.forms; /** * diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/RodaMemberList.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/RodaMemberList.java index c97eac9bbb..a7226e00fa 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/RodaMemberList.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/RodaMemberList.java @@ -107,8 +107,8 @@ public SafeHtml getValue(RODAMember member) { activeColumn.setSortable(true); addColumn(typeColumn, SafeHtmlUtils.fromSafeConstant(""), false, false, 3); - addColumn(nameColumn, messages.userIdentifier(), true, false); addColumn(fullNameColumn, messages.userFullName(), true, false); + addColumn(nameColumn, messages.userIdentifier(), true, false); addColumn(groupsColumn, messages.userGroups(), true, false); addColumn(activeColumn, SafeHtmlUtils.fromSafeConstant(""), false, false, 3); diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/pagination/ListSelectionStateMappers.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/pagination/ListSelectionStateMappers.java index 4d4d4d0cee..8b501eecbf 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/pagination/ListSelectionStateMappers.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/pagination/ListSelectionStateMappers.java @@ -17,10 +17,10 @@ import org.roda.core.data.v2.ip.IndexedFile; import org.roda.core.data.v2.ip.IndexedRepresentation; import org.roda.core.data.v2.jobs.IndexedReport; +import org.roda.wui.common.client.ClientLogger; import com.github.nmorel.gwtjackson.client.ObjectMapper; import com.google.gwt.core.client.GWT; -import org.roda.wui.common.client.ClientLogger; public class ListSelectionStateMappers { diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/resources/main.gss b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/resources/main.gss index 5361f688dc..ff8ef15f88 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/resources/main.gss +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/resources/main.gss @@ -1140,6 +1140,10 @@ button:after { content: "\eb61"; } +.btn-password:after { + content: "\f042"; +} + .btn-key:after { content: "\e73c"; } @@ -4811,12 +4815,14 @@ td.datePickerMonth, td.datePickerYear { flex: 1; display: flex; gap: 16px; + align-items: flex-start; } .search-field { margin-right: 20px; - height: 35px; + min-height: 35px; + height: auto; } .search-field-warning { @@ -4860,7 +4866,7 @@ td.datePickerMonth, td.datePickerYear { .search-field-left-panel .search-field-input-panel { display: flex; gap: 8px; - align-items: center; + align-items: flex-start; } .search-field-left-panel .search-field-input-panel .gwt-Label { @@ -9867,3 +9873,137 @@ wui-disposal-hold-data .form-textbox { user-select: none; } +.gwt-DialogBox.wui-dialog-confirm .wui-dialog-layout, .gwt-DialogBox.wui-dialog-information .wui-dialog-layout, .gwt-DialogBox.wui-dialog-prompt .wui-dialog-layout { + max-width: 95vw; +} + +.edit-permissions-dialog .wui-dialog-layout, .edit-user-information-dialog .wui-dialog-layout, .create-user-dialog .wui-dialog-layout { + width: 80vw; + max-width: 80vw !important; + max-height: 85vh; + display: flex; + flex-direction: column; +} + +.edit-permissions-dialog .wui-dialog-layout > .content, .edit-user-information-dialog .wui-dialog-layout > .content, .create-user-dialog .wui-dialog-layout > .content { + flex: 1; + overflow-y: auto; + min-height: 0; + padding-bottom: 20px; +} + +.edit-permissions-dialog .wui-dialog-layout > .dialog-button-panel, .edit-user-information-dialog .wui-dialog-layout > .dialog-button-panel, .create-user-dialog .wui-dialog-layout > .dialog-button-panel { + flex-shrink: 0; + margin-top: 15px; +} + +/* ============================================================ + GENERIC FORM FIELD STRUCTURE + ============================================================ */ +.generic-data-panel { + min-height: auto; +} + +.generic-data-panel-fields { + display: flex; + flex-direction: column; +} + +.generic-form-field { + display: flex; + max-width: 750px; + margin-right: 20px; + min-height: 35px; /* Allows the row to grow for TextAreas */ + height: auto; +} + +.generic-form-field .form-textbox { + font-size: 14px; + color: black; + flex: 1; +} + +.generic-form-field.full_width { + margin-right: 0px; +} + +.generic-form-field-left-panel { + flex: 1; + display: flex; + gap: 16px; + align-items: flex-start; /* Keeps the label at the top for tall inputs */ + width: 100%; +} + +.generic-form-field-input-panel { + display: flex; + gap: 8px; + align-items: flex-start; /* Prevents upward overflow for tall inputs */ +} + +.generic-form-field-input-panel.full_width { + width: 100%; +} + +/* Ensure child textboxes expand correctly within the new layout */ +.generic-form-field-input-panel .form-textbox { + background-color: #FAFAFA; + border-bottom: 1px solid #D9D9D9; + border-left: none; + flex: 1; +} + +.mb-20 { + margin-bottom: 20px; +} + +.generic-data-panel-fields .form-separator { + font-size: 14px; + font-weight: bold; + margin: 0 -15px 0 -15px !important; +} + +.generic-data-panel-fields .form-readonly-value { + padding-top: 5px; /* Adjust to align with the labels */ + font-size: 14px; + color: black; + flex: 1; +} + +.generic-data-panel-fields .form-label { + font-size: 14px; + font-weight: 300; + color: #666666; + width: 250px; +} + +/* ============================================================ + GENERIC STACKED FIELD (Label on top, Input below) + ============================================================ */ +.generic-stacked-field { + display: flex; + flex-direction: column; + max-width: 750px; + margin-bottom: 15px; /* Spacing between the fields */ +} + +/* Override the 250px fixed width from the side-by-side layout */ +.generic-stacked-field .form-label { + width: auto; + margin-bottom: 4px; /* Small gap between label and password box */ + font-size: 14px; + font-weight: 300; + color: #666666; +} + +/* Ensure the password box inherits the exact same generic look */ +.generic-stacked-field .form-textbox { + background-color: #FAFAFA; + border: none; + border-bottom: 1px solid #D9D9D9; + font-size: 14px; + color: black; + width: 100%; + box-sizing: border-box; + padding: 6px 8px; /* Slightly taller padding for standalone look */ +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/search/SearchFieldPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/search/SearchFieldPanel.java index 95017845a1..7f75b639aa 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/search/SearchFieldPanel.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/search/SearchFieldPanel.java @@ -25,7 +25,6 @@ import org.roda.wui.common.client.tools.Humanize; import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.event.dom.client.ClickHandler; diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/utils/FormUtilities.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/utils/FormUtilities.java index 88a23fc806..02d62059a5 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/utils/FormUtilities.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/utils/FormUtilities.java @@ -41,6 +41,7 @@ import com.google.gwt.user.client.ui.RichTextArea; import com.google.gwt.user.client.ui.TextArea; import com.google.gwt.user.client.ui.TextBox; +import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.datepicker.client.DateBox; import config.i18n.client.ClientMessages; @@ -62,7 +63,7 @@ public static void create(FlowPanel panel, Set bundle, boolean ad } public static void create(FlowPanel panel, Set bundle, boolean addStyle, - final Callable onChange) { + final Callable onChange) { if (bundle != null) { for (MetadataValue mv : bundle) { boolean mandatory = mv.get("mandatory") != null && "true".equalsIgnoreCase(mv.get("mandatory")); @@ -110,6 +111,34 @@ public static void create(FlowPanel panel, Set bundle, boolean ad } } + /** + * Helper method to construct the standardized DOM structure for every generated field. + */ + private static void assembleFormRow(FlowPanel layout, Widget labelWidget, Widget inputWidget, Widget descriptionWidget, Widget errorWidget) { + layout.addStyleName("generic-form-field"); + + FlowPanel leftPanel = new FlowPanel(); + leftPanel.addStyleName("generic-form-field-left-panel"); + + FlowPanel inputPanel = new FlowPanel(); + inputPanel.addStyleName("generic-form-field-input-panel full_width"); + + // Assemble components + inputPanel.add(inputWidget); + leftPanel.add(labelWidget); + leftPanel.add(inputPanel); + + if (descriptionWidget != null) { + leftPanel.add(descriptionWidget); + } + + if (errorWidget != null) { + leftPanel.add(errorWidget); + } + + layout.add(leftPanel); + } + private static String getFieldLabel(MetadataValue mv) { String result = mv.getId(); String rawLabel = mv.get("label"); @@ -146,13 +175,15 @@ private static String getFieldLabel(MetadataValue mv) { } private static void addTextField(FlowPanel panel, final FlowPanel layout, final MetadataValue mv, - final boolean mandatory, final Callable onChange) { + final boolean mandatory, final Callable onChange) { // Top label Label mvLabel = new Label(getFieldLabel(mv)); mvLabel.addStyleName("form-label"); if (mandatory) { mvLabel.addStyleName("form-label-mandatory"); + mvLabel.setText(mvLabel.getText() + "*"); } + // Field final TextBox mvText = new TextBox(); mvText.setTitle(mvLabel.getText()); @@ -174,35 +205,36 @@ public void onChange(ChangeEvent changeEvent) { } }); - layout.add(mvLabel); - layout.add(mvText); - // Description + Label mvDescription = null; String description = mv.get("description"); if (description != null && description.length() > 0) { - Label mvDescription = new Label(description); + mvDescription = new Label(description); mvDescription.addStyleName("form-help"); - layout.add(mvDescription); } + // Error + Label errorLabel = null; if (mv.get("error") != null && !"".equals(mv.get("error").trim())) { - Label errorLabel = new Label(mv.get("error")); + errorLabel = new Label(mv.get("error")); errorLabel.addStyleName("form-label-error"); - layout.add(errorLabel); mvText.addStyleName("isWrong"); } + assembleFormRow(layout, mvLabel, mvText, mvDescription, errorLabel); panel.add(layout); } private static void addTextArea(FlowPanel panel, final FlowPanel layout, final MetadataValue mv, - final boolean mandatory, final Callable onChange) { + final boolean mandatory, final Callable onChange) { // Top label Label mvLabel = new Label(getFieldLabel(mv)); mvLabel.addStyleName("form-label"); if (mandatory) { mvLabel.addStyleName("form-label-mandatory"); + mvLabel.setText(mvLabel.getText() + "*"); } + // Field final TextArea mvText = new TextArea(); mvText.setTitle(mvLabel.getText()); @@ -224,37 +256,37 @@ public void onChange(ChangeEvent changeEvent) { } }); - layout.add(mvLabel); - layout.add(mvText); - // Description + Label mvDescription = null; String description = mv.get("description"); if (description != null && description.length() > 0) { - Label mvDescription = new Label(description); + mvDescription = new Label(description); mvDescription.addStyleName("form-help"); - layout.add(mvDescription); } + // Error + Label errorLabel = null; if (mv.get("error") != null && !"".equalsIgnoreCase(mv.get("error").trim())) { - Label errorLabel = new Label(mv.get("error")); + errorLabel = new Label(mv.get("error")); errorLabel.addStyleName("form-label-error"); - layout.add(errorLabel); mvText.addStyleName("isWrong"); } + assembleFormRow(layout, mvLabel, mvText, mvDescription, errorLabel); panel.add(layout); } private static void addRichTextArea(FlowPanel panel, final FlowPanel layout, final MetadataValue mv, - final boolean mandatory, final Callable onChange) { + final boolean mandatory, final Callable onChange) { // Top label Label mvLabel = new Label(getFieldLabel(mv)); mvLabel.addStyleName("form-label"); if (mandatory) { mvLabel.addStyleName("form-label-mandatory"); + mvLabel.setText(mvLabel.getText() + "*"); } - // Field + // Field wrapped with toolbar final FlowPanel fieldLayout = new FlowPanel(); final RichTextArea mvText = new RichTextArea(); @@ -282,34 +314,36 @@ public void onBlur(BlurEvent arg0) { fieldLayout.add(toolbar); fieldLayout.add(mvText); - layout.add(mvLabel); - layout.add(fieldLayout); - // Description + Label mvDescription = null; String description = mv.get("description"); if (description != null && description.length() > 0) { - Label mvDescription = new Label(description); + mvDescription = new Label(description); mvDescription.addStyleName("form-help"); - layout.add(mvDescription); } + // Error + Label errorLabel = null; if (mv.get("error") != null && !"".equalsIgnoreCase(mv.get("error").trim())) { - Label errorLabel = new Label(mv.get("error")); + errorLabel = new Label(mv.get("error")); errorLabel.addStyleName("form-label-error"); - layout.add(errorLabel); mvText.addStyleName("isWrong"); } + + assembleFormRow(layout, mvLabel, fieldLayout, mvDescription, errorLabel); panel.add(layout); } private static void addList(FlowPanel panel, final FlowPanel layout, final MetadataValue mv, final boolean mandatory, - final Callable onChange) { + final Callable onChange) { // Top Label Label mvLabel = new Label(getFieldLabel(mv)); mvLabel.addStyleName("form-label"); if (mandatory) { mvLabel.addStyleName("form-label-mandatory"); + mvLabel.setText(mvLabel.getText() + "*"); } + // Field final ListBox mvList = new ListBox(); mvList.setTitle(mvLabel.getText()); @@ -411,7 +445,7 @@ public void onChange(ChangeEvent changeEvent) { } if (mandatory && (mvList.getSelectedValue() == null || "".equalsIgnoreCase(mvList.getSelectedValue().trim()))) { - mvList.removeStyleName("isWrong"); + mvList.addStyleName("isWrong"); // Added isWrong logic here for empty list selection } } }); @@ -421,35 +455,37 @@ public void onChange(ChangeEvent changeEvent) { mv.set("value", mvList.getSelectedValue()); } - layout.add(mvLabel); - layout.add(mvList); - // Description + Label mvDescription = null; String description = mv.get("description"); if (description != null && description.length() > 0) { - Label mvDescription = new Label(description); + mvDescription = new Label(description); mvDescription.addStyleName("form-help"); - layout.add(mvDescription); } + // Error + Label errorLabel = null; if (mv.get("error") != null && !"".equals(mv.get("error").trim())) { - Label errorLabel = new Label(mv.get("error")); + errorLabel = new Label(mv.get("error")); errorLabel.addStyleName("form-label-error"); - layout.add(errorLabel); mvList.addStyleName("isWrong"); } + + assembleFormRow(layout, mvLabel, mvList, mvDescription, errorLabel); panel.add(layout); } private static void addDatePicker(FlowPanel panel, final FlowPanel layout, final MetadataValue mv, - final boolean mandatory, final Callable onChange) { + final boolean mandatory, final Callable onChange) { // Top label final DateTimeFormat dateTimeFormat = DateTimeFormat.getFormat("yyyy-MM-dd"); Label mvLabel = new Label(getFieldLabel(mv)); mvLabel.addStyleName("form-label"); if (mandatory) { mvLabel.addStyleName("form-label-mandatory"); + mvLabel.setText(mvLabel.getText() + "*"); } + // Field final DateBox mvDate = new DateBox(); mvDate.setTitle(mvLabel.getText()); @@ -490,7 +526,6 @@ public void onValueChange(ValueChangeEvent valueChangeEvent) { }); mvDate.getTextBox().addValueChangeHandler(new ValueChangeHandler() { - @Override public void onValueChange(ValueChangeEvent event) { FormUtilities.callOnChange(onChange); @@ -508,23 +543,23 @@ public void onValueChange(ValueChangeEvent event) { } }); - layout.add(mvLabel); - layout.add(mvDate); - // Description + Label mvDescription = null; String description = mv.get("description"); if (description != null && description.length() > 0) { - Label mvDescription = new Label(description); + mvDescription = new Label(description); mvDescription.addStyleName("form-help"); - layout.add(mvDescription); } + // Error + Label errorLabel = null; if (mv.get("error") != null && !"".equals(mv.get("error").trim())) { - Label errorLabel = new Label(mv.get("error")); + errorLabel = new Label(mv.get("error")); errorLabel.addStyleName("form-label-error"); - layout.add(errorLabel); mvDate.addStyleName("isWrong"); } + + assembleFormRow(layout, mvLabel, mvDate, mvDescription, errorLabel); panel.add(layout); } @@ -615,13 +650,17 @@ public static void addIfNotBlank(FlowPanel panel, String label, String value, St } } + public static void addSeparator(FlowPanel panel, String label) { + panel.add(buildSeparator(label)); + } + public static FlowPanel buildSeparator(String text) { FlowPanel separator = new FlowPanel(); separator.addStyleName("form-separator"); - Label separatorLabel = new Label(messages.groups()); + Label separatorLabel = new Label(text); separatorLabel.addStyleName("separator-label"); separator.add(separatorLabel); return separator; } -} +} \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/utils/HtmlSnippetUtils.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/utils/HtmlSnippetUtils.java index b8acef57cc..e7160d83f2 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/utils/HtmlSnippetUtils.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/utils/HtmlSnippetUtils.java @@ -549,6 +549,10 @@ public static final void setCssClassDisabled(UIObject uiobject, boolean disabled } } + public static String getMetadataValueLabel(MetadataValue metadataValue) { + return getFieldLabel(metadataValue); + } + public static void createExtraShow(FlowPanel panel, Set bundle, boolean addStyle) { FlowPanel lastSeparator = null; boolean hasFields = false; diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/DisposalHoldDataPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/DisposalHoldDataPanel.java index 2388892694..9130c160da 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/DisposalHoldDataPanel.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/hold/DisposalHoldDataPanel.java @@ -32,8 +32,7 @@ import com.google.gwt.user.client.ui.Widget; import config.i18n.client.ClientMessages; -import org.roda.wui.client.GenericDataPanel; -import org.roda.wui.client.disposal.schedule.DisposalScheduleDataPanel; +import org.roda.wui.client.common.forms.GenericDataPanel; /** * @author Tiago Fraga diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/DisposalRuleDataPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/DisposalRuleDataPanel.java index 58aa1412ba..3e265541fa 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/DisposalRuleDataPanel.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/rule/DisposalRuleDataPanel.java @@ -22,7 +22,7 @@ import org.roda.core.data.v2.index.filter.SimpleFilterParameter; import org.roda.core.data.v2.ip.AIPState; import org.roda.core.data.v2.ip.IndexedAIP; -import org.roda.wui.client.GenericDataPanel; +import org.roda.wui.client.common.forms.GenericDataPanel; import org.roda.wui.client.common.lists.utils.AsyncTableCellOptions; import org.roda.wui.client.common.lists.utils.ConfigurableAsyncTableCell; import org.roda.wui.client.common.lists.utils.ListBuilder; diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/DisposalScheduleDataPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/DisposalScheduleDataPanel.java index 9338e52582..c138ef25fb 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/DisposalScheduleDataPanel.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/disposal/schedule/DisposalScheduleDataPanel.java @@ -15,7 +15,7 @@ import org.roda.core.data.v2.disposal.schedule.DisposalActionCode; import org.roda.core.data.v2.disposal.schedule.DisposalSchedule; import org.roda.core.data.v2.disposal.schedule.RetentionPeriodIntervalCode; -import org.roda.wui.client.GenericDataPanel; +import org.roda.wui.client.common.forms.GenericDataPanel; import org.roda.wui.common.client.tools.StringUtils; import com.google.gwt.core.client.GWT; diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java index f829d2c88d..e3bf89170a 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/BreadcrumbUtils.java @@ -46,6 +46,10 @@ import org.roda.wui.client.disposal.schedule.ShowDisposalSchedule; import org.roda.wui.client.ingest.appraisal.IngestAppraisal; import org.roda.wui.client.ingest.transfer.IngestTransfer; +import org.roda.wui.client.management.members.CreateGroup; +import org.roda.wui.client.management.members.CreateUser; +import org.roda.wui.client.management.members.EditGroup; +import org.roda.wui.client.management.members.EditUser; import org.roda.wui.client.planning.RiskRegister; import org.roda.wui.client.planning.ShowRisk; import org.roda.wui.client.management.members.MemberManagement; @@ -525,6 +529,51 @@ private static SafeHtml getBreadcrumbLabel(IndexedAIP aip) { return breadcrumbLabel; } + public static List getRODAMembersBreadcrumbs() { + List ret = new ArrayList<>(); + ret.add(new BreadcrumbItem(SafeHtmlUtils.fromSafeConstant(messages.usersAndGroupsTitle()), + messages.usersAndGroupsTitle(), MemberManagement.RESOLVER.getHistoryPath())); + + return ret; + } + + public static List getCreateUserBreadcrumbs() { + List ret = getRODAMembersBreadcrumbs(); + + ret.add(new BreadcrumbItem(SafeHtmlUtils.fromSafeConstant(messages.createUserTitle()), messages.createUserTitle(), + CreateUser.RESOLVER.getHistoryPath())); + + return ret; + } + + public static List getCreateGroupBreadcrumbs() { + List ret = getRODAMembersBreadcrumbs(); + + ret.add(new BreadcrumbItem(SafeHtmlUtils.fromSafeConstant(messages.createGroupTitle()), messages.createGroupTitle(), + CreateGroup.RESOLVER.getHistoryPath())); + + return ret; + } + + public static List getEditMemberBreadcrumbs(RODAMember member) { + List ret = getRODAMemberBreadcrumbs(member); + + if (member.isUser()) { + List path = new ArrayList<>(EditUser.RESOLVER.getHistoryPath()); + path.add(member.getId()); + + ret.add( + new BreadcrumbItem(SafeHtmlUtils.fromSafeConstant(messages.editUserTitle()), messages.editUserTitle(), path)); + } else { + List path = new ArrayList<>(EditGroup.RESOLVER.getHistoryPath()); + path.add(member.getId()); + + ret.add( + new BreadcrumbItem(SafeHtmlUtils.fromSafeConstant(messages.editGroupTitle()), messages.editGroupTitle(), path)); + } + return ret; + } + public static List getRODAMemberBreadcrumbs(RODAMember user) { List ret = new ArrayList<>(); ret.add(new BreadcrumbItem(SafeHtmlUtils.fromSafeConstant(messages.usersAndGroupsTitle()), @@ -533,7 +582,7 @@ public static List getRODAMemberBreadcrumbs(RODAMember user) { if (user != null) { List path = new ArrayList<>(ShowMember.RESOLVER.getHistoryPath()); path.add(user.getUUID()); - String label = user.getId(); + String label = user.getFullName(); ret.add(new BreadcrumbItem(SafeHtmlUtils.fromString(label), label, path)); } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/UserMenu.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/UserMenu.java index b9f7111be2..a95823c355 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/UserMenu.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/main/UserMenu.java @@ -121,7 +121,7 @@ private void updateVisibles(User user) { registerItem.addStyleName("user_menu_item_register"); menu.addItem(registerItem); } else { - MenuItem userItem = customMenuItem("fa fa-user", user.getName(), "navigationMenu-item-label", userMenu, null); + MenuItem userItem = customMenuItem("fa fa-user", user.getFullName(), "navigationMenu-item-label", userMenu, null); userItem.addStyleName("user_menu_item"); menu.addItem(userItem); } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateGroup.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateGroup.java new file mode 100644 index 0000000000..245a6935d7 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateGroup.java @@ -0,0 +1,123 @@ +package org.roda.wui.client.management.members; + +import java.util.HashSet; +import java.util.List; + +import com.google.gwt.user.client.ui.Button; +import org.roda.core.data.v2.user.Group; +import org.roda.core.data.v2.user.requests.CreateGroupRequest; +import org.roda.wui.client.common.NavigationToolbar; +import org.roda.wui.client.common.NoActionsToolbar; +import org.roda.wui.client.common.TitlePanel; +import org.roda.wui.client.common.UserLogin; +import org.roda.wui.client.common.forms.GenericDataPanel; +import org.roda.wui.client.main.BreadcrumbUtils; +import org.roda.wui.client.management.members.data.panels.GroupDataPanel; +import org.roda.wui.client.services.Services; +import org.roda.wui.common.client.HistoryResolver; +import org.roda.wui.common.client.tools.HistoryUtils; +import org.roda.wui.common.client.tools.ListUtils; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.FocusPanel; +import com.google.gwt.user.client.ui.Widget; + +import config.i18n.client.ClientMessages; +import org.roda.wui.common.client.widgets.Toast; + +/** + * @author Miguel Guimarães + */ + +public class CreateGroup extends Composite { + public static final HistoryResolver RESOLVER = new HistoryResolver() { + + @Override + public void resolve(List historyTokens, final AsyncCallback callback) { + CreateGroup createGroup = new CreateGroup(); + callback.onSuccess(createGroup); + } + + @Override + public void isCurrentUserPermitted(AsyncCallback callback) { + UserLogin.getInstance().checkRoles(new HistoryResolver[] {MemberManagement.RESOLVER}, false, callback); + } + + @Override + public List getHistoryPath() { + return ListUtils.concat(MemberManagement.RESOLVER.getHistoryPath(), getHistoryToken()); + } + + @Override + public String getHistoryToken() { + return "create_group"; + } + }; + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); + + @UiField + FocusPanel keyboardFocus; + @UiField + NavigationToolbar navigationToolbar; + @UiField + NoActionsToolbar actionsToolbar; + @UiField + TitlePanel title; + @UiField + FlowPanel groupDataPanel; + + public CreateGroup() { + initWidget(uiBinder.createAndBindUi(this)); + + // 1. Create the panel and keep a reference + GroupDataPanel dataPanel = new GroupDataPanel(false); + dataPanel.setGroup(new Group()); + // Bind the Save Action logic + dataPanel.setSaveHandler(() -> { + Group group = dataPanel.getValue(); + CreateGroupRequest request = new CreateGroupRequest(); + request.setName(group.getName()); + request.setFullName(group.getFullName()); + request.setDirectRoles(new HashSet<>()); + Services services = new Services("Create group", "create"); + services.membersResource(s -> s.createGroup(request)).whenComplete((created, error) -> { + if (error == null) { + Toast.showInfo(messages.groups(), messages.groupSuccessfullyCreated()); + HistoryUtils.newHistory(ShowMember.RESOLVER, created.getUUID()); + } else { + Toast.showError(messages.groups(), error.getMessage()); + HistoryUtils.newHistory(MemberManagement.RESOLVER); + } + }); + }); + + // Bind the Cancel Action logic + dataPanel.setCancelHandler(() -> HistoryUtils.newHistory(MemberManagement.RESOLVER)); + + groupDataPanel.add(dataPanel); + + navigationToolbar.withoutButtons().build(); + navigationToolbar.updateBreadcrumbPath(BreadcrumbUtils.getCreateGroupBreadcrumbs()); + + actionsToolbar.setLabel(messages.showGroupTitle()); + + // 3. Pass the shared object + actionsToolbar.build(); + + title.setText(messages.createGroupTitle()); + title.setIconClass("Group"); + title.addStyleName("mb-20"); + + keyboardFocus.setFocus(true); + keyboardFocus.addStyleName("browse"); + } + + interface MyUiBinder extends UiBinder { + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateGroup.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateGroup.ui.xml new file mode 100644 index 0000000000..2adb801a9c --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateGroup.ui.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateUser.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateUser.java new file mode 100644 index 0000000000..d4985ba3d1 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateUser.java @@ -0,0 +1,131 @@ +package org.roda.wui.client.management.members; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.i18n.client.LocaleInfo; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.FocusPanel; +import com.google.gwt.user.client.ui.Widget; +import config.i18n.client.ClientMessages; +import org.roda.core.data.v2.user.User; +import org.roda.core.data.v2.user.requests.CreateUserRequest; +import org.roda.wui.client.common.NavigationToolbar; +import org.roda.wui.client.common.NoActionsToolbar; +import org.roda.wui.client.common.TitlePanel; +import org.roda.wui.client.common.UserLogin; +import org.roda.wui.client.common.actions.Actionable; +import org.roda.wui.client.common.utils.AsyncCallbackUtils; +import org.roda.wui.client.main.BreadcrumbUtils; +import org.roda.wui.client.management.members.data.panels.UserDataPanel; +import org.roda.wui.client.services.Services; +import org.roda.wui.common.client.HistoryResolver; +import org.roda.wui.common.client.tools.HistoryUtils; +import org.roda.wui.common.client.tools.ListUtils; +import org.roda.wui.common.client.widgets.Toast; + +import java.util.HashSet; +import java.util.List; + +/** + * @author Miguel Guimarães + */ + +public class CreateUser extends Composite { + public static final HistoryResolver RESOLVER = new HistoryResolver() { + + @Override + public void resolve(List historyTokens, final AsyncCallback callback) { + CreateUser createUser = new CreateUser(new User()); + callback.onSuccess(createUser); + } + + @Override + public void isCurrentUserPermitted(AsyncCallback callback) { + UserLogin.getInstance().checkRoles(new HistoryResolver[] {MemberManagement.RESOLVER}, false, callback); + } + + @Override + public List getHistoryPath() { + return ListUtils.concat(MemberManagement.RESOLVER.getHistoryPath(), getHistoryToken()); + } + + @Override + public String getHistoryToken() { + return "create_user"; + } + }; + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); + + @UiField + FocusPanel keyboardFocus; + @UiField + NavigationToolbar navigationToolbar; + @UiField + NoActionsToolbar actionsToolbar; + @UiField + TitlePanel title; + @UiField + FlowPanel userDataPanel; + + public CreateUser(User user) { + initWidget(uiBinder.createAndBindUi(this)); + + // 1. Create the panel and keep a reference + UserDataPanel dataPanel = new UserDataPanel(false); + dataPanel.setUser(user); + dataPanel.setSaveHandler(() -> { + + CreateUserRequest request = new CreateUserRequest(user.getEmail(), user.getName(), user.getFullName(), + new HashSet<>(), new HashSet<>(), user.isGuest(), null, dataPanel.getUserExtra()); + + Services services = new Services("Create user", "create"); + services.membersResource(s -> s.createUser(request, LocaleInfo.getCurrentLocale().getLocaleName())) + .whenComplete((created, error) -> { + if (error == null) { + Toast.showInfo(messages.groups(), messages.groupSuccessfullyCreated()); + HistoryUtils.newHistory(ShowMember.RESOLVER, created.getUUID()); + } else { + Toast.showError(messages.groups(), error.getMessage()); + HistoryUtils.newHistory(MemberManagement.RESOLVER); + } + }); + }); + + // Bind the Cancel Action logic + dataPanel.setCancelHandler(() -> HistoryUtils.newHistory(MemberManagement.RESOLVER)); + + userDataPanel.add(dataPanel); + + navigationToolbar.withoutButtons().build(); + navigationToolbar.updateBreadcrumbPath(BreadcrumbUtils.getCreateUserBreadcrumbs()); + + actionsToolbar.setLabel(messages.showUserTitle()); + + // 3. Pass the shared object + actionsToolbar.setObjectAndBuild(user, null, new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + AsyncCallbackUtils.defaultFailureTreatment(caught); + } + + @Override + public void onSuccess(Actionable.ActionImpact result) { + // Redirect user or show success message if result == ActionImpact.UPDATED + } + }); + + title.setText(messages.createUserTitle()); + title.setIconClass("User"); + title.addStyleName("mb-20"); + + keyboardFocus.setFocus(true); + keyboardFocus.addStyleName("browse"); + } + + interface MyUiBinder extends UiBinder { + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateUser.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateUser.ui.xml new file mode 100644 index 0000000000..d878268eb0 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateUser.ui.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateUserPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateUserPanel.java deleted file mode 100644 index bdbce4643c..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateUserPanel.java +++ /dev/null @@ -1,303 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE file at the root of the source - * tree and available online at - * - * https://github.com/keeps/roda - */ -package org.roda.wui.client.management.members; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.ChangeEvent; -import com.google.gwt.event.dom.client.ChangeHandler; -import com.google.gwt.event.dom.client.KeyUpEvent; -import com.google.gwt.event.dom.client.KeyUpHandler; -import com.google.gwt.event.logical.shared.HasValueChangeHandlers; -import com.google.gwt.event.logical.shared.ValueChangeEvent; -import com.google.gwt.event.logical.shared.ValueChangeHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.uibinder.client.UiBinder; -import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.ListBox; -import com.google.gwt.user.client.ui.TextBox; -import com.google.gwt.user.client.ui.Widget; -import config.i18n.client.ClientMessages; -import org.roda.core.data.exceptions.AuthorizationDeniedException; -import org.roda.core.data.v2.generics.MetadataValue; -import org.roda.core.data.v2.user.User; -import org.roda.wui.client.common.utils.AsyncCallbackUtils; -import org.roda.wui.client.common.utils.FormUtilities; -import org.roda.wui.client.services.MembersRestService; -import org.roda.wui.client.services.Services; -import org.roda.wui.common.client.ClientLogger; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -/** - * @author António Lindo - */ -public class CreateUserPanel extends Composite implements HasValueChangeHandlers { - - private static final ClientMessages messages = GWT.create(ClientMessages.class); - private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); - @SuppressWarnings("unused") - private final ClientLogger logger = new ClientLogger(getClass().getName()); - @UiField - TextBox username; - @UiField - Label usernameError; - @UiField - TextBox fullName; - @UiField - Label fullNameError; - @UiField - TextBox email; - @UiField - Label emailError; - @UiField - FlowPanel extra; - @UiField - HTML errors; - - // has to be true to detected new field changes - private boolean changed = true; - private Set userExtra; - // Add this field - private Runnable onFormReadyCallback; - - /** - * Create a new user data panel - * - */ - public CreateUserPanel() { - initWidget(uiBinder.createAndBindUi(this)); - super.setVisible(true); - errors.setVisible(false); - - ChangeHandler changeHandler = new ChangeHandler() { - - @Override - public void onChange(ChangeEvent event) { - CreateUserPanel.this.onChange(); - } - }; - - KeyUpHandler keyUpHandler = new KeyUpHandler() { - - @Override - public void onKeyUp(KeyUpEvent event) { - onChange(); - } - }; - - username.addKeyDownHandler(new UserAndGroupKeyDownHandler()); - - username.addChangeHandler(changeHandler); - username.addKeyUpHandler(keyUpHandler); - fullName.addChangeHandler(changeHandler); - fullName.addKeyUpHandler(keyUpHandler); - - usernameError.setVisible(false); - fullNameError.setVisible(false); - emailError.setVisible(false); - setUser(new User()); - } - - @SuppressWarnings("unused") - private int setSelected(ListBox listbox, String text) { - int index = -1; - if (text != null) { - for (int i = 0; i < listbox.getItemCount(); i++) { - if (listbox.getValue(i).equals(text)) { - index = i; - break; - } - } - if (index >= 0) { - listbox.setSelectedIndex(index); - } else { - listbox.addItem(text); - index = listbox.getItemCount() - 1; - listbox.setSelectedIndex(index); - } - } else { - listbox.setSelectedIndex(-1); - } - return index; - } - - /** - * Get user defined by this panel. This panel defines: name, fullname, title, - * organization name, postal address, postal code, locality, country, email, - * phone number, fax and which groups this user belongs to. - * - * @return the user modified by this panel - */ - public User getUser() { - User user = new User(); - user.setId(username.getText()); - user.setName(username.getText()); - user.setFullName(fullName.getText()); - user.setEmail(email.getText()); - - return user; - } - - private void setUser(User user) { - this.username.setText(user.getName()); - this.fullName.setText(user.getFullName()); - this.email.setText(user.getEmail()); - - Services services = new Services("Get User extra", "get"); - services.membersResource(MembersRestService::getDefaultUserExtra).whenComplete((extra, error) -> { - if (extra != null) { - CreateUserPanel.this.userExtra = extra.getExtraFormFields(); - createForm(userExtra); - - // ADD THIS: Trigger the callback to recenter the dialog - if (onFormReadyCallback != null) { - onFormReadyCallback.run(); - } - } else if (error != null) { - if (error instanceof AuthorizationDeniedException) { - GWT.log("No permissions: " + error.getMessage()); - } else { - AsyncCallbackUtils.defaultFailureTreatment(error); - } - - // ADD THIS: It's good practice to also recenter on error - // in case error messages alter the panel's dimensions - if (onFormReadyCallback != null) { - onFormReadyCallback.run(); - } - } - }); - } - - public void createForm(Set userExtra) { - extra.clear(); - FormUtilities.create(extra, userExtra, false); - } - - /** - * Is user data panel valid - * - * @return true if valid - */ - public boolean isValid() { - List errorList = new ArrayList<>(); - if (username.getText().length() == 0) { - username.addStyleName("isWrong"); - usernameError.setText(messages.mandatoryField()); - usernameError.setVisible(true); - Window.scrollTo(username.getAbsoluteLeft(), username.getAbsoluteTop()); - errorList.add(messages.isAMandatoryField(messages.username())); - } else { - username.removeStyleName("isWrong"); - usernameError.setVisible(false); - } - - if (fullName.getText().length() == 0) { - fullName.addStyleName("isWrong"); - fullNameError.setText(messages.mandatoryField()); - fullNameError.setVisible(true); - if (errorList.isEmpty()) { - Window.scrollTo(fullName.getAbsoluteLeft(), fullName.getAbsoluteTop()); - } - errorList.add(messages.isAMandatoryField(messages.fullname())); - } else { - fullName.removeStyleName("isWrong"); - fullNameError.setVisible(false); - } - - if (email.getText() == null || "".equals(email.getText().trim())) { - email.addStyleName("isWrong"); - emailError.setText(messages.mandatoryField()); - emailError.setVisible(true); - if (errorList.isEmpty()) { - Window.scrollTo(email.getAbsoluteLeft(), email.getAbsoluteTop()); - } - errorList.add(messages.isAMandatoryField(messages.email())); - } else if (!email.getText() - .matches("^[_A-Za-z0-9-%+]+(\\.[_A-Za-z0-9-%+]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(\\.[_A-Za-z0-9-]+)")) { - email.addStyleName("isWrong"); - emailError.setText(messages.wrongMailFormat()); - emailError.setVisible(true); - if (errorList.isEmpty()) { - Window.scrollTo(email.getAbsoluteLeft(), email.getAbsoluteTop()); - } - errorList.add(messages.emailNotValid()); - } else { - email.removeStyleName("isWrong"); - emailError.setVisible(false); - } - - List extraErrors = FormUtilities.validate(userExtra, extra); - errorList.addAll(extraErrors); - - if (!errorList.isEmpty()) { - errors.setVisible(true); - StringBuilder errorString = new StringBuilder(); - for (String error : errorList) { - errorString.append("").append(error).append(""); - errorString.append("
"); - } - errors.setHTML(errorString.toString()); - } else { - errors.setVisible(false); - } - return errorList.isEmpty() ? true : false; - } - - @Override - public void setVisible(boolean visible) { - super.setVisible(visible); - } - - public void clear() { - username.setText(""); - fullName.setText(""); - email.setText(""); - } - - /** - * Is user data panel has been changed - * - * @return changed - */ - public boolean isChanged() { - return changed; - } - - @Override - public HandlerRegistration addValueChangeHandler(ValueChangeHandler handler) { - return addHandler(handler, ValueChangeEvent.getType()); - } - - protected void onChange() { - changed = true; - ValueChangeEvent.fire(this, getValue()); - } - - public void setOnFormReadyCallback(Runnable callback) { - this.onFormReadyCallback = callback; - } - - public User getValue() { - return getUser(); - } - - public Set getUserExtra() { - return userExtra; - } - - interface MyUiBinder extends UiBinder { - } -} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateUserPanel.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateUserPanel.ui.xml deleted file mode 100644 index c2894fe828..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/CreateUserPanel.ui.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - .section { - margin-top: 20px; - } - - - - - - - - - - - - * - - - - - - - * - - - - - - - * - - - - - - - - - - - - diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/EditGroup.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/EditGroup.java new file mode 100644 index 0000000000..03a2fcffce --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/EditGroup.java @@ -0,0 +1,125 @@ +package org.roda.wui.client.management.members; + +import java.util.List; + +import org.roda.core.data.v2.user.Group; +import org.roda.wui.client.common.NavigationToolbar; +import org.roda.wui.client.common.NoActionsToolbar; +import org.roda.wui.client.common.TitlePanel; +import org.roda.wui.client.common.UserLogin; +import org.roda.wui.client.main.BreadcrumbUtils; +import org.roda.wui.client.management.members.data.panels.GroupDataPanel; +import org.roda.wui.client.services.Services; +import org.roda.wui.common.client.HistoryResolver; +import org.roda.wui.common.client.tools.HistoryUtils; +import org.roda.wui.common.client.tools.ListUtils; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.FocusPanel; +import com.google.gwt.user.client.ui.Widget; + +import config.i18n.client.ClientMessages; +import org.roda.wui.common.client.widgets.Toast; + +/** + * @author Miguel Guimarães + */ +public class EditGroup extends Composite { + public static final HistoryResolver RESOLVER = new HistoryResolver() { + @Override + public void resolve(List historyTokens, final AsyncCallback callback) { + if (historyTokens.size() == 1) { + String groupId = historyTokens.get(0); + Services services = new Services("Get Group", "get"); + services.membersResource(s -> s.getGroup(groupId)).whenComplete((group, error) -> { + if (group != null) { + EditGroup editGroup = new EditGroup(group); + callback.onSuccess(editGroup); + } else if (error != null) { + callback.onFailure(error); + } + }); + } else { + HistoryUtils.newHistory(MemberManagement.RESOLVER); + callback.onSuccess(null); + } + } + + @Override + public void isCurrentUserPermitted(AsyncCallback callback) { + UserLogin.getInstance().checkRoles(new HistoryResolver[] {MemberManagement.RESOLVER}, false, callback); + } + + @Override + public List getHistoryPath() { + return ListUtils.concat(MemberManagement.RESOLVER.getHistoryPath(), getHistoryToken()); + } + + @Override + public String getHistoryToken() { + return "edit_group"; + } + }; + + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); + + @UiField + FocusPanel keyboardFocus; + @UiField + NavigationToolbar navigationToolbar; + @UiField + NoActionsToolbar actionsToolbar; + @UiField + TitlePanel title; + @UiField + FlowPanel groupDataPanel; + + public EditGroup(Group group) { + initWidget(uiBinder.createAndBindUi(this)); + + GroupDataPanel groupDataForm = new GroupDataPanel(true); + groupDataForm.setGroup(group); + + // Bind the Save Action logic + groupDataForm.setSaveHandler(() -> { + Group updatedGroup = groupDataForm.getValue(); + Services services = new Services("Update group", "update"); + services.membersResource(s -> s.updateGroup(updatedGroup)).whenComplete((updated, error) -> { + if (error == null) { + Toast.showInfo(messages.groups(), messages.groupSuccessfullyUpdated()); + HistoryUtils.newHistory(ShowMember.RESOLVER, updated.getUUID()); + } else { + Toast.showError(messages.groups(), messages.failedToUpdateGroup()); + HistoryUtils.newHistory(ShowMember.RESOLVER, group.getUUID()); + } + }); + }); + + // Bind the Cancel Action logic + groupDataForm.setCancelHandler(() -> HistoryUtils.newHistory(ShowMember.RESOLVER, group.getUUID())); + + groupDataPanel.add(groupDataForm); + + navigationToolbar.withoutButtons().build(); + navigationToolbar.updateBreadcrumbPath(BreadcrumbUtils.getEditMemberBreadcrumbs(group)); + + actionsToolbar.setLabel(messages.showGroupTitle()); + actionsToolbar.build(); + + title.setText(group.getFullName()); + title.setIconClass("Group"); + title.addStyleName("mb-20"); + + keyboardFocus.setFocus(true); + keyboardFocus.addStyleName("browse"); + } + + interface MyUiBinder extends UiBinder { + } +} \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/EditGroup.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/EditGroup.ui.xml new file mode 100644 index 0000000000..601ecc27b7 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/EditGroup.ui.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/EditUser.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/EditUser.java new file mode 100644 index 0000000000..90d54d0a0b --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/EditUser.java @@ -0,0 +1,133 @@ +package org.roda.wui.client.management.members; + +import java.util.List; + +import com.google.gwt.user.client.ui.Button; +import org.roda.core.data.v2.user.User; +import org.roda.core.data.v2.user.requests.UpdateUserRequest; +import org.roda.wui.client.common.NavigationToolbar; +import org.roda.wui.client.common.NoActionsToolbar; +import org.roda.wui.client.common.TitlePanel; +import org.roda.wui.client.common.UserLogin; +import org.roda.wui.client.management.members.data.panels.UserDataPanel; +import org.roda.wui.client.main.BreadcrumbUtils; +import org.roda.wui.client.services.Services; +import org.roda.wui.common.client.HistoryResolver; +import org.roda.wui.common.client.tools.HistoryUtils; +import org.roda.wui.common.client.tools.ListUtils; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.FocusPanel; +import com.google.gwt.user.client.ui.Widget; + +import config.i18n.client.ClientMessages; +import org.roda.wui.common.client.widgets.Toast; + +/** + * @author Miguel Guimarães + */ + +public class EditUser extends Composite { + public static final HistoryResolver RESOLVER = new HistoryResolver() { + + @Override + public void resolve(List historyTokens, final AsyncCallback callback) { + if (historyTokens.size() == 1) { + String username = historyTokens.get(0); + Services services = new Services("Get User", "get"); + services.membersResource(s -> s.getUser(username)).whenComplete((user, error) -> { + if (user != null) { + EditUser editUser = new EditUser(user); + callback.onSuccess(editUser); + } else if (error != null) { + callback.onFailure(error); + } + }); + } else { + HistoryUtils.newHistory(MemberManagement.RESOLVER); + callback.onSuccess(null); + } + } + + @Override + public void isCurrentUserPermitted(AsyncCallback callback) { + UserLogin.getInstance().checkRoles(new HistoryResolver[] {MemberManagement.RESOLVER}, false, callback); + } + + @Override + public List getHistoryPath() { + return ListUtils.concat(MemberManagement.RESOLVER.getHistoryPath(), getHistoryToken()); + } + + @Override + public String getHistoryToken() { + return "edit_user"; + } + }; + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); + + @UiField + FocusPanel keyboardFocus; + @UiField + NavigationToolbar navigationToolbar; + @UiField + NoActionsToolbar actionsToolbar; + @UiField + TitlePanel title; + @UiField + FlowPanel userDataPanel; + @UiField + FlowPanel actions; + + public EditUser(User user) { + initWidget(uiBinder.createAndBindUi(this)); + + // 1. Create the panel and keep a reference + UserDataPanel dataPanel = new UserDataPanel(true); + dataPanel.setUser(user); + dataPanel.setSaveHandler(() ->{ + UpdateUserRequest request = new UpdateUserRequest(); + request.setUser(dataPanel.getValue()); + request.setPassword(null); + request.setValues(dataPanel.getUserExtra()); + + Services services = new Services("Update user", "update"); + services.membersResource(s -> s.updateUser(request)).whenComplete((updated, error) -> { + if (error == null) { + Toast.showInfo(messages.groups(), messages.userSuccessfullyUpdated()); + HistoryUtils.newHistory(ShowMember.RESOLVER, updated.getUUID()); + } else { + Toast.showError(messages.groups(), messages.failedToUpdateUser()); + HistoryUtils.newHistory(ShowMember.RESOLVER, updated.getUUID()); + } + }); + }); + + dataPanel.setCancelHandler(() -> HistoryUtils.newHistory(ShowMember.RESOLVER, user.getUUID())); + + userDataPanel.add(dataPanel); + + navigationToolbar.withoutButtons().build(); + navigationToolbar.updateBreadcrumbPath(BreadcrumbUtils.getEditMemberBreadcrumbs(user)); + + actionsToolbar.setLabel(messages.showUserTitle()); + + actionsToolbar.build(); + + title.setText(user.getFullName()); + title.setIconClass("User"); + title.addStyleName("mb-20"); + + keyboardFocus.setFocus(true); + keyboardFocus.addStyleName("browse"); + } + + interface MyUiBinder extends UiBinder { + } +} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/EditUser.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/EditUser.ui.xml new file mode 100644 index 0000000000..4a60e43cbb --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/EditUser.ui.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/GroupDataPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/GroupDataPanel.java deleted file mode 100644 index 3e93dc3c79..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/GroupDataPanel.java +++ /dev/null @@ -1,194 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE file at the root of the source - * tree and available online at - * - * https://github.com/keeps/roda - */ -/** - * - */ -package org.roda.wui.client.management.members; - -import org.roda.core.data.v2.user.Group; -import org.roda.wui.common.client.ClientLogger; -import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.ChangeEvent; -import com.google.gwt.event.dom.client.ChangeHandler; -import com.google.gwt.event.dom.client.KeyUpEvent; -import com.google.gwt.event.dom.client.KeyUpHandler; -import com.google.gwt.event.logical.shared.HasValueChangeHandlers; -import com.google.gwt.event.logical.shared.ValueChangeEvent; -import com.google.gwt.event.logical.shared.ValueChangeHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.uibinder.client.UiBinder; -import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.TextBox; -import com.google.gwt.user.client.ui.Widget; - -/** - * @author Luis Faria - * - */ -public class GroupDataPanel extends Composite implements HasValueChangeHandlers { - - private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); - @UiField - TextBox groupName; - @UiField - TextBox fullName; - - private Group group = new Group(); - @SuppressWarnings("unused") - private final ClientLogger logger = new ClientLogger(getClass().getName()); - - private final boolean editMode; - private boolean changed = false; - private boolean checked = false; - /** - * Create a new group data panel - * - * @param editMode - * if group name should be editable - */ - public GroupDataPanel(boolean editMode) { - initWidget(uiBinder.createAndBindUi(this)); - - this.editMode = editMode; - super.setVisible(true); - - ChangeHandler changeHandler = new ChangeHandler() { - - @Override - public void onChange(ChangeEvent event) { - GroupDataPanel.this.onChange(); - } - }; - - KeyUpHandler keyUpHandler = new KeyUpHandler() { - - @Override - public void onKeyUp(KeyUpEvent event) { - onChange(); - } - }; - - groupName.setEnabled(!isEditMode()); - groupName.addKeyDownHandler(new UserAndGroupKeyDownHandler()); - groupName.addChangeHandler(changeHandler); - groupName.addKeyUpHandler(keyUpHandler); - fullName.addChangeHandler(changeHandler); - fullName.addKeyUpHandler(keyUpHandler); - } - - /** - * Get group defined by this panel. This panel defines: name, fullname - * - * @return the group modified by this panel - */ - public Group getGroup() { - group.setId(groupName.getText()); - group.setName(groupName.getText()); - group.setFullName(fullName.getText()); - return group; - } - - /** - * Set group information of group - * - * @param group - */ - public void setGroup(Group group) { - this.group = group; - this.groupName.setText(group.getName()); - this.fullName.setText(group.getFullName()); - } - - /** - * Is group data panel valid - * - * @return true if valid - */ - public boolean isValid() { - boolean valid = true; - - if (groupName.getText().isEmpty()) { - valid = false; - groupName.addStyleName("isWrong"); - } else { - groupName.removeStyleName("isWrong"); - } - - if (fullName.getText().isEmpty()) { - valid = false; - fullName.addStyleName("isWrong"); - } else { - fullName.removeStyleName("isWrong"); - } - - checked = true; - return valid; - } - - /** - * Is group name read only - * - * @return true if read only - */ - public boolean isGroupNameReadOnly() { - return groupName.isReadOnly(); - } - - /** - * Set group name read only - * - * @param readonly - */ - public void setGroupNameReadOnly(boolean readonly) { - groupName.setReadOnly(readonly); - } - - public void clear() { - groupName.setText(""); - fullName.setText(""); - } - - /** - * Is group data panel editable, i.e. on create group mode - * - * @return true if editable - */ - public boolean isEditMode() { - return editMode; - } - - /** - * Is group data panel has been changed - * - * @return changed - */ - public boolean isChanged() { - return changed; - } - - @Override - public HandlerRegistration addValueChangeHandler(ValueChangeHandler handler) { - return addHandler(handler, ValueChangeEvent.getType()); - } - - protected void onChange() { - changed = true; - if (checked) { - isValid(); - } - ValueChangeEvent.fire(this, getValue()); - } - - public Group getValue() { - return getGroup(); - } - - interface MyUiBinder extends UiBinder { - } -} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/GroupDataPanel.ui.xml b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/GroupDataPanel.ui.xml deleted file mode 100644 index 707256479c..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/GroupDataPanel.ui.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - .section { - margin-top: 20px; - } - - - - - - - * - - - - * - - - - - - diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/GroupSelect.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/GroupSelect.java deleted file mode 100644 index 9c1b757a75..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/GroupSelect.java +++ /dev/null @@ -1,284 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE file at the root of the source - * tree and available online at - * - * https://github.com/keeps/roda - */ -package org.roda.wui.client.management.members; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.Vector; - -import org.roda.core.data.common.RodaConstants; -import org.roda.core.data.v2.index.FindRequest; -import org.roda.core.data.v2.index.filter.Filter; -import org.roda.core.data.v2.index.filter.SimpleFilterParameter; -import org.roda.core.data.v2.index.sort.SortParameter; -import org.roda.core.data.v2.index.sort.Sorter; -import org.roda.core.data.v2.user.Group; -import org.roda.core.data.v2.user.RODAMember; -import org.roda.wui.client.services.Services; - -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.event.logical.shared.HasValueChangeHandlers; -import com.google.gwt.event.logical.shared.ValueChangeEvent; -import com.google.gwt.event.logical.shared.ValueChangeHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.i18n.client.LocaleInfo; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.CheckBox; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.HorizontalPanel; -import com.google.gwt.user.client.ui.Label; - -/** - * - * @author Luis Faria - * - */ -public class GroupSelect extends FlowPanel implements HasValueChangeHandlers> { - - private final List blacklist; - private final List groups; - private HashMap userSelections; - private boolean enabled; - - /** - * Create a new group selection widget - * - * @param visible - * start as visible or wait until its initialized - */ - public GroupSelect() { - this.groups = new Vector<>(); - this.blacklist = new Vector<>(); - this.userSelections = new HashMap<>(); - enabled = true; - this.addStyleName("groups"); - } - - public void init(final AsyncCallback callback) { - // TODO use RodaMemberList instead of a list of checkboxes - - boolean isUser = false; - boolean justActive = true; - Filter filter = new Filter(); - filter.add(new SimpleFilterParameter(RodaConstants.MEMBERS_IS_USER, Boolean.toString(isUser))); - Sorter sorter = new Sorter(new SortParameter(RodaConstants.MEMBERS_FULLNAME, false)); - - Services services = new Services("Find RODA members", "get"); - FindRequest request = FindRequest.getBuilder(filter, justActive).withSorter(sorter) - .build(); - services.membersResource(s -> s.find(request, LocaleInfo.getCurrentLocale().getLocaleName())) - .whenComplete((indexedResult, throwable) -> { - if (throwable != null) { - callback.onFailure(throwable); - } else { - for (RODAMember member : indexedResult.getResults()) { - if (member instanceof Group) { - Group group = (Group) member; - GroupCheckbox groupCheckbox = new GroupCheckbox(group, group.getFullName(), group.getId()); - groups.add(groupCheckbox); - } - } - - for (final GroupCheckbox groupCheckbox : groups) { - GroupSelect.this.add(groupCheckbox); - groupCheckbox.addValueChangeHandler(new ValueChangeHandler() { - - @Override - public void onValueChange(ValueChangeEvent event) { - if (userSelections.keySet().contains(event.getValue().getId())) { - userSelections.remove(event.getValue().getId()); - } else { - userSelections.put(event.getValue().getId(), event.getValue()); - } - onChange(); - } - }); - } - callback.onSuccess(true); - } - }); - } - - public Map getUserSelections() { - return userSelections; - } - - public boolean isEnabled() { - return enabled; - } - - public Set getMemberGroups() { - Set memberGroups = new HashSet<>(); - for (GroupCheckbox g : groups) { - if (g.isChecked()) { - memberGroups.add(g.getGroup().getId()); - } - } - return memberGroups; - } - - public void setMemberGroups(Set memberGroups) { - Iterator it = memberGroups.iterator(); - - while (it.hasNext()) { - String group = it.next(); - boolean foundit = false; - for (Iterator j = groups.iterator(); j.hasNext() && !foundit;) { - GroupCheckbox g = j.next(); - if (g.getGroup().getId().equals(group)) { - foundit = true; - g.setChecked(true); - userSelections.put(group, g.getGroup()); - } - } - } - } - - public void addGroupToBlacklist(String group) { - blacklist.add(group); - } - - @Override - public HandlerRegistration addValueChangeHandler(ValueChangeHandler> handler) { - return addHandler(handler, ValueChangeEvent.getType()); - } - - protected void onChange() { - ValueChangeEvent.fire(this, getValue()); - } - - public List getValue() { - return new Vector<>(getUserSelections().values()); - } - - private class GroupCheckbox extends HorizontalPanel - implements HasValueChangeHandlers, Comparable { - - private final String sortingkeyword; - private final Group group; - private final CheckBox checkbox; - private final Label descriptionLabel; - - public GroupCheckbox(Group group, String description, String sortingkeyword) { - this.group = group; - this.checkbox = new CheckBox(); - this.checkbox.setTitle(description); - this.checkbox.getElement().getFirstChildElement().setTitle(description); - this.descriptionLabel = new Label(description); - this.sortingkeyword = sortingkeyword; - this.add(checkbox); - this.add(descriptionLabel); - - this.descriptionLabel.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - checkbox.setValue(!checkbox.getValue()); - onChange(); - } - }); - - this.checkbox.addValueChangeHandler(new ValueChangeHandler() { - @Override - public void onValueChange(ValueChangeEvent event) { - onChange(); - } - }); - - this.addStyleName("group"); - checkbox.addStyleName("group-checkbox"); - descriptionLabel.setStylePrimaryName("group-description"); - } - - public boolean isChecked() { - return checkbox.getValue(); - } - - public void setChecked(boolean checked) { - checkbox.setValue(checked); - } - - public Group getGroup() { - return group; - } - - @Override - public int compareTo(GroupCheckbox groupCheckbox) { - return sortingkeyword.compareTo(groupCheckbox.sortingkeyword); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + getOuterType().hashCode(); - result = prime * result + ((checkbox == null) ? 0 : checkbox.hashCode()); - result = prime * result + ((descriptionLabel == null) ? 0 : descriptionLabel.hashCode()); - result = prime * result + ((group == null) ? 0 : group.hashCode()); - result = prime * result + ((sortingkeyword == null) ? 0 : sortingkeyword.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - GroupCheckbox other = (GroupCheckbox) obj; - if (!getOuterType().equals(other.getOuterType())) { - return false; - } - if (group == null) { - if (other.group != null) { - return false; - } - } else if (!group.equals(other.group)) { - return false; - } - if (sortingkeyword == null) { - if (other.sortingkeyword != null) { - return false; - } - } else if (!sortingkeyword.equals(other.sortingkeyword)) { - return false; - } - return true; - } - - public String getSortingkeyword() { - return sortingkeyword; - } - - @Override - public HandlerRegistration addValueChangeHandler(ValueChangeHandler handler) { - return addHandler(handler, ValueChangeEvent.getType()); - } - - protected void onChange() { - ValueChangeEvent.fire(this, getValue()); - } - - public Group getValue() { - return getGroup(); - } - - private GroupSelect getOuterType() { - return GroupSelect.this; - } - } -} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/MemberManagement.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/MemberManagement.java index 462efa6fd9..dcddac2785 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/MemberManagement.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/MemberManagement.java @@ -11,7 +11,7 @@ import org.roda.core.data.v2.user.RODAMember; import org.roda.wui.client.common.UserLogin; -import org.roda.wui.client.common.actions.RODAMemberActions; +import org.roda.wui.client.common.actions.RODAMemberSearchWrapperActions; import org.roda.wui.client.common.lists.RodaMemberList; import org.roda.wui.client.common.lists.utils.AsyncTableCellOptions; import org.roda.wui.client.common.lists.utils.ListBuilder; @@ -35,7 +35,11 @@ public class MemberManagement extends Composite { private static final ClientMessages messages = GWT.create(ClientMessages.class); private static MemberManagement instance = null; - private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); public static final HistoryResolver RESOLVER = new HistoryResolver() { + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); + @UiField + FlowPanel memberManagementDescription; + @UiField(provided = true) + SearchWrapper searchWrapper; public static final HistoryResolver RESOLVER = new HistoryResolver() { @Override public void resolve(List historyTokens, AsyncCallback callback) { @@ -57,16 +61,11 @@ public String getHistoryToken() { return "user"; } }; - @UiField - FlowPanel memberManagementDescription; - @UiField(provided = true) - SearchWrapper searchWrapper; public MemberManagement() { ListBuilder rodaMemberListBuilder = new ListBuilder<>(() -> new RodaMemberList(), new AsyncTableCellOptions<>(RODAMember.class, "MemberManagement_rodaMembers") - .withSummary(messages.usersAndGroupsTitle()).bindOpener() - .withActionable(RODAMemberActions.get())); + .withSummary(messages.usersAndGroupsTitle()).bindOpener().withActionable(RODAMemberSearchWrapperActions.get())); searchWrapper = new SearchWrapper(false).createListAndSearchPanel(rodaMemberListBuilder); @@ -97,11 +96,21 @@ public void resolve(List historyTokens, AsyncCallback callback) if (historyTokens.isEmpty()) { callback.onSuccess(this); } else if (historyTokens.size() == 1) { - HistoryUtils.newHistory(RESOLVER); - callback.onSuccess(null); + if (historyTokens.get(0).equals(CreateUser.RESOLVER.getHistoryToken())) { + CreateUser.RESOLVER.resolve(HistoryUtils.tail(historyTokens), callback); + } else if (historyTokens.get(0).equals(CreateGroup.RESOLVER.getHistoryToken())) { + CreateGroup.RESOLVER.resolve(HistoryUtils.tail(historyTokens), callback); + } else { + HistoryUtils.newHistory(RESOLVER); + callback.onSuccess(null); + } } else if (historyTokens.size() == 2) { if (historyTokens.get(0).equals(ShowMember.RESOLVER.getHistoryToken())) { ShowMember.RESOLVER.resolve(HistoryUtils.tail(historyTokens), callback); + } else if (historyTokens.get(0).equals(EditGroup.RESOLVER.getHistoryToken())) { + EditGroup.RESOLVER.resolve(HistoryUtils.tail(historyTokens), callback); + } else if (historyTokens.get(0).equals(EditUser.RESOLVER.getHistoryToken())) { + EditUser.RESOLVER.resolve(HistoryUtils.tail(historyTokens), callback); } else { HistoryUtils.newHistory(RESOLVER); callback.onSuccess(null); diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/PasswordPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/PasswordPanel.java index bdcc4117c7..2f27824437 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/PasswordPanel.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/PasswordPanel.java @@ -7,6 +7,7 @@ */ package org.roda.wui.client.management.members; +import com.google.gwt.user.client.ui.Widget; import org.roda.wui.common.client.tools.StringUtils; import com.google.gwt.core.shared.GWT; @@ -45,6 +46,8 @@ public PasswordPanel(boolean editmode) { changed = false; editLayout = new FlowPanel(); + + // Initialize Widgets editPassword = new PasswordTextBox(); editPasswordRepeat = new PasswordTextBox(); editPasswordLabel = new Label(messages.password()); @@ -53,10 +56,9 @@ public PasswordPanel(boolean editmode) { editPasswordRepeatLabel.addStyleName("form-label"); editPasswordNote = new Label(messages.passwordNote()); - editLayout.add(editPasswordLabel); - editLayout.add(editPassword); - editLayout.add(editPasswordRepeatLabel); - editLayout.add(editPasswordRepeat); + // Build the layout using the generic form structure + editLayout.add(createFieldRow(editPasswordLabel, editPassword)); + editLayout.add(createFieldRow(editPasswordRepeatLabel, editPasswordRepeat)); editButton = new Button(messages.userDataChangePassword()); editButton.addClickHandler(new ClickHandler() { @@ -70,11 +72,11 @@ public void onClick(ClickEvent event) { }); if (editmode) { - FlowPanel editButtonPanel = new FlowPanel(); Label passwordLabel = new Label(messages.password()); passwordLabel.addStyleName("form-label"); - editButtonPanel.add(passwordLabel); - editButtonPanel.add(editButton); + + // Wrap the button in the same generic form structure + FlowPanel editButtonPanel = createFieldRow(passwordLabel, editButton); setWidget(editButtonPanel); buttonMode = true; } else { @@ -97,21 +99,41 @@ public void onKeyUp(KeyUpEvent event) { editPassword.addStyleName("password-input"); editPassword.addStyleName("form-textbox"); editPassword.getElement().setTitle(messages.password()); - // WCAGUtilities.getInstance().makeAccessible(editPassword.getParent().getElement()); editPasswordRepeat.addStyleName("password-input"); editPasswordRepeat.addStyleName("form-textbox"); editPasswordRepeat.getElement().setTitle(messages.password()); - // WCAGUtilities.getInstance().makeAccessible(editPasswordRepeat.getParent().getElement()); editPasswordNote.addStyleName("password-note"); - // WCAGUtilities.getInstance().makeAccessible(editPasswordNote.getParent().getElement()); editButton.addStyleName("password-button"); editButton.addStyleName("btn"); editButton.addStyleName("btn-play"); } + /** + * Helper method to structure custom widgets exactly like the GenericDataForm + * text boxes. + */ + private FlowPanel createFieldRow(Widget labelWidget, Widget inputWidget) { + FlowPanel searchField = new FlowPanel(); + searchField.addStyleName("search-field"); + + FlowPanel leftPanel = new FlowPanel(); + leftPanel.addStyleName("search-field-left-panel"); + + FlowPanel inputPanel = new FlowPanel(); + inputPanel.addStyleName("search-field-input-panel full_width"); + + // Assemble the DOM + inputPanel.add(inputWidget); + leftPanel.add(labelWidget); + leftPanel.add(inputPanel); + searchField.add(leftPanel); + + return searchField; + } + public boolean isChanged() { return changed; } @@ -181,5 +203,4 @@ public void clear() { public HandlerRegistration addValueChangeHandler(ValueChangeHandler handler) { return addHandler(handler, ValueChangeEvent.getType()); } - -} +} \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/PermissionsPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/PermissionsPanel.java deleted file mode 100644 index 4ec6467a41..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/PermissionsPanel.java +++ /dev/null @@ -1,291 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE file at the root of the source - * tree and available online at - * - * https://github.com/keeps/roda - */ -/** - * - */ -package org.roda.wui.client.management.members; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.MissingResourceException; -import java.util.Set; - -import org.roda.wui.common.client.ClientLogger; -import org.roda.wui.common.client.tools.ConfigurationManager; -import org.roda.wui.common.client.widgets.LoadingPopup; -import org.roda.wui.common.client.widgets.wcag.WCAGUtilities; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.event.logical.shared.HasValueChangeHandlers; -import com.google.gwt.event.logical.shared.ValueChangeEvent; -import com.google.gwt.event.logical.shared.ValueChangeHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.CheckBox; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.HorizontalPanel; -import com.google.gwt.user.client.ui.Label; - -import config.i18n.client.ClientMessages; - -/** - * @author Luis Faria - * - */ -public class PermissionsPanel extends FlowPanel implements HasValueChangeHandlers> { - - private class Permission extends HorizontalPanel implements HasValueChangeHandlers { - - private final String role; - private boolean locked; - private boolean enabled; - private final CheckBox checkbox; - private final Label descriptionLabel; - - public Permission(String role, String description) { - this.role = role; - this.checkbox = new CheckBox(); - this.descriptionLabel = new Label(description); - this.add(checkbox); - this.add(descriptionLabel); - this.locked = false; - this.enabled = true; - - this.descriptionLabel.addClickHandler(new ClickHandler() { - - @Override - public void onClick(ClickEvent event) { - if (isEnabled() && !locked) { - checkbox.setValue(!checkbox.getValue()); - onChange(); - } - } - }); - - this.checkbox.addValueChangeHandler(new ValueChangeHandler() { - - @Override - public void onValueChange(ValueChangeEvent event) { - onChange(); - } - }); - - this.addStyleName("permission"); - WCAGUtilities.addTitleToCheckbox(checkbox, description); - checkbox.addStyleName("permission-checkbox"); - descriptionLabel.setStylePrimaryName("permission-description"); - } - - public boolean isLocked() { - return locked; - } - - public void setLocked(boolean locked) { - this.locked = locked; - checkbox.setEnabled(!locked); - } - - public boolean isChecked() { - return checkbox.getValue(); - } - - public void setChecked(boolean checked) { - checkbox.setValue(checked); - } - - public String getRole() { - return role; - } - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - if (!locked) { - this.checkbox.setEnabled(enabled); - if (enabled) { - this.descriptionLabel.removeStyleDependentName("off"); - this.descriptionLabel.addStyleDependentName("on"); - } else { - this.descriptionLabel.removeStyleDependentName("on"); - this.descriptionLabel.addStyleDependentName("off"); - } - } - } - - @Override - public HandlerRegistration addValueChangeHandler(ValueChangeHandler handler) { - return addHandler(handler, ValueChangeEvent.getType()); - } - - protected void onChange() { - ValueChangeEvent.fire(this, getValue()); - } - - public String getValue() { - return getRole(); - } - - private PermissionsPanel getOuterType() { - return PermissionsPanel.this; - } - } - - private static final ClientMessages messages = GWT.create(ClientMessages.class); - - @SuppressWarnings("unused") - private final ClientLogger logger = new ClientLogger(getClass().getName()); - - private final List permissions; - - private List userSelections; - - private boolean enabled; - - private final LoadingPopup loading; - - /** - * - */ - public PermissionsPanel() { - this.permissions = new ArrayList<>(); - this.userSelections = new ArrayList<>(); - - loading = new LoadingPopup(this); - loading.show(); - - this.enabled = true; - - this.addStyleName("permissions"); - } - - public void init(final AsyncCallback callback) { - List roles = ConfigurationManager.getStringList("ui.role"); - for (String role : roles) { - String description; - try { - description = messages.role(role); - } catch (MissingResourceException e) { - description = role + " (needs translation)"; - } - - Permission permission = new Permission(role, description); - permissions.add(permission); - PermissionsPanel.this.add(permission); - permission.addValueChangeHandler(new ValueChangeHandler() { - - @Override - public void onValueChange(ValueChangeEvent event) { - if (userSelections.contains(event.getValue())) { - userSelections.remove(event.getValue()); - } else { - userSelections.add(event.getValue()); - } - onChange(); - } - }); - } - - loading.hide(); - callback.onSuccess(true); - } - - public List getUserSelections() { - return userSelections; - } - - /** - * Set all permissions defined by roles checked and set locked with parameters - * - * @param roles - * roles of the permissions to check - * @param lock - * if permissions should also be locked - */ - public void checkPermissions(Set roles, boolean lock) { - Iterator it = roles.iterator(); - - while (it.hasNext()) { - String role = it.next(); - boolean foundit = false; - for (Iterator j = permissions.iterator(); j.hasNext() && !foundit;) { - Permission p = j.next(); - if (p.getRole().equals(role)) { - foundit = true; - p.setChecked(true); - p.setLocked(lock); - } - } - } - } - - @Override - public void clear() { - for (Permission p : permissions) { - p.setChecked(false); - p.setLocked(false); - } - } - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - for (Permission p : permissions) { - p.setEnabled(enabled); - } - } - - public void updateLockedPermissions(Set memberGroups) { - if (!memberGroups.isEmpty()) { - this.setEnabled(false); - loading.show(); - } - } - - /** - * Get roles that are directly defined, i.e. are not inherited - * - * @return - */ - public Set getDirectRoles() { - List checkedPermissions = new ArrayList<>(); - for (Permission p : permissions) { - if (p.isChecked() && !p.isLocked()) { - checkedPermissions.add(p); - } - } - - Set specialRoles = new HashSet<>(); - for (Permission checkedPermission : checkedPermissions) { - specialRoles.add(checkedPermission.getRole()); - } - - return specialRoles; - } - - @Override - public HandlerRegistration addValueChangeHandler(ValueChangeHandler> handler) { - return addHandler(handler, ValueChangeEvent.getType()); - } - - protected void onChange() { - ValueChangeEvent.fire(this, getValue()); - } - - public List getValue() { - return getUserSelections(); - } -} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/Profile.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/Profile.java index e2247d65bb..18881847f2 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/Profile.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/Profile.java @@ -122,7 +122,7 @@ public Profile(User user) { initWidget(uiBinder.createAndBindUi(this)); - userDataPanel.setUsernameReadOnly(true); + //userDataPanel.setUsernameReadOnly(true); } private SecureString getPassword() { diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/Register.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/Register.java index 89598b961b..47b0f2c4be 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/Register.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/Register.java @@ -145,7 +145,7 @@ public Register() { } void setExtra(Set b) { - this.userDataPanel.setUserExtra(b); + //this.userDataPanel.setUserExtra(b); } @UiHandler("buttonApply") diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/ShowMember.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/ShowMember.java index d286ebec35..d11cb899ce 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/ShowMember.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/ShowMember.java @@ -39,8 +39,8 @@ public class ShowMember extends Composite { public void resolve(List historyTokens, final AsyncCallback callback) { if (historyTokens.size() == 1) { String username = historyTokens.get(0); - Services services = new Services("Get User", "get"); - services.membersResource(s -> s.getUser(username)).whenComplete((member, error) -> { + Services services = new Services("Get Member", "get"); + services.membersResource(s -> s.getMember(username)).whenComplete((member, error) -> { if (member != null) { ShowMember showMember = new ShowMember(member); callback.onSuccess(showMember); @@ -132,7 +132,7 @@ private void initHandlers(RODAMember member) { () -> HistoryUtils.newHistory(MemberManagement.RESOLVER.getHistoryPath())); // Change this to use the DOM Swap refresh method instead of HistoryUtils - handlers.put(Actionable.ActionImpact.UPDATED, () -> refreshToolbar(member.getUUID(), member.isUser())); + handlers.put(Actionable.ActionImpact.UPDATED, () -> refreshToolbar(member.getId(), member.isUser())); } private void refreshToolbar(String id, boolean isUser) { diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/UserDataPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/UserDataPanel.java index 2c4a9bff0c..8152137c11 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/UserDataPanel.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/UserDataPanel.java @@ -5,9 +5,7 @@ * * https://github.com/keeps/roda */ -/** - * - */ + package org.roda.wui.client.management.members; import java.util.ArrayList; @@ -16,28 +14,20 @@ import org.roda.core.data.v2.generics.MetadataValue; import org.roda.core.data.v2.user.User; +import org.roda.wui.client.common.forms.GenericDataForm; +import org.roda.wui.client.common.forms.GenericDataPanel; import org.roda.wui.client.common.utils.FormUtilities; import org.roda.wui.common.client.ClientLogger; import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.ChangeEvent; -import com.google.gwt.event.dom.client.ChangeHandler; -import com.google.gwt.event.dom.client.KeyUpEvent; -import com.google.gwt.event.dom.client.KeyUpHandler; import com.google.gwt.event.logical.shared.HasValueChangeHandlers; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.uibinder.client.UiBinder; -import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.ListBox; -import com.google.gwt.user.client.ui.TextBox; -import com.google.gwt.user.client.ui.Widget; import config.i18n.client.ClientMessages; @@ -45,244 +35,109 @@ * @author Luis Faria * */ -public class UserDataPanel extends Composite implements HasValueChangeHandlers { +public class UserDataPanel extends Composite implements GenericDataPanel, HasValueChangeHandlers { private static final ClientMessages messages = GWT.create(ClientMessages.class); - private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); - @SuppressWarnings("unused") private final ClientLogger logger = new ClientLogger(getClass().getName()); + private final boolean editMode; - @UiField - TextBox username; - @UiField - Label usernameError; - @UiField(provided = true) - PasswordPanel password; - @UiField - Label passwordError; - @UiField - TextBox fullName; - @UiField - Label fullNameError; - @UiField - TextBox email; - @UiField - Label emailError; - @UiField - FlowPanel extra; - @UiField - HTML errors; - // has to be true to detected new field changes private boolean changed = false; - private boolean checked = false; private Set userExtra; - /** - * Create a new user data panel - * - * @param visible - * @param editMode - */ - public UserDataPanel(boolean visible, boolean editMode) { - - password = new PasswordPanel(editMode); - initWidget(uiBinder.createAndBindUi(this)); + // UI Components + private FlowPanel mainContainer = new FlowPanel(); + private GenericDataForm userForm = new GenericDataForm<>(); + private PasswordPanel passwordPanel; + private FlowPanel extraFieldsPanel = new FlowPanel(); + private HTML errors = new HTML(); + public UserDataPanel(boolean visible, boolean editMode) { this.editMode = editMode; - super.setVisible(visible); + this.passwordPanel = new PasswordPanel(editMode); - errors.setVisible(false); + // 1. Setup Generic Fields + userForm.addReadOnlyField(messages.username(), User::getName, true); + userForm.addTextField(messages.fullname(), User::getFullName, User::setFullName, true, false); - ValueChangeHandler valueChangedHandler = new ValueChangeHandler() { + // Email with Regex Validation + userForm.addTextField(messages.email(), User::getEmail, User::setEmail, true, false, + "^[_A-Za-z0-9-%+]+(\\.[_A-Za-z0-9-%+]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(\\.[_A-Za-z0-9-]+)", + messages.emailNotValid()); - @Override - public void onValueChange(ValueChangeEvent event) { - onChange(); - } - }; - - ChangeHandler changeHandler = new ChangeHandler() { + // userForm.addCustomWidget(passwordPanel); + userForm.addCustomWidget(extraFieldsPanel); - @Override - public void onChange(ChangeEvent event) { - UserDataPanel.this.onChange(); - } - }; + // 3. Assemble Main Layout + mainContainer.add(userForm); - KeyUpHandler keyUpHandler = new KeyUpHandler() { - - @Override - public void onKeyUp(KeyUpEvent event) { - onChange(); - } - }; - - username.addKeyDownHandler(new UserAndGroupKeyDownHandler()); - - username.addChangeHandler(changeHandler); - username.addKeyUpHandler(keyUpHandler); - password.addValueChangeHandler(valueChangedHandler); - fullName.addChangeHandler(changeHandler); - fullName.addKeyUpHandler(keyUpHandler); + errors.setVisible(false); + mainContainer.add(errors); - usernameError.setVisible(false); - passwordError.setVisible(false); - fullNameError.setVisible(false); - emailError.setVisible(false); - } + initWidget(mainContainer); + super.setVisible(visible); - @SuppressWarnings("unused") - private int setSelected(ListBox listbox, String text) { - int index = -1; - if (text != null) { - for (int i = 0; i < listbox.getItemCount(); i++) { - if (listbox.getValue(i).equals(text)) { - index = i; - break; - } - } - if (index >= 0) { - listbox.setSelectedIndex(index); - } else { - listbox.addItem(text); - index = listbox.getItemCount() - 1; - listbox.setSelectedIndex(index); - } - } else { - listbox.setSelectedIndex(-1); - } - return index; + // 4. Handlers + ValueChangeHandler changeHandler = event -> onChange(); + userForm.addValueChangeHandler(changeHandler); + passwordPanel.addValueChangeHandler(event -> onChange()); } - /** - * Get user defined by this panel. This panel defines: name, fullname, title, - * organization name, postal address, postal code, locality, country, email, - * phone number, fax and which groups this user belongs to. - * - * @return the user modified by this panel - */ public User getUser() { - User user = new User(); - user.setId(username.getText()); - user.setName(username.getText()); - user.setFullName(fullName.getText()); - user.setEmail(email.getText()); - - return user; + return userForm.getValue(); } - /** - * Set user information of user - * - * @param user - */ public void setUser(User user) { - this.username.setText(user.getName()); - this.fullName.setText(user.getFullName()); - this.email.setText(user.getEmail()); + userForm.setModel(user); this.userExtra = user.getExtra(); - + createForm(this.userExtra); this.changed = false; } public void createForm(Set userExtra) { - extra.clear(); - FormUtilities.create(extra, userExtra, false); + extraFieldsPanel.clear(); + FormUtilities.create(extraFieldsPanel, userExtra, false); } - /** - * Get the password - * - * @return the password if changed, or null if it remains the same - */ public String getPassword() { - return password.getValue(); + return passwordPanel.getValue(); } - /** - * Check if password changed - * - * @return true if password changed, false otherwise - */ public boolean isPasswordChanged() { - return password.isChanged(); + return passwordPanel.isChanged(); + } + + public Set getUserExtra() { + return userExtra; } - /** - * Is user data panel valid - * - * @return true if valid - */ + @Override public boolean isValid() { List errorList = new ArrayList<>(); - if (username.getText().isEmpty()) { - username.addStyleName("isWrong"); - usernameError.setText(messages.mandatoryField()); - usernameError.setVisible(true); - Window.scrollTo(username.getAbsoluteLeft(), username.getAbsoluteTop()); - errorList.add(messages.isAMandatoryField(messages.username())); - } else { - username.removeStyleName("isWrong"); - usernameError.setVisible(false); + + // 1. Validate Generic Form + if (!userForm.isValid()) { + errorList.add(messages.mandatoryField() + " / " + messages.emailNotValid()); } - if (!password.matchConfirmation()) { - if (errorList.isEmpty()) { - Window.scrollTo(password.getAbsoluteLeft(), password.getAbsoluteTop()); - } + // 2. Validate Password Panel + if (!passwordPanel.matchConfirmation()) { + Window.scrollTo(passwordPanel.getAbsoluteLeft(), passwordPanel.getAbsoluteTop()); errorList.add(messages.passwordDoesNotMatchConfirmation()); - } else if (password.isSmall()) { - if (errorList.isEmpty()) { - Window.scrollTo(password.getAbsoluteLeft(), password.getAbsoluteTop()); - } + } else if (passwordPanel.isSmall()) { + Window.scrollTo(passwordPanel.getAbsoluteLeft(), passwordPanel.getAbsoluteTop()); errorList.add(messages.passwordIsTooSmall()); } - if (fullName.getText().isEmpty()) { - fullName.addStyleName("isWrong"); - fullNameError.setText(messages.mandatoryField()); - fullNameError.setVisible(true); - if (errorList.isEmpty()) { - Window.scrollTo(fullName.getAbsoluteLeft(), fullName.getAbsoluteTop()); - } - errorList.add(messages.isAMandatoryField(messages.fullname())); - } else { - fullName.removeStyleName("isWrong"); - fullNameError.setVisible(false); - } - - if (email.getText() == null || "".equals(email.getText().trim())) { - email.addStyleName("isWrong"); - emailError.setText(messages.mandatoryField()); - emailError.setVisible(true); - if (errorList.isEmpty()) { - Window.scrollTo(email.getAbsoluteLeft(), email.getAbsoluteTop()); - } - errorList.add(messages.isAMandatoryField(messages.email())); - } else if (!email.getText() - .matches("^[_A-Za-z0-9-%+]+(\\.[_A-Za-z0-9-%+]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(\\.[_A-Za-z0-9-]+)")) { - email.addStyleName("isWrong"); - emailError.setText(messages.wrongMailFormat()); - emailError.setVisible(true); - if (errorList.isEmpty()) { - Window.scrollTo(email.getAbsoluteLeft(), email.getAbsoluteTop()); - } - errorList.add(messages.emailNotValid()); - } else { - email.removeStyleName("isWrong"); - emailError.setVisible(false); - } - - List extraErrors = FormUtilities.validate(userExtra, extra); + // 3. Validate Extra Fields + List extraErrors = FormUtilities.validate(userExtra, extraFieldsPanel); errorList.addAll(extraErrors); - checked = true; + // 4. Render Errors if (!errorList.isEmpty()) { errors.setVisible(true); StringBuilder errorString = new StringBuilder(); for (String error : errorList) { - errorString.append("").append(error).append(""); - errorString.append("
"); + errorString.append("").append(error).append("
"); } errors.setHTML(errorString.toString()); } else { @@ -292,54 +147,13 @@ public boolean isValid() { return errorList.isEmpty(); } - /** - * Is user name read only - * - * @return true if read only - */ - public boolean isUsernameReadOnly() { - return username.isReadOnly(); - } - - /** - * Set user name read only - * - * @param readonly - */ - public void setUsernameReadOnly(boolean readonly) { - username.setReadOnly(readonly); - } - - @Override - public void setVisible(boolean visible) { - super.setVisible(visible); - } - - public void clear() { - username.setText(""); - password.clear(); - fullName.setText(""); - email.setText(""); - - this.changed = false; - } - - /** - * Is user data panel editable, i.e. on create user mode - * - * @return true if editable - */ - public boolean isEditMode() { - return editMode; + public boolean isChanged() { + return changed || userForm.isChanged(); } - /** - * Is user data panel has been changed - * - * @return changed - */ - public boolean isChanged() { - return changed; + protected void onChange() { + changed = true; + ValueChangeEvent.fire(this, getUser()); } @Override @@ -347,27 +161,8 @@ public HandlerRegistration addValueChangeHandler(ValueChangeHandler handle return addHandler(handler, ValueChangeEvent.getType()); } - protected void onChange() { - changed = true; - if (checked) { - isValid(); - } - ValueChangeEvent.fire(this, getValue()); - } - + @Override public User getValue() { return getUser(); } - - public Set getUserExtra() { - return userExtra; - } - - public void setUserExtra(Set extra) { - UserDataPanel.this.userExtra = extra; - createForm(extra); - } - - interface MyUiBinder extends UiBinder { - } } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/UserDetailsPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/UserDetailsPanel.java deleted file mode 100644 index ec4d000d2d..0000000000 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/UserDetailsPanel.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.roda.wui.client.management.members; - -/** - * @author Miguel Guimarães - */ - -public class UserDetailsPanel { -} diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/data/panels/GroupDataPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/data/panels/GroupDataPanel.java new file mode 100644 index 0000000000..a9c2ed7096 --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/data/panels/GroupDataPanel.java @@ -0,0 +1,109 @@ +package org.roda.wui.client.management.members.data.panels; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.FlowPanel; +import config.i18n.client.ClientMessages; +import org.roda.core.data.v2.user.Group; +import org.roda.wui.client.common.forms.GenericDataForm; +import org.roda.wui.client.common.forms.GenericDataPanel; + +import com.google.gwt.event.logical.shared.HasValueChangeHandlers; +import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.ui.Composite; + +/** + * @author Miguel Guimarães + */ +public class GroupDataPanel extends Composite implements GenericDataPanel, HasValueChangeHandlers { + private static final ClientMessages messages = GWT.create(ClientMessages.class); + + private final GenericDataForm groupForm; + private final boolean editMode; + + private final Button saveButton; + private final Button cancelButton; + + public GroupDataPanel(boolean editMode) { + this.editMode = editMode; + this.groupForm = new GenericDataForm<>(); + + if (editMode) { + groupForm.addReadOnlyField(messages.groupName(), Group::getName, true); + groupForm.addTextField(messages.groupFullname(), Group::getFullName, Group::setFullName, true); + } else { + groupForm.addTextField(messages.groupName(), Group::getName, Group::setName, true); + groupForm.addTextField(messages.groupFullname(), Group::getFullName, Group::setFullName, true); + } + + // 1. Initialize Buttons + saveButton = new Button(messages.saveButton()); + saveButton.addStyleName("btn btn-primary btn-play"); + + cancelButton = new Button(messages.cancelButton()); + cancelButton.addStyleName("btn btn-link"); + + // 2. Wrap buttons in a FlowPanel for spacing + FlowPanel actionsPanel = new FlowPanel(); + actionsPanel.addStyleName("alignButtonsPanel"); // Uses your existing CSS spacing + actionsPanel.add(saveButton); + actionsPanel.add(cancelButton); + + // 3. Inject the buttons at the bottom of the generic form + groupForm.addCustomWidget(actionsPanel); + + // Initialize the composite using the generic form as the root widget + initWidget(groupForm); + } + + /** + * Defines what happens when the Save button is clicked. + * It automatically validates the form before executing the runnable. + */ + public void setSaveHandler(Runnable onSave) { + saveButton.addClickHandler(event -> { + if (isValid()) { + onSave.run(); + } + }); + } + + /** + * Defines what happens when the Cancel button is clicked. + */ + public void setCancelHandler(Runnable onCancel) { + cancelButton.addClickHandler(event -> onCancel.run()); + } + + public void setGroup(Group group) { + groupForm.setModel(group); + } + + @Override + public Group getValue() { + return groupForm.getValue(); + } + + @Override + public boolean isValid() { + return groupForm.isValid(); + } + + public boolean isChanged() { + return groupForm.isChanged(); + } + + public boolean isEditMode() { + return editMode; + } + + @Override + public HandlerRegistration addValueChangeHandler(ValueChangeHandler handler) { + return groupForm.addValueChangeHandler(handler); + } + + public void clear() { + setGroup(new Group()); + } +} \ No newline at end of file diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/data/panels/PasswordDataPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/data/panels/PasswordDataPanel.java new file mode 100644 index 0000000000..0cadb2cafe --- /dev/null +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/management/members/data/panels/PasswordDataPanel.java @@ -0,0 +1,189 @@ +package org.roda.wui.client.management.members.data.panels; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.ChangeHandler; +import com.google.gwt.event.dom.client.KeyUpHandler; +import com.google.gwt.event.logical.shared.HasValueChangeHandlers; +import com.google.gwt.event.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.PasswordTextBox; +import com.google.gwt.user.client.ui.Widget; +import config.i18n.client.ClientMessages; +import org.roda.wui.client.common.forms.GenericDataPanel; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Miguel Guimarães + */ + +public class PasswordDataPanel extends Composite implements GenericDataPanel, HasValueChangeHandlers { + + private static final ClientMessages messages = GWT.create(ClientMessages.class); + private final boolean mandatory; + private PasswordTextBox passwordBox; + private PasswordTextBox confirmBox; + private HTML errors; + private boolean changed = false; + + /** + * @param mandatory + * If true, the password cannot be empty. If false (e.g., Edit Mode), + * it only validates if the user types something. + */ + public PasswordDataPanel(boolean mandatory) { + this.mandatory = mandatory; + + FlowPanel mainContainer = new FlowPanel(); + mainContainer.addStyleName("generic-data-panel-fields"); + + // 1. Initialize Inputs + passwordBox = new PasswordTextBox(); + passwordBox.addStyleName("form-textbox"); + + confirmBox = new PasswordTextBox(); + confirmBox.addStyleName("form-textbox"); + + // 2. Initialize Labels + Label passwordLabel = new Label(messages.password()); + if (mandatory) { + passwordLabel.setText(passwordLabel.getText() + "*"); + } + passwordLabel.addStyleName("form-label"); + + Label confirmLabel = new Label(messages.passwordConfirmation()); + if (mandatory) { + confirmLabel.setText(confirmLabel.getText() + "*"); + } + confirmLabel.addStyleName("form-label"); + + // 4. Setup Error HTML + errors = new HTML(); + errors.setVisible(false); + mainContainer.add(errors); + + // 3. Add to layout using the stacked helper + mainContainer.add(createStackedFieldRow(passwordLabel, passwordBox)); + mainContainer.add(createStackedFieldRow(confirmLabel, confirmBox)); + + // 5. Setup Handlers + ChangeHandler changeHandler = event -> onChange(); + KeyUpHandler keyUpHandler = event -> onChange(); + + passwordBox.addChangeHandler(changeHandler); + passwordBox.addKeyUpHandler(keyUpHandler); + confirmBox.addChangeHandler(changeHandler); + confirmBox.addKeyUpHandler(keyUpHandler); + + initWidget(mainContainer); + } + + /** + * Helper method to create a stacked layout:
{0,number} intellektuellen Entitäten mit dieser Repräsentationsinformation verknüpft representationInformationIntellectualEntities[\=0]:Es gibt keine intellektuellen Entitäten, die mit dieser Repräsentationsinformartion verknüpft sind representationInformationIntellectualEntities[\=1]:Es ist eine intellektuelle Entität mit dieser Repräsentationsinformation verknüpft +representationInformationIntellectualEntities[one]:Es ist eine intellektuelle Entität mit dieser Repräsentationsinformation verknüpft representationInformationRepresentations:Es sind {0,number} Repräsentationen mit dieser Repräsentationsinformation verknüpft representationInformationRepresentations[\=0]:Es gibt keine Repräsentationen, die mit dieser Repräsentationsinformartion verknüpft sind representationInformationRepresentations[\=1]:Es ist eine Repräsentation mit dieser Repräsentationsinformation verknüpft +representationInformationRepresentations[one]:Es ist eine Repräsentation mit dieser Repräsentationsinformation verknüpft representationInformationFiles:Es sind {0,number} Dateien mit dieser Repräsentationsinformation verknüpft representationInformationFiles[\=0]:Es gibt keine Dateien, die mit dieser Repräsentationsinformartion verknüpft sind representationInformationFiles[\=1]:Es ist eine Datei mit dieser Repräsentationsinformation verknüpft +representationInformationFiles[one]:Es ist eine Datei mit dieser Repräsentationsinformation verknüpft # Descriptive Metadata metadataType:Typ @@ -1518,6 +1522,7 @@ clearDisposalHoldDialogMessage[one]:Sind Sie sicher, dass Sie alle Entsorgungssp clearDisposalHoldDialogMessage:Sind Sie sicher, dass Sie alle Entsorgungssperren auf die ausgewählten {0, number} Elemente aufheben möchten? liftDisposalHoldDialogTitle:Kassationsperre aufheben liftDisposalHoldDialogMessage[\=1]:Sind Sie sicher, dass Sie die Kassationsperre aufheben wollen? +liftDisposalHoldDialogMessage[one]:Sind Sie sicher, dass Sie die Kassationsperre aufheben wollen? liftDisposalHoldDialogMessage:Sind Sie sicher, dass Sie die {0, Zahl} ausgewählten Kassationsperren aufheben wollen? disassociateDisposalHoldDialogTitle:Entsorgungssperre aufheben disassociateDisposalHoldDialogMessage[one]:Sind Sie sicher, dass Sie die Entsorgungssperre auf das ausgewählte Element aufheben möchten? @@ -1529,6 +1534,7 @@ disposalHoldAssociatedOn:Verbunden am disposalHoldAssociatedBy:Verbunden von disposalHoldAssociatedFrom:Verbunden von disposalHoldAssociatedFromValue[\=1]:{0} AIP +disposalHoldAssociatedFromValue[one]:{0} AIP disposalHoldAssociatedFromValue:{0} AIPs disposalScheduleAssociationInformationTitle:Aufbewahrugsfrist disposalScheduleAssociationTitle:Entsorgungsplan @@ -1559,12 +1565,16 @@ disposalPolicyRetainPermanently:Dauerhaft erhalten disposalPolicyDestroyedAIPSummary:Vernichtet am {0} retentionPeriod:{0} retentionPeriod[\=1|DAYS]:1 Tag +retentionPeriod[one|DAYS]:1 Tag retentionPeriod[other|DAYS]:{0,number} Tage retentionPeriod[\=1|WEEKS]:1 Woche +retentionPeriod[one|WEEKS]:1 Woche retentionPeriod[other|WEEKS]:{0,number} Wochen retentionPeriod[\=1|MONTHS]:1 Monat +retentionPeriod[one|MONTHS]:1 Monat retentionPeriod[other|MONTHS]:{0,number} Monate retentionPeriod[\=1|YEARS]:1 Jahr +retentionPeriod[one|YEARS]:1 Jahr retentionPeriod[other|YEARS]:{0,number} Jahre retentionPeriod[other|NO_RETENTION_PERIOD]:Keine Aufbewahrugsfrist permanentlyDeleteConfirmDialogTitle:Datensätze dauerhaft aus dem Papierkorb löschen diff --git a/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_de_DE.properties b/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_de_DE.properties index 58308e8e32..93ec52c6e6 100644 --- a/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_de_DE.properties +++ b/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_de_DE.properties @@ -263,6 +263,7 @@ oneOfAObject[org.roda.core.data.v2.disposal.hold.DisposalHold]:Kassationssperre oneOfAObject[org.roda.core.data.v2.disposal.confirmation.DisposalConfirmation]:Kassationsbestätigung oneOfAObject[org.roda.core.data.v2.disposal.rule.DisposalRule]:Kassationsregel selected[\=1]:{0,number} {1} ausgewählt +selected[one]:{0,number} {1} ausgewählt selected:{0,number} {1} ausgewählt inspectTransferredResource:Übertragene Ressource untersuchen identifierNotFound:Indentifikator (nicht gefunden) @@ -841,12 +842,15 @@ representationInformationEditAssociations:Assoziationen bearbeiten representationInformationIntellectualEntities:Es sind {0,number} intellektuellen Entitäten mit dieser Repräsentationsinformation verknüpft representationInformationIntellectualEntities[\=0]:Es gibt keine intellektuellen Entitäten, die mit dieser Repräsentationsinformartion verknüpft sind representationInformationIntellectualEntities[\=1]:Es ist eine intellektuelle Entität mit dieser Repräsentationsinformation verknüpft +representationInformationIntellectualEntities[one]:Es ist eine intellektuelle Entität mit dieser Repräsentationsinformation verknüpft representationInformationRepresentations:Es sind {0,number} Repräsentationen mit dieser Repräsentationsinformation verknüpft representationInformationRepresentations[\=0]:Es gibt keine Repräsentationen, die mit dieser Repräsentationsinformartion verknüpft sind representationInformationRepresentations[\=1]:Es ist eine Repräsentation mit dieser Repräsentationsinformation verknüpft +representationInformationRepresentations[one]:Es ist eine Repräsentation mit dieser Repräsentationsinformation verknüpft representationInformationFiles:Es sind {0,number} Dateien mit dieser Repräsentationsinformation verknüpft representationInformationFiles[\=0]:Es gibt keine Dateien, die mit dieser Repräsentationsinformartion verknüpft sind representationInformationFiles[\=1]:Es ist eine Datei mit dieser Repräsentationsinformation verknüpft +representationInformationFiles[one]:Es ist eine Datei mit dieser Repräsentationsinformation verknüpft # Descriptive Metadata metadataType:Typ @@ -1518,6 +1522,7 @@ clearDisposalHoldDialogMessage[one]:Sind Sie sicher, dass Sie alle Kassationsspe clearDisposalHoldDialogMessage:Sind Sie sicher, dass Sie alle Kassationssperren für die ausgewählten {0, number} Elemente aufheben möchten? liftDisposalHoldDialogTitle:Kassationsperre aufheben liftDisposalHoldDialogMessage[\=1]:Sind Sie sicher, dass Sie die Kassationsperre aufheben wollen? +liftDisposalHoldDialogMessage[one]:Sind Sie sicher, dass Sie die Kassationsperre aufheben wollen? liftDisposalHoldDialogMessage:Sind Sie sicher, dass Sie die {0, Zahl} ausgewählten Kassationsperren aufheben wollen? disassociateDisposalHoldDialogTitle:Kassationssperre aufheben disassociateDisposalHoldDialogMessage[one]:Sind Sie sicher, dass Sie die Kassationssperre für das ausgewählte Element aufheben möchten? @@ -1529,6 +1534,7 @@ disposalHoldAssociatedOn:Assoziiert am disposalHoldAssociatedBy:Assoziiert druch disposalHoldAssociatedFrom:Assoziiert von disposalHoldAssociatedFromValue[\=1]:{0} AIP +disposalHoldAssociatedFromValue[one]:{0} AIP disposalHoldAssociatedFromValue:{0} AIPs disposalScheduleAssociationInformationTitle:Aufbewahrugsfrist disposalScheduleAssociationTitle:Kassationsplan @@ -1559,12 +1565,16 @@ disposalPolicyRetainPermanently:Dauerhaft erhalten disposalPolicyDestroyedAIPSummary:Vernichtet am {0} retentionPeriod:{0} retentionPeriod[\=1|DAYS]:1 Tag +retentionPeriod[one|DAYS]:1 Tag retentionPeriod[other|DAYS]:{0,number} Tage retentionPeriod[\=1|WEEKS]:1 Woche +retentionPeriod[one|WEEKS]:1 Woche retentionPeriod[other|WEEKS]:{0,number} Wochen retentionPeriod[\=1|MONTHS]:1 Monat +retentionPeriod[one|MONTHS]:1 Monat retentionPeriod[other|MONTHS]:{0,number} Monate retentionPeriod[\=1|YEARS]:1 Jahr +retentionPeriod[one|YEARS]:1 Jahr retentionPeriod[other|YEARS]:{0,number} Jahre retentionPeriod[other|NO_RETENTION_PERIOD]:Keine Aufbewahrugsfrist permanentlyDeleteConfirmDialogTitle:Datensätze dauerhaft aus dem Papierkorb löschen diff --git a/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_es.properties b/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_es.properties index 16ea5b2a27..fd88b7d393 100644 --- a/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_es.properties +++ b/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_es.properties @@ -263,6 +263,7 @@ oneOfAObject[org.roda.core.data.v2.disposal.hold.DisposalHold]:disposal hold oneOfAObject[org.roda.core.data.v2.disposal.confirmation.DisposalConfirmation]:disposal confirmation oneOfAObject[org.roda.core.data.v2.disposal.rule.DisposalRule]:disposal rule selected[\=1]:{0,number} {1} selected +selected[one]:{0,number} {1} selected selected:{0,number} {1} selected inspectTransferredResource:Inspeccionar recursos transferidos identifierNotFound:Identificador (no encontrado) @@ -841,12 +842,15 @@ representationInformationEditAssociations:Edit associations representationInformationIntellectualEntities:There are {0,number} intellectual entities associated with this representation information representationInformationIntellectualEntities[\=0]:There are no intellectual entities associated with this representation information representationInformationIntellectualEntities[\=1]:There is one intellectual entity associated with this representation information +representationInformationIntellectualEntities[one]:There is one intellectual entity associated with this representation information representationInformationRepresentations:There are {0,number} representations associated with this representation information representationInformationRepresentations[\=0]:There are no representations associated with this representation information representationInformationRepresentations[\=1]:There is one representation associated with this representation information +representationInformationRepresentations[one]:There is one representation associated with this representation information representationInformationFiles:There are {0,number} files associated with this representation information representationInformationFiles[\=0]:There are no files associated with this representation information representationInformationFiles[\=1]:There is one file associated with this representation information +representationInformationFiles[one]:There is one file associated with this representation information # Descriptive Metadata metadataType:Tipo @@ -1518,6 +1522,7 @@ clearDisposalHoldDialogMessage[one]:Are you sure you want to disassociate all di clearDisposalHoldDialogMessage:Are you sure you want to disassociate all disposal holds from the selected {0, number} items? liftDisposalHoldDialogTitle:Lift disposal hold liftDisposalHoldDialogMessage[\=1]:Are you sure you want to lift the disposal hold? +liftDisposalHoldDialogMessage[one]:Are you sure you want to lift the disposal hold? liftDisposalHoldDialogMessage:Are you sure you want to lift the {0, number} selected disposal holds? disassociateDisposalHoldDialogTitle:Disassociate disposal hold disassociateDisposalHoldDialogMessage[one]:Are you sure you want to disassociate the disposal hold from the selected item? @@ -1529,6 +1534,7 @@ disposalHoldAssociatedOn:Associated on disposalHoldAssociatedBy:Associated by disposalHoldAssociatedFrom:Associated from disposalHoldAssociatedFromValue[\=1]:{0} AIP +disposalHoldAssociatedFromValue[one]:{0} AIP disposalHoldAssociatedFromValue:{0} AIPs disposalScheduleAssociationInformationTitle:Retention period disposalScheduleAssociationTitle:Disposal schedule @@ -1559,12 +1565,16 @@ disposalPolicyRetainPermanently:Permanently retained disposalPolicyDestroyedAIPSummary:Destroyed on {0} retentionPeriod:{0} retentionPeriod[\=1|DAYS]:1 día +retentionPeriod[one|DAYS]:1 día retentionPeriod[other|DAYS]:{0,number} days retentionPeriod[\=1|WEEKS]:1 week +retentionPeriod[one|WEEKS]:1 week retentionPeriod[other|WEEKS]:{0,number} weeks retentionPeriod[\=1|MONTHS]:1 month +retentionPeriod[one|MONTHS]:1 month retentionPeriod[other|MONTHS]:{0,number} months retentionPeriod[\=1|YEARS]:1 year +retentionPeriod[one|YEARS]:1 year retentionPeriod[other|YEARS]:{0,number} years retentionPeriod[other|NO_RETENTION_PERIOD]:No retention period permanentlyDeleteConfirmDialogTitle:Permanently delete records from disposal bin diff --git a/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_hr.properties b/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_hr.properties index 42a764e1ad..670bac7a9c 100644 --- a/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_hr.properties +++ b/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_hr.properties @@ -200,6 +200,8 @@ oneOfAObject[org.roda.core.data.v2.ip.disposal.DisposalHold]:izlučivanje postav oneOfAObject[org.roda.core.data.v2.ip.disposal.DisposalConfirmation]:potvrda izlučivanja oneOfAObject[org.roda.core.data.v2.ip.disposal.DisposalRule]:pravilo izlučivanja selected[\=1]:{0,number} {1} odabrano +selected[one]:{0,number} {1} odabrano +selected[few]:{0,number} {1} odabrano selected:{0,number} {1} odabrano inspectTransferredResource:Pregledajte preneseni resurs identifierNotFound:Identifikator (nije pronađen) @@ -713,12 +715,18 @@ representationInformationEditAssociations:Uredite udruživanja representationInformationIntellectualEntities:{0, number} intelektualnih entiteta je povezano s ovim pojavnim oblikom representationInformationIntellectualEntities[\=0]:Nema intelektualnih entiteta povezanih s ovim pojavnim oblikom representationInformationIntellectualEntities[\=1]:Jedan intelektualni entitet je povezan s ovim pojavnim oblikom +representationInformationIntellectualEntities[one]:{0, number} intelektualni entitet je povezan s ovim pojavnim oblikom +representationInformationIntellectualEntities[few]:{0, number} intelektualna entiteta su povezana s ovim pojavnim oblikom representationInformationRepresentations:{0, number} prikaza je povezano s ovim pojavnim oblikom representationInformationRepresentations[\=0]:Nema prikaza povezanih s ovim pojavnim oblikom representationInformationRepresentations[\=1]:Jedan prikaz je povezan s ovim pojavnim oblikom +representationInformationRepresentations[one]:{0, number} prikaz je povezan s ovim pojavnim oblikom +representationInformationRepresentations[few]:{0, number} prikaza su povezana s ovim pojavnim oblikom representationInformationFiles:{0, number} datoteka je povezano s ovim pojavnim oblikom representationInformationFiles[\=0]:Nema datoteka povezanih s ovim pojavnim oblikom representationInformationFiles[\=1]:Jedna datoteka je povezana s ovim pojavnim oblikom +representationInformationFiles[one]:{0, number} datoteka je povezana s ovim pojavnim oblikom +representationInformationFiles[few]:{0, number} datoteke su povezane s ovim pojavnim oblikom # Descriptive Metadata metadataType:Vrsta metadataFilename:Naziv datoteke @@ -1327,6 +1335,8 @@ associateDisposalHoldButton:Pridruži izlučivanje postavljeno na čekanje disposalHoldSelectionDialogTitle:Pridruži izlučivanje postavljeno na čekanje applyDisposalHoldDialogTitle:Potvrdite povezanost izlučivanja koje je postavljeno na čekanje s intelektualnim entitetom applyDisposalHoldDialogMessage[\=1]:Jeste li sigurni da želite primijeniti izlučivanje postavljeno na čekanje na odabranu stavku? +applyDisposalHoldDialogMessage[one]:Jeste li sigurni da želite primijeniti izlučivanje postavljeno na čekanje na odabranu stavku? +applyDisposalHoldDialogMessage[few]:Jeste li sigurni da želite primijeniti izlučivanje postavljeno na čekanje na odabranih {0, number} predmeta? applyDisposalHoldDialogMessage:Jeste li sigurni da želite primijeniti izlučivanje postavljeno na čekanje na odabranih {0, number} predmeta? applyDisposalHoldButton:Pridruži izlučivanje postavljeno na čekanje createDisposalHoldButton:Kreiraj izlučivanje koje je postavljeno na čekanje @@ -1334,17 +1344,30 @@ clearDisposalHoldButton:Očisti izlučivanja postavljena na čekanje overrideDisposalHoldButton:Nadjačaj izlučivanje postavljeno na čekanje clearDisposalHoldDialogTitle:Očisti izlučivanja postavljena na čekanje clearDisposalHoldDialogMessage[\=1]:Jeste li sigurni da želite razdvojiti sva izlučivanja postavljena na čekanje od odabrane stavke? +clearDisposalHoldDialogMessage[one]:Jeste li sigurni da želite razdvojiti sva izlučivanja postavljena na čekanje od odabrane stavke? +clearDisposalHoldDialogMessage[few]:Jeste li sigurni da želite razdvojiti sva izlučivanja postavljena na čekanje od odabranih {0, number} stavaka? clearDisposalHoldDialogMessage:Jeste li sigurni da želite razdvojiti sva izlučivanja postavljena na čekanje od odabranih {0, number} stavaka? +liftDisposalHoldDialogTitle:Ukini izlučivanje postavljeno na čekanje +liftDisposalHoldDialogMessage[\=1]:Jeste li sigurni da želite ukinuti izlučivanje postavljeno na čekanje? +liftDisposalHoldDialogMessage[one]:Jeste li sigurni da želite ukinuti izlučivanje postavljeno na čekanje? +liftDisposalHoldDialogMessage[few]:Jeste li sigurni da želite ukinuti {0, number} odabranih izlučivanja postavljenih na čekanje? +liftDisposalHoldDialogMessage:Jeste li sigurni da želite ukinuti {0, number} odabranih izlučivanja postavljenih na čekanje? disassociateDisposalHoldDialogTitle:Razdvoji izlučivanje postavljeno na čekanje disassociateDisposalHoldDialogMessage[\=1]:Jeste li sigurni da želite razdvojiti izlučivanje koje je postavljeno na čekanje od odabrane stavke? +disassociateDisposalHoldDialogMessage[one]:Jeste li sigurni da želite razdvojiti izlučivanje koje je postavljeno na čekanje od odabrane stavke? +disassociateDisposalHoldDialogMessage[few]:Jeste li sigurni da želite razdvojiti izlučivanje koje je postavljeno na čekanje od odabranih {0, number} stavaka? disassociateDisposalHoldDialogMessage:Jeste li sigurni da želite razdvojiti izlučivanje koje je postavljeno na čekanje od odabranih {0, number} stavaka? disassociateDisposalScheduleDialogTitle:Razdvoji raspored izlučivanja disassociateDisposalScheduleDialogMessage[\=1]:Jeste li sigurni da želite razdvojiti raspored izlučivanja od odabrane stavke? +disassociateDisposalScheduleDialogMessage[one]:Jeste li sigurni da želite razdvojiti raspored izlučivanja od odabrane stavke? +disassociateDisposalScheduleDialogMessage[few]:Jeste li sigurni da želite razdvojiti raspored izlučivanja od odabranih {0, number} stavaka? disassociateDisposalScheduleDialogMessage:Jeste li sigurni da želite razdvojiti raspored izlučivanja od odabranih {0, number} stavaka? disposalHoldAssociatedOn:Pridruženo disposalHoldAssociatedBy:Pridružio disposalHoldAssociatedFrom:Pridružen od disposalHoldAssociatedFromValue[\=1]:{0} AIP +disposalHoldAssociatedFromValue[one]:{0} AIP +disposalHoldAssociatedFromValue[few]:{0} AIP-a disposalHoldAssociatedFromValue:{0} AIP-ova disposalScheduleAssociationInformationTitle:Razdoblje čuvanja disposalScheduleAssociationTitle:Raspored izlučivanja @@ -1363,11 +1386,17 @@ disposalPolicyActionSummary:{0} disposalPolicyActionSummary[REVIEW]:revizija disposalPolicyActionSummary[DESTROY]:uništavanje disposalPolicyScheduleMonthSummary[\=1]:za 1 mjesec +disposalPolicyScheduleMonthSummary[one]:za {0,number} mjesec +disposalPolicyScheduleMonthSummary[few]:za {0,number} mjeseca disposalPolicyScheduleMonthSummary:za {0,number} mjeseci disposalPolicyScheduleYearSummary[\=1]:za 1 godinu +disposalPolicyScheduleYearSummary[one]:za {0,number} godinu +disposalPolicyScheduleYearSummary[few]:za {0,number} godine disposalPolicyScheduleYearSummary:za {0,number} godina disposalPolicyScheduleDaySummary[\=0]:danas disposalPolicyScheduleDaySummary[\=1]:sutra +disposalPolicyScheduleDaySummary[one]:za {0, number} dan +disposalPolicyScheduleDaySummary[few]:za {0, number} dana disposalPolicyScheduleDaySummary:za {0, number} dana disposalPolicySummaryReady:Prekoračen rok za {0} permanentlyRetained:Nikada @@ -1375,12 +1404,20 @@ disposalPolicyRetainPermanently:Trajno zadržana disposalPolicyDestroyedAIPSummary:Uništetno {0} retentionPeriod:{0} retentionPeriod[\=1|DAYS]:1 dan +retentionPeriod[one|DAYS]:{0,number} dan +retentionPeriod[few|DAYS]:{0,number} dana retentionPeriod[other|DAYS]:{0,number} dana retentionPeriod[\=1|WEEKS]:1 tjedan +retentionPeriod[one|WEEKS]:{0,number} tjedan +retentionPeriod[few|WEEKS]:{0,number} tjedna retentionPeriod[other|WEEKS]:{0,number} tjedana retentionPeriod[\=1|MONTHS]:1 mjesec +retentionPeriod[one|MONTHS]:{0,number} mjesec +retentionPeriod[few|MONTHS]:{0,number} mjeseca retentionPeriod[other|MONTHS]:{0,number} mjeseci retentionPeriod[\=1|YEARS]:1 godina +retentionPeriod[one|YEARS]:{0,number} godina +retentionPeriod[few|YEARS]:{0,number} godine retentionPeriod[other|YEARS]:{0,number} godina retentionPeriod[other|NO_RETENTION_PERIOD]:Nema razdoblja čuvanja permanentlyDeleteConfirmDialogTitle:Trajno izbrišite zapise iz pretinca obrisanih informacijskih paketa diff --git a/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_hu.properties b/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_hu.properties index 2d1ab0af82..e1ac146b87 100644 --- a/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_hu.properties +++ b/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_hu.properties @@ -1496,20 +1496,20 @@ deleteDisposalRuleDialogMessage:Are you sure you want to remove the disposal rul associateDisposalHoldButton:Associate disposal hold disposalHoldSelectionDialogTitle:Associate disposal hold applyDisposalHoldDialogTitle:Confirm disposal hold association to intellectual entity -applyDisposalHoldDialogMessage[one]:Are you sure you want to apply the disposal hold to the selected item? +applyDisposalHoldDialogMessage[\=1]:Are you sure you want to apply the disposal hold to the selected item? applyDisposalHoldDialogMessage:Are you sure you want to apply the disposal hold to the selected {0, number} items? applyDisposalHoldButton:Associate disposal hold createDisposalHoldButton:Create disposal hold clearDisposalHoldButton:Clear disposal holds overrideDisposalHoldButton:Override disposal hold clearDisposalHoldDialogTitle:Clear disposal holds -clearDisposalHoldDialogMessage[one]:Are you sure you want to disassociate all disposal holds from the selected item? +clearDisposalHoldDialogMessage[\=1]:Are you sure you want to disassociate all disposal holds from the selected item? clearDisposalHoldDialogMessage:Are you sure you want to disassociate all disposal holds from the selected {0, number} items? disassociateDisposalHoldDialogTitle:Disassociate disposal hold -disassociateDisposalHoldDialogMessage[one]:Are you sure you want to disassociate the disposal hold from the selected item? +disassociateDisposalHoldDialogMessage[\=1]:Are you sure you want to disassociate the disposal hold from the selected item? disassociateDisposalHoldDialogMessage:Are you sure you want to disassociate the disposal hold from the selected {0, number} items? disassociateDisposalScheduleDialogTitle:Disassociate disposal schedule -disassociateDisposalScheduleDialogMessage[one]:Are you sure you want to disassociate the disposal schedule from the selected item? +disassociateDisposalScheduleDialogMessage[\=1]:Are you sure you want to disassociate the disposal schedule from the selected item? disassociateDisposalScheduleDialogMessage:Are you sure you want to disassociate the disposal schedule from the selected {0, number} items? disposalHoldAssociatedOn:Társítva disposalHoldAssociatedBy:Associated by @@ -1532,12 +1532,12 @@ disposalPolicyConfirmationSummary:Assigned to a disposal confirmation disposalPolicyActionSummary:{0} disposalPolicyActionSummary[REVIEW]:felülvizsgálat disposalPolicyActionSummary[DESTROY]:megsemmisítés -disposalPolicyScheduleMonthSummary[one]:1 hónap alatt +disposalPolicyScheduleMonthSummary[\=1]:1 hónap alatt disposalPolicyScheduleMonthSummary:{0,number} hónap alatt -disposalPolicyScheduleYearSummary[one]:1 év alatt +disposalPolicyScheduleYearSummary[\=1]:1 év alatt disposalPolicyScheduleYearSummary:{0,number} év alatt disposalPolicyScheduleDaySummary[\=0]:ma -disposalPolicyScheduleDaySummary[one]:holnap +disposalPolicyScheduleDaySummary[\=1]:holnap disposalPolicyScheduleDaySummary:{0,number} nap alatt disposalPolicySummaryReady:Overdue for {0} permanentlyRetained:Soha diff --git a/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_pt_PT.properties b/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_pt_PT.properties index d6e7f00ffa..500d9f5209 100644 --- a/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_pt_PT.properties +++ b/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_pt_PT.properties @@ -268,6 +268,7 @@ oneOfAObject[org.roda.core.data.v2.disposal.hold.DisposalHold]:suspensão de eli oneOfAObject[org.roda.core.data.v2.disposal.confirmation.DisposalConfirmation]:autos de eliminação oneOfAObject[org.roda.core.data.v2.disposal.rule.DisposalRule]:condição de eliminação selected[\=1]:{0,number} {1} selecionados +selected[one]:{0,number} {1} selecionados selected:{0,number} {1} selecionados inspectTransferredResource:Inspecionar recurso transferido identifierNotFound:Identificador (não encontrado) @@ -871,12 +872,15 @@ representationInformationEditAssociations:Editar associações representationInformationIntellectualEntities:Existem {0,number} entidades intelectuais relacionadas com esta informação de representação representationInformationIntellectualEntities[\=0]:Não existem entidades intelectuais relacionadas com esta informação de representação representationInformationIntellectualEntities[\=1]:Existe uma entidade intelectual relacionada com esta informação de representação +representationInformationIntellectualEntities[one]:Existe uma entidade intelectual relacionada com esta informação de representação representationInformationRepresentations:Existem {0,number} representações relacionadas com esta informação de representação representationInformationRepresentations[\=0]:Não existem representações relacionadas com esta informação de representação representationInformationRepresentations[\=1]:Existe uma representação relacionada com esta informação de representação +representationInformationRepresentations[one]:Existe uma representação relacionada com esta informação de representação representationInformationFiles:Existem {0,number} ficheiros relacionados com esta informação de representação representationInformationFiles[\=0]:Não existem ficheiros relacionados com esta informação de representação representationInformationFiles[\=1]:Existe um ficheiro relacionado com esta informação de representação +representationInformationFiles[one]:Existe um ficheiro relacionado com esta informação de representação # Descriptive Metadata metadataType:Tipo @@ -1566,6 +1570,7 @@ clearDisposalHoldDialogMessage[one]:Tem a certeza que pretende desassociar todas clearDisposalHoldDialogMessage:Tem a certeza que pretende desassociar todas as suspensões de eliminação dos {0, number} items selecionados? liftDisposalHoldDialogTitle:Levantar suspensão de eliminação liftDisposalHoldDialogMessage[\=1]:Tem a certeza que quer levantar a suspensão de eliminação? +liftDisposalHoldDialogMessage[one]:Tem a certeza que quer levantar a suspensão de eliminação? liftDisposalHoldDialogMessage:Tem a certeza que quer levantar as {0, number} suspensões de eliminação selecionadas? disassociateDisposalHoldDialogTitle:Desassociar suspensão de eliminação disassociateDisposalHoldDialogMessage[one]:Tem a certeza que prentende desassociar a suspensão de eliminação do item selecionado? @@ -1577,6 +1582,7 @@ disposalHoldAssociatedOn:Associado em disposalHoldAssociatedBy:Associado por disposalHoldAssociatedFrom:Associado desde disposalHoldAssociatedFromValue[\=1]:{0} AIP +disposalHoldAssociatedFromValue[one]:{0} AIP disposalHoldAssociatedFromValue:{0} AIPs disposalScheduleAssociationInformationTitle:Prazo de conservação disposalScheduleAssociationTitle:Tabela de seleção @@ -1608,12 +1614,16 @@ disposalPolicyRetainPermanently:Conservado permanentemente disposalPolicyDestroyedAIPSummary:Eliminado em {0} retentionPeriod:{0} retentionPeriod[\=1|DAYS]:1 dia +retentionPeriod[one|DAYS]:1 dia retentionPeriod[other|DAYS]:{0,number} dias retentionPeriod[\=1|WEEKS]:1 semana +retentionPeriod[one|WEEKS]:1 semana retentionPeriod[other|WEEKS]:{0,number} semanas retentionPeriod[\=1|MONTHS]:1 mês +retentionPeriod[one|MONTHS]:1 mês retentionPeriod[other|MONTHS]:{0,number} meses retentionPeriod[\=1|YEARS]:1 ano +retentionPeriod[one|YEARS]:1 ano retentionPeriod[other|YEARS]:{0,number} anos retentionPeriod[other|NO_RETENTION_PERIOD]:Sem prazo de conservação permanentlyDeleteConfirmDialogTitle:Eliminar permanentemente entidades da caixa de eliminação diff --git a/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_sv_SE.properties b/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_sv_SE.properties index 4c7a7a23f6..e1bbeb24e8 100644 --- a/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_sv_SE.properties +++ b/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_sv_SE.properties @@ -261,6 +261,7 @@ oneOfAObject[org.roda.core.data.v2.ip.disposal.DisposalHold]:gallringsstopp oneOfAObject[org.roda.core.data.v2.ip.disposal.DisposalConfirmation]:gallringsbekräftelse oneOfAObject[org.roda.core.data.v2.ip.disposal.DisposalRule]:gallringsregler selected[\=1]:{0,number} {1} vald +selected[one]:{0,number} {1} vald selected:{0,number} {1} vald inspectTransferredResource:Inspektera överförd resurs identifierNotFound:Identifierare (hittades inte) @@ -833,12 +834,15 @@ representationInformationEditAssociations:Redigera association representationInformationIntellectualEntities:Det finns {0,number} logiska enheter associerade med denna representationsinformation representationInformationIntellectualEntities[\=0]:Det finns inga logiska enheter associerade med denna representationsinformation representationInformationIntellectualEntities[\=1]:Det finns en logisk enhet associerad med denna representationsinformation +representationInformationIntellectualEntities[one]:Det finns en logisk enhet associerad med denna representationsinformation representationInformationRepresentations:Det finns {0,number} representationer associerade till denna representationsinformation representationInformationRepresentations[\=0]:Det finns inga representationer kopplade till denna representationsinformation representationInformationRepresentations[\=1]:Det finns en representation associerad med denna representationsinformation +representationInformationRepresentations[one]:Det finns en representation associerad med denna representationsinformation representationInformationFiles:Det finns {0, number} filer associerade med denna representationsinformation representationInformationFiles[\=0]:Det finns inga filer associerade med denna representationsinformation representationInformationFiles[\=1]:Det finns en fil associerad med denna representationsinformation +representationInformationFiles[one]:Det finns en fil associerad med denna representationsinformation # Descriptive Metadata metadataType:Typ @@ -1505,6 +1509,10 @@ overrideDisposalHoldButton:Åsidosätt gallringsstopp clearDisposalHoldDialogTitle:Rensa bort gallringsstopp clearDisposalHoldDialogMessage[one]:Är du säker på att du vill koppla bort alla gallringsstopp från det valda objektet? clearDisposalHoldDialogMessage:Är du säker på att du vill koppla bort alla gallringsstopp från de valda {0, number} objekten? +liftDisposalHoldDialogTitle:Häv gallringsstopp +liftDisposalHoldDialogMessage[\=1]:Är du säker på att du vill häva gallringsstoppet? +liftDisposalHoldDialogMessage[one]:Är du säker på att du vill häva gallringsstoppet? +liftDisposalHoldDialogMessage:Är du säker på att du vill häva de {0, number} valda gallringsstoppen? disassociateDisposalHoldDialogTitle:Koppla loss gallringsstopp disassociateDisposalHoldDialogMessage[one]:Är du säker på att du vill koppla bort gallringsstoppet från det valda objektet? disassociateDisposalHoldDialogMessage:Är du säker på att du vill koppla bort gallringsstoppet från de valda {0, number} objekten? @@ -1515,6 +1523,7 @@ disposalHoldAssociatedOn:Associerad med disposalHoldAssociatedBy:Associerad av disposalHoldAssociatedFrom:Associerad från disposalHoldAssociatedFromValue[\=1]:{0} AIP +disposalHoldAssociatedFromValue[one]:{0} AIP disposalHoldAssociatedFromValue:{0} AIP:er disposalScheduleAssociationInformationTitle:Gallringsfrist disposalScheduleAssociationTitle:Gallringsschema @@ -1545,12 +1554,16 @@ disposalPolicyRetainPermanently:Behålls permanent disposalPolicyDestroyedAIPSummary:Gallrades {0} retentionPeriod:{0} retentionPeriod[\=1|DAYS]:1 dag +retentionPeriod[one|DAYS]:1 dag retentionPeriod[other|DAYS]:{0,number} dagar retentionPeriod[\=1|WEEKS]:1 vecka +retentionPeriod[one|WEEKS]:1 vecka retentionPeriod[other|WEEKS]:{0,number} veckor retentionPeriod[\=1|MONTHS]:1 månad +retentionPeriod[one|MONTHS]:1 månad retentionPeriod[other|MONTHS]:{0,number} månader retentionPeriod[\=1|YEARS]:1 år +retentionPeriod[one|YEARS]:1 år retentionPeriod[other|YEARS]:{0,number} år retentionPeriod[other|NO_RETENTION_PERIOD]:Ingen gallringsfrist permanentlyDeleteConfirmDialogTitle:Ta permanent bort objekt från papperskorgen From d1ecd59bb1af875b6763374e5d09fe89b12a139a Mon Sep 17 00:00:00 2001 From: Luis Faria Date: Mon, 4 May 2026 13:35:31 +0100 Subject: [PATCH 27/37] fix(search): remove space in block-join queries to fix q.op=AND interaction Solr's standard query parser tokenizes {!parent which=...} and the child query as SEPARATE clauses when whitespace separates them. With q.op=AND, the child query becomes a required top-level clause matching child documents directly, returning 0 AIP results. Removing the space makes the child/parent filter directly adjacent to the closing brace, so Solr correctly treats it as the sub-query for the local params parser: {!parent which=}. Extend NestedDocumentSearchTest with a layer-5 assertion that uses AllFilterParameter + ParentWhichFilterParameter (the production scenario), covering the *:* AND {!parent ...} query form. Co-Authored-By: Claude Sonnet 4.6 --- .../core/index/NestedDocumentSearchTest.java | 18 ++++++++++++++++++ .../org/roda/core/index/utils/SolrUtils.java | 4 ++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/roda-core/roda-core-tests/src/main/java/org/roda/core/index/NestedDocumentSearchTest.java b/roda-core/roda-core-tests/src/main/java/org/roda/core/index/NestedDocumentSearchTest.java index 418d874a6d..2936248db3 100644 --- a/roda-core/roda-core-tests/src/main/java/org/roda/core/index/NestedDocumentSearchTest.java +++ b/roda-core/roda-core-tests/src/main/java/org/roda/core/index/NestedDocumentSearchTest.java @@ -26,6 +26,7 @@ import org.roda.core.data.common.RodaConstants; import org.roda.core.data.exceptions.RODAException; import org.roda.core.data.v2.index.IndexResult; +import org.roda.core.data.v2.index.filter.AllFilterParameter; import org.roda.core.data.v2.index.filter.AndFiltersParameters; import org.roda.core.data.v2.index.filter.Filter; import org.roda.core.data.v2.index.filter.ParentWhichFilterParameter; @@ -155,5 +156,22 @@ public void testParentWhichFilterFindsEmailArchiveAIPByChildSender() throws Exce assertEquals("ParentWhichFilterParameter should return exactly 1 AIP", 1L, result.getTotalCount()); assertEquals("Returned AIP id should match", aipId, result.getResults().get(0).getId()); + + // Layer 5: same block-join combined with AllFilterParameter (*:* AND {!parent ...}) + // This reproduces the production scenario where q.op=AND was breaking the query + Filter combinedFilter = new Filter( + new AllFilterParameter(), + new ParentWhichFilterParameter( + new SimpleFilterParameter("content_type", "emailarchive"), + new AndFiltersParameters(Arrays.asList( + new SimpleFilterParameter("sender_s", "joao.silva@empresa.pt"), + new SimpleFilterParameter("content_type", "email"))))); + + IndexResult combinedResult = index.find(IndexedAIP.class, combinedFilter, Sorter.NONE, + new Sublist(0, 10), Collections.emptyList()); + + assertEquals("AllFilter + ParentWhichFilterParameter should return exactly 1 AIP", 1L, + combinedResult.getTotalCount()); + assertEquals("Returned AIP id should match", aipId, combinedResult.getResults().get(0).getId()); } } diff --git a/roda-core/roda-core/src/main/java/org/roda/core/index/utils/SolrUtils.java b/roda-core/roda-core/src/main/java/org/roda/core/index/utils/SolrUtils.java index 56b56b7eb4..f8f36cf5ce 100644 --- a/roda-core/roda-core/src/main/java/org/roda/core/index/utils/SolrUtils.java +++ b/roda-core/roda-core/src/main/java/org/roda/core/index/utils/SolrUtils.java @@ -1041,7 +1041,7 @@ private static void appendBlockJoinChildrenFilterParameter(StringBuilder ret, Ch StringBuilder someParents = new StringBuilder(); parseFilterParameter(someParents, parameter.getParentFilter(), false); - ret.append("{!child of=").append(replace).append("} ").append(someParents); + ret.append("{!child of=").append(replace).append("}").append(someParents); } else { ret.append("{!child of=").append(replace).append("}"); } @@ -1058,7 +1058,7 @@ private static void appendBlockJoinFilterParameter(StringBuilder ret, ParentWhic StringBuilder someChildren = new StringBuilder(); parseFilterParameter(someChildren, parameter.getChildrenFilter(), false); - ret.append("{!parent which=").append(replace).append("} ").append(someChildren); + ret.append("{!parent which=").append(replace).append("}").append(someChildren); } else { ret.append("{!parent which=").append(replace).append("}"); } From ef3154a188b30068865ca3be0ee179244cf878d4 Mon Sep 17 00:00:00 2001 From: Luis Faria Date: Mon, 4 May 2026 14:38:42 +0100 Subject: [PATCH 28/37] fix(search): fix block-join query format for q.op=AND compatibility - Use double-quoted which= local param: `which="field:value"` instead of `which=(field:"value")` - Use required-operator (+) prefix in child/parent sub-queries: `(+field:"value")` - Skip AllFilterParameter when block-join params are present to avoid spurious *:* AND prefix - Add dedicated buildBlockJoinMask() and buildBlockJoinSubQuery() helpers to generate correct Solr block-join syntax without relying on the standard filter-to-query path - Add string-level query assertion in NestedDocumentSearchTest (Layer 4) to lock in format - Update Layer 5 to assert AllFilterParameter is suppressed when block-join is present Co-Authored-By: Claude Sonnet 4.6 --- .../core/index/NestedDocumentSearchTest.java | 17 +++- .../org/roda/core/index/utils/SolrUtils.java | 89 +++++++++++++++---- 2 files changed, 88 insertions(+), 18 deletions(-) diff --git a/roda-core/roda-core-tests/src/main/java/org/roda/core/index/NestedDocumentSearchTest.java b/roda-core/roda-core-tests/src/main/java/org/roda/core/index/NestedDocumentSearchTest.java index 2936248db3..028d6bd937 100644 --- a/roda-core/roda-core-tests/src/main/java/org/roda/core/index/NestedDocumentSearchTest.java +++ b/roda-core/roda-core-tests/src/main/java/org/roda/core/index/NestedDocumentSearchTest.java @@ -36,6 +36,7 @@ import org.roda.core.data.v2.ip.AIP; import org.roda.core.data.v2.ip.IndexedAIP; import org.roda.core.index.utils.IterableIndexResult; +import org.roda.core.index.utils.SolrUtils; import org.roda.core.model.ModelService; import org.roda.core.security.LdapUtilityTestHelper; import org.roda.core.storage.DefaultStoragePath; @@ -143,7 +144,7 @@ public void testParentWhichFilterFindsEmailArchiveAIPByChildSender() throws Exce long childCount = childResp.getResults().getNumFound(); assertTrue("Child email documents should be indexed in Solr (found " + childCount + ")", childCount > 0); - // Layer 4: block-join ParentWhichFilterParameter returns the parent AIP + // Layer 4: block-join ParentWhichFilterParameter — verify generated query string and Solr result Filter nestedFilter = new Filter( new ParentWhichFilterParameter( new SimpleFilterParameter("content_type", "emailarchive"), @@ -151,14 +152,19 @@ public void testParentWhichFilterFindsEmailArchiveAIPByChildSender() throws Exce new SimpleFilterParameter("sender_s", "joao.silva@empresa.pt"), new SimpleFilterParameter("content_type", "email"))))); + String nestedQuery = SolrUtils.parseFilter(nestedFilter); + assertEquals("Block-join query must use double-quoted which= and + required operators", + "{!parent which=\"content_type:emailarchive\"}(+sender_s:\"joao.silva@empresa.pt\" +content_type:\"email\")", + nestedQuery); + IndexResult result = index.find(IndexedAIP.class, nestedFilter, Sorter.NONE, new Sublist(0, 10), Collections.emptyList()); assertEquals("ParentWhichFilterParameter should return exactly 1 AIP", 1L, result.getTotalCount()); assertEquals("Returned AIP id should match", aipId, result.getResults().get(0).getId()); - // Layer 5: same block-join combined with AllFilterParameter (*:* AND {!parent ...}) - // This reproduces the production scenario where q.op=AND was breaking the query + // Layer 5: AllFilterParameter combined with ParentWhichFilterParameter must not emit *:* prefix + // (production scenario: default filter adds AllFilterParameter, should not break block-join) Filter combinedFilter = new Filter( new AllFilterParameter(), new ParentWhichFilterParameter( @@ -167,6 +173,11 @@ public void testParentWhichFilterFindsEmailArchiveAIPByChildSender() throws Exce new SimpleFilterParameter("sender_s", "joao.silva@empresa.pt"), new SimpleFilterParameter("content_type", "email"))))); + String combinedQuery = SolrUtils.parseFilter(combinedFilter); + assertEquals("AllFilterParameter must be dropped when block-join is present", + "{!parent which=\"content_type:emailarchive\"}(+sender_s:\"joao.silva@empresa.pt\" +content_type:\"email\")", + combinedQuery); + IndexResult combinedResult = index.find(IndexedAIP.class, combinedFilter, Sorter.NONE, new Sublist(0, 10), Collections.emptyList()); diff --git a/roda-core/roda-core/src/main/java/org/roda/core/index/utils/SolrUtils.java b/roda-core/roda-core/src/main/java/org/roda/core/index/utils/SolrUtils.java index f8f36cf5ce..7004f212c7 100644 --- a/roda-core/roda-core/src/main/java/org/roda/core/index/utils/SolrUtils.java +++ b/roda-core/roda-core/src/main/java/org/roda/core/index/utils/SolrUtils.java @@ -982,7 +982,12 @@ public static String parseFilter(Filter filter) throws RequestNotValidException if (filter == null || filter.getParameters().isEmpty()) { return ret.toString(); } else { + boolean hasBlockJoin = filter.getParameters().stream() + .anyMatch(p -> p instanceof ParentWhichFilterParameter || p instanceof ChildOfFilterParameter); for (FilterParameter parameter : filter.getParameters()) { + if (hasBlockJoin && parameter instanceof AllFilterParameter) { + continue; + } parseFilterParameter(ret, parameter, true); } } @@ -1033,34 +1038,88 @@ private static void parseFilterParameter(StringBuilder ret, FilterParameter para private static void appendBlockJoinChildrenFilterParameter(StringBuilder ret, ChildOfFilterParameter parameter, boolean prefixWithANDOperatorIfBuilderNotEmpty) throws RequestNotValidException { appendANDOperator(ret, prefixWithANDOperatorIfBuilderNotEmpty); - StringBuilder blockMask = new StringBuilder(); - parseFilterParameter(blockMask, parameter.getChildrenOfFilter(), false); - String replace = blockMask.toString().replace(": ", ":"); + String maskValue = buildBlockJoinMask(parameter.getChildrenOfFilter()); + String escapedMask = maskValue.replace("\"", "\\\""); if (parameter.getParentFilter() != null) { - StringBuilder someParents = new StringBuilder(); - parseFilterParameter(someParents, parameter.getParentFilter(), false); - - ret.append("{!child of=").append(replace).append("}").append(someParents); + String parentQuery = buildBlockJoinSubQuery(parameter.getParentFilter()); + ret.append("{!child of=\"").append(escapedMask).append("\"}").append(parentQuery); } else { - ret.append("{!child of=").append(replace).append("}"); + ret.append("{!child of=\"").append(escapedMask).append("\"}"); } } private static void appendBlockJoinFilterParameter(StringBuilder ret, ParentWhichFilterParameter parameter, boolean prefixWithANDOperatorIfBuilderNotEmpty) throws RequestNotValidException { appendANDOperator(ret, prefixWithANDOperatorIfBuilderNotEmpty); - StringBuilder blockMask = new StringBuilder(); - parseFilterParameter(blockMask, parameter.getParentFilter(), false); - String replace = blockMask.toString().replace(": ", ":"); + String maskValue = buildBlockJoinMask(parameter.getParentFilter()); + String escapedMask = maskValue.replace("\"", "\\\""); if (parameter.getChildrenFilter() != null) { - StringBuilder someChildren = new StringBuilder(); - parseFilterParameter(someChildren, parameter.getChildrenFilter(), false); + String childQuery = buildBlockJoinSubQuery(parameter.getChildrenFilter()); + ret.append("{!parent which=\"").append(escapedMask).append("\"}").append(childQuery); + } else { + ret.append("{!parent which=\"").append(escapedMask).append("\"}"); + } + } + + private static String buildBlockJoinMask(FilterParameter parameter) throws RequestNotValidException { + if (parameter instanceof SimpleFilterParameter simplePar) { + String value = simplePar.getValue(); + if (value.matches("[\\w.-]+")) { + return simplePar.getName() + ":" + value; + } else { + return simplePar.getName() + ":\"" + value.replace("\"", "\\\"") + "\""; + } + } + StringBuilder sb = new StringBuilder(); + parseFilterParameter(sb, parameter, false); + String s = sb.toString().replace(": ", ":"); + if (s.startsWith("(") && s.endsWith(")")) { + s = s.substring(1, s.length() - 1); + } + return s; + } - ret.append("{!parent which=").append(replace).append("}").append(someChildren); + private static String buildBlockJoinSubQuery(FilterParameter parameter) throws RequestNotValidException { + StringBuilder sb = new StringBuilder(); + buildBlockJoinSubQueryClause(sb, parameter); + return "(" + sb.toString() + ")"; + } + + private static void buildBlockJoinSubQueryClause(StringBuilder sb, FilterParameter parameter) + throws RequestNotValidException { + if (parameter instanceof SimpleFilterParameter simplePar) { + sb.append("+").append(simplePar.getName()).append(":\"") + .append(simplePar.getValue().replace("\"", "\\\"")).append("\""); + } else if (parameter instanceof BasicSearchFilterParameter basicPar) { + String value = basicPar.getValue(); + if (StringUtils.isBlank(value) || "*".equals(value)) { + sb.append("+").append(basicPar.getName()).append(":*"); + } else { + String cleanValue = value.matches("^\".+\"$") ? value.substring(1, value.length() - 1) : value; + sb.append("+").append(basicPar.getName()).append(":\"").append(cleanValue.replace("\"", "\\\"")).append("\""); + } + } else if (parameter instanceof AndFiltersParameters andPar) { + List values = andPar.getValues(); + for (int i = 0; i < values.size(); i++) { + if (i > 0) { + sb.append(" "); + } + buildBlockJoinSubQueryClause(sb, values.get(i)); + } + } else if (parameter instanceof OrFiltersParameters orPar) { + sb.append("("); + List values = orPar.getValues(); + for (int i = 0; i < values.size(); i++) { + if (i > 0) { + sb.append(" OR "); + } + buildBlockJoinSubQueryClause(sb, values.get(i)); + } + sb.append(")"); } else { - ret.append("{!parent which=").append(replace).append("}"); + parseFilterParameter(sb, parameter, !sb.isEmpty()); } } From 60b833a6e7c4acff47332798ec42b0bda1c81ae4 Mon Sep 17 00:00:00 2001 From: Luis Faria Date: Mon, 4 May 2026 14:46:16 +0100 Subject: [PATCH 29/37] fix(emailarchive): index dateStart/dateEnd as standard dateInitial/dateFinal fields Map the email archive date range fields to the standard AIP date fields so they appear in the existing date range search facet without needing custom Solr fields. Co-Authored-By: Claude Sonnet 4.6 --- .../main/resources/config/crosswalks/ingest/emailarchive.xslt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/roda-core/roda-core/src/main/resources/config/crosswalks/ingest/emailarchive.xslt b/roda-core/roda-core/src/main/resources/config/crosswalks/ingest/emailarchive.xslt index b127b7ebf5..f720ec15e8 100644 --- a/roda-core/roda-core/src/main/resources/config/crosswalks/ingest/emailarchive.xslt +++ b/roda-core/roda-core/src/main/resources/config/crosswalks/ingest/emailarchive.xslt @@ -39,10 +39,10 @@ - T00:00:00Z + T00:00:00Z - T00:00:00Z + T00:00:00Z From dc7a849410dfac849aec2c2ac735a7daf543c497 Mon Sep 17 00:00:00 2001 From: Luis Faria Date: Mon, 4 May 2026 14:49:25 +0100 Subject: [PATCH 30/37] revert: remove AllFilterParameter suppression from parseFilter block-join path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The *:* AND prefix produced by AllFilterParameter alongside a block-join query is functionally harmless — *:* matches every document so intersecting with it leaves the block-join result unchanged. The specialization was unnecessary. Co-Authored-By: Claude Sonnet 4.6 --- .../java/org/roda/core/index/NestedDocumentSearchTest.java | 4 ++-- .../src/main/java/org/roda/core/index/utils/SolrUtils.java | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/roda-core/roda-core-tests/src/main/java/org/roda/core/index/NestedDocumentSearchTest.java b/roda-core/roda-core-tests/src/main/java/org/roda/core/index/NestedDocumentSearchTest.java index 028d6bd937..8923b1c622 100644 --- a/roda-core/roda-core-tests/src/main/java/org/roda/core/index/NestedDocumentSearchTest.java +++ b/roda-core/roda-core-tests/src/main/java/org/roda/core/index/NestedDocumentSearchTest.java @@ -174,8 +174,8 @@ public void testParentWhichFilterFindsEmailArchiveAIPByChildSender() throws Exce new SimpleFilterParameter("content_type", "email"))))); String combinedQuery = SolrUtils.parseFilter(combinedFilter); - assertEquals("AllFilterParameter must be dropped when block-join is present", - "{!parent which=\"content_type:emailarchive\"}(+sender_s:\"joao.silva@empresa.pt\" +content_type:\"email\")", + assertEquals("AllFilterParameter combined with block-join produces *:* AND prefix (harmless, matches everything)", + "*:* AND {!parent which=\"content_type:emailarchive\"}(+sender_s:\"joao.silva@empresa.pt\" +content_type:\"email\")", combinedQuery); IndexResult combinedResult = index.find(IndexedAIP.class, combinedFilter, Sorter.NONE, diff --git a/roda-core/roda-core/src/main/java/org/roda/core/index/utils/SolrUtils.java b/roda-core/roda-core/src/main/java/org/roda/core/index/utils/SolrUtils.java index 7004f212c7..a8913130d4 100644 --- a/roda-core/roda-core/src/main/java/org/roda/core/index/utils/SolrUtils.java +++ b/roda-core/roda-core/src/main/java/org/roda/core/index/utils/SolrUtils.java @@ -982,12 +982,7 @@ public static String parseFilter(Filter filter) throws RequestNotValidException if (filter == null || filter.getParameters().isEmpty()) { return ret.toString(); } else { - boolean hasBlockJoin = filter.getParameters().stream() - .anyMatch(p -> p instanceof ParentWhichFilterParameter || p instanceof ChildOfFilterParameter); for (FilterParameter parameter : filter.getParameters()) { - if (hasBlockJoin && parameter instanceof AllFilterParameter) { - continue; - } parseFilterParameter(ret, parameter, true); } } From 0d4d5c0e0a29cbc2c46fa94a3a7cb905082865b3 Mon Sep 17 00:00:00 2001 From: Luis Faria Date: Mon, 4 May 2026 14:53:32 +0100 Subject: [PATCH 31/37] fix(search): handle wildcard values in block-join child sub-query builder Wildcards (* ?) cannot appear inside quoted phrases in Solr's standard query parser. When a BasicSearchFilterParameter value contains wildcards, emit each whitespace-separated token as an unquoted +field:token* clause instead of the phrase-quoted form that was causing SyntaxError. Co-Authored-By: Claude Sonnet 4.6 --- .../main/java/org/roda/core/index/utils/SolrUtils.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/roda-core/roda-core/src/main/java/org/roda/core/index/utils/SolrUtils.java b/roda-core/roda-core/src/main/java/org/roda/core/index/utils/SolrUtils.java index a8913130d4..18a5edce61 100644 --- a/roda-core/roda-core/src/main/java/org/roda/core/index/utils/SolrUtils.java +++ b/roda-core/roda-core/src/main/java/org/roda/core/index/utils/SolrUtils.java @@ -1091,6 +1091,14 @@ private static void buildBlockJoinSubQueryClause(StringBuilder sb, FilterParamet String value = basicPar.getValue(); if (StringUtils.isBlank(value) || "*".equals(value)) { sb.append("+").append(basicPar.getName()).append(":*"); + } else if (value.contains("*") || value.contains("?")) { + // wildcards cannot appear inside quoted phrases — emit each token unquoted + for (String token : value.trim().split("\\s+")) { + if (sb.length() > 0) { + sb.append(" "); + } + sb.append("+").append(basicPar.getName()).append(":").append(escapeSolrSpecialChars(token)); + } } else { String cleanValue = value.matches("^\".+\"$") ? value.substring(1, value.length() - 1) : value; sb.append("+").append(basicPar.getName()).append(":\"").append(cleanValue.replace("\"", "\\\"")).append("\""); From 78f098da93c60779c62484dcf8feb8f5e51da632 Mon Sep 17 00:00:00 2001 From: Luis Faria Date: Mon, 4 May 2026 15:13:03 +0100 Subject: [PATCH 32/37] fix(search): fix email sent date filter format and selector type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change emailSentDate search field type from 'date' to 'date_interval' so the UI uses a proper from/to date picker and creates DateIntervalFilterParameter with Date objects rather than SimpleFilterParameter with Date.toString() (which produces 'Mon Mar 15 00:00:00 GMT+000 2021' — invalid for Solr) - Add explicit DateIntervalFilterParameter, DateRangeFilterParameter and LongRangeFilterParameter handlers in buildBlockJoinSubQueryClause to emit clean +field:[from TO to] range clauses with proper ISO8601 dates instead of going through the verbose appendRangeInterval fallback Co-Authored-By: Claude Sonnet 4.6 --- .../org/roda/core/index/utils/SolrUtils.java | 26 ++++++++++++++++--- .../main/resources/config/roda-wui.properties | 2 +- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/roda-core/roda-core/src/main/java/org/roda/core/index/utils/SolrUtils.java b/roda-core/roda-core/src/main/java/org/roda/core/index/utils/SolrUtils.java index 18a5edce61..c10609bfd2 100644 --- a/roda-core/roda-core/src/main/java/org/roda/core/index/utils/SolrUtils.java +++ b/roda-core/roda-core/src/main/java/org/roda/core/index/utils/SolrUtils.java @@ -1037,8 +1037,8 @@ private static void appendBlockJoinChildrenFilterParameter(StringBuilder ret, Ch String escapedMask = maskValue.replace("\"", "\\\""); if (parameter.getParentFilter() != null) { - String parentQuery = buildBlockJoinSubQuery(parameter.getParentFilter()); - ret.append("{!child of=\"").append(escapedMask).append("\"}").append(parentQuery); + String parentQuery = buildBlockJoinSubQuery(parameter.getParentFilter()).replace("\"", "\\\""); + ret.append("{!child of=\"").append(escapedMask).append("\" v=\"").append(parentQuery).append("\"}"); } else { ret.append("{!child of=\"").append(escapedMask).append("\"}"); } @@ -1051,8 +1051,8 @@ private static void appendBlockJoinFilterParameter(StringBuilder ret, ParentWhic String escapedMask = maskValue.replace("\"", "\\\""); if (parameter.getChildrenFilter() != null) { - String childQuery = buildBlockJoinSubQuery(parameter.getChildrenFilter()); - ret.append("{!parent which=\"").append(escapedMask).append("\"}").append(childQuery); + String childQuery = buildBlockJoinSubQuery(parameter.getChildrenFilter()).replace("\"", "\\\""); + ret.append("{!parent which=\"").append(escapedMask).append("\" v=\"").append(childQuery).append("\"}"); } else { ret.append("{!parent which=\"").append(escapedMask).append("\"}"); } @@ -1121,6 +1121,24 @@ private static void buildBlockJoinSubQueryClause(StringBuilder sb, FilterParamet buildBlockJoinSubQueryClause(sb, values.get(i)); } sb.append(")"); + } else if (parameter instanceof DateIntervalFilterParameter dateIntervalPar) { + if (dateIntervalPar.getFromValue() != null || dateIntervalPar.getToValue() != null) { + sb.append("+").append(dateIntervalPar.getFromName()).append(":[") + .append(processFromDate(dateIntervalPar.getFromValue())).append(" TO ") + .append(processToDate(dateIntervalPar.getToValue(), dateIntervalPar.getGranularity())).append("]"); + } + } else if (parameter instanceof DateRangeFilterParameter dateRangePar) { + if (dateRangePar.getFromValue() != null || dateRangePar.getToValue() != null) { + sb.append("+").append(dateRangePar.getName()).append(":[") + .append(processFromDate(dateRangePar.getFromValue())).append(" TO ") + .append(processToDate(dateRangePar.getToValue(), dateRangePar.getGranularity(), false)).append("]"); + } + } else if (parameter instanceof LongRangeFilterParameter longRangePar) { + if (longRangePar.getFromValue() != null || longRangePar.getToValue() != null) { + sb.append("+").append(longRangePar.getName()).append(":[") + .append(longRangePar.getFromValue() != null ? longRangePar.getFromValue() : "*").append(" TO ") + .append(longRangePar.getToValue() != null ? longRangePar.getToValue() : "*").append("]"); + } } else { parseFilterParameter(sb, parameter, !sb.isEmpty()); } diff --git a/roda-ui/roda-wui/src/main/resources/config/roda-wui.properties b/roda-ui/roda-wui/src/main/resources/config/roda-wui.properties index a62e7f561f..3499cc7df1 100644 --- a/roda-ui/roda-wui/src/main/resources/config/roda-wui.properties +++ b/roda-ui/roda-wui/src/main/resources/config/roda-wui.properties @@ -459,7 +459,7 @@ ui.search.fields.IndexedAIP.emailSender.nestedParentType = emailarchive ui.search.fields.IndexedAIP.emailSentDate.fields = sentDate_dt ui.search.fields.IndexedAIP.emailSentDate.i18n = ui.search.fields.IndexedAIP.emailSentDate -ui.search.fields.IndexedAIP.emailSentDate.type = date +ui.search.fields.IndexedAIP.emailSentDate.type = date_interval ui.search.fields.IndexedAIP.emailSentDate.nestedType = email ui.search.fields.IndexedAIP.emailSentDate.nestedParentType = emailarchive From 561ad400481a9bc6d1a3741d0ba291c7b95a8b28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Faria?= Date: Tue, 5 May 2026 10:27:18 +0100 Subject: [PATCH 33/37] Update Spanish translations --- .../config/i18n/ServerMessages_es.properties | 444 +++--- .../i18n/client/ClientMessages_es.properties | 1408 ++++++++--------- 2 files changed, 926 insertions(+), 926 deletions(-) diff --git a/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_es.properties b/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_es.properties index 8f99070f13..c4e1ab8bb1 100644 --- a/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_es.properties +++ b/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_es.properties @@ -10,14 +10,14 @@ ui.search.fields.IndexedAIP.scope=Descripción ui.search.fields.IndexedAIP.origination=Origen ui.search.fields.IndexedAIP.dates=Fecha ui.search.fields.IndexedAIP.level=Nivel -ui.search.fields.IndexedAIP.ingestSIPIds=Ingest SIP identifier +ui.search.fields.IndexedAIP.ingestSIPIds=Identificador del SIP de ingesta ui.search.fields.IndexedAIP.type=Tipo -ui.search.fields.IndexedAIP.hasRepresentations=With files -ui.search.fields.IndexedAIP.retentionPeriodStartDate=Retention start date -ui.search.fields.IndexedAIP.overdueDate=Overdue on -ui.search.fields.IndexedAIP.destroyedOn=Destroyed on -ui.search.fields.IndexedAIP.destroyedBy=Destroyed by -ui.search.fields.IndexedAIP.disposalScheduleName=Disposal schedule +ui.search.fields.IndexedAIP.hasRepresentations=Con archivos +ui.search.fields.IndexedAIP.retentionPeriodStartDate=Fecha de inicio de retención +ui.search.fields.IndexedAIP.overdueDate=Vence el +ui.search.fields.IndexedAIP.destroyedOn=Destruido el +ui.search.fields.IndexedAIP.destroyedBy=Destruido por +ui.search.fields.IndexedAIP.disposalScheduleName=Calendario de eliminación ui.search.fields.IndexedRepresentation.identifier=Identificador ui.search.fields.IndexedRepresentation.uuid=Identificador @@ -31,16 +31,16 @@ ui.search.fields.IndexedRepresentation.representationStates=Estado ui.search.fields.IndexedFile.uuid=Identificador ui.search.fields.IndexedFile.filename=Nombre del archivo -ui.search.fields.IndexedFile.width=Width -ui.search.fields.IndexedFile.height=Height -ui.search.fields.IndexedFile.compression=Lossless compression +ui.search.fields.IndexedFile.width=Ancho +ui.search.fields.IndexedFile.height=Alto +ui.search.fields.IndexedFile.compression=Compresión sin pérdidas ui.search.fields.IndexedFile.path=Ruta ui.search.fields.IndexedFile.format=Formato ui.search.fields.IndexedFile.formatVersion=Versión del formato ui.search.fields.IndexedFile.pronom=PRONOM ui.search.fields.IndexedFile.mimetype=Mimetype ui.search.fields.IndexedFile.extension=Extensión -ui.search.fields.IndexedFile.formatDesignation=Format designation +ui.search.fields.IndexedFile.formatDesignation=Designación del formato ui.search.fields.IndexedFile.filesize=Tamaño del archivo ui.search.fields.IndexedFile.fulltext=Texto completo @@ -48,10 +48,10 @@ ui.search.fields.Job.name=Nombre ui.search.fields.Job.dates=Fechas ui.search.fields.IndexedReport.dates=Fechas -ui.search.fields.IndexedReport.sourceObjectOriginalIds=Source original identifiers -ui.search.fields.IndexedReport.sourceObjectOriginalName=Source original name -ui.search.fields.IndexedReport.outcomeObjectId=Outcome object identifier -ui.search.fields.IndexedReport.outcomeObjectLabel=Outcome object label +ui.search.fields.IndexedReport.sourceObjectOriginalIds=Identificadores originales de origen +ui.search.fields.IndexedReport.sourceObjectOriginalName=Nombre original de origen +ui.search.fields.IndexedReport.outcomeObjectId=Identificador del objeto resultado +ui.search.fields.IndexedReport.outcomeObjectLabel=Etiqueta del objeto resultado ui.search.fields.IndexedPreservationEvent.dates=Fechas @@ -63,68 +63,68 @@ ui.search.fields.Notification.dates=Fechas ui.search.fields.TransferredResource.dates=Fechas ui.search.fields.TransferredResource.size=Tamaño -ui.search.fields.IndexedRisk.dates=Identification date +ui.search.fields.IndexedRisk.dates=Fecha de identificación ui.search.fields.IndexedRisk.name=Nombre -ui.search.fields.RiskIncidence.mitigatedOn=Mitigation date -ui.search.fields.RiskIncidence.detectedOn=Detection date +ui.search.fields.RiskIncidence.mitigatedOn=Fecha de mitigación +ui.search.fields.RiskIncidence.detectedOn=Fecha de detección ui.search.fields.RepresentationInformation.name=Nombre ui.search.fields.RepresentationInformation.description=Descripción -ui.search.fields.RepresentationInformation.tags=Tags +ui.search.fields.RepresentationInformation.tags=Etiquetas ########################################################################## # Representation information relation configurations and their inverses ########################################################################## -ui.search.fields.RepresentationInformation.relation.conforms_to=Conforms to -ui.search.fields.RepresentationInformation.relation.has_created=Has created -ui.search.fields.RepresentationInformation.relation.was_created_by=Was created by -ui.search.fields.RepresentationInformation.relation.has_format=Has format -ui.search.fields.RepresentationInformation.relation.is_format_of=Is format of -ui.search.fields.RepresentationInformation.relation.has_part=Has part -ui.search.fields.RepresentationInformation.relation.is_part_of=Is part of -ui.search.fields.RepresentationInformation.relation.has_version=Has version -ui.search.fields.RepresentationInformation.relation.is_version_of=Is version of -ui.search.fields.RepresentationInformation.relation.references=References -ui.search.fields.RepresentationInformation.relation.is_referenced_by=Is referenced by -ui.search.fields.RepresentationInformation.relation.replaces=Replaces -ui.search.fields.RepresentationInformation.relation.is_replaced_by=Is replaced by -ui.search.fields.RepresentationInformation.relation.requires=Requires -ui.search.fields.RepresentationInformation.relation.is_required_by=Is required by -ui.search.fields.RepresentationInformation.relation.has_source=Has source -ui.search.fields.RepresentationInformation.relation.is_source_of=Is source of -ui.search.fields.RepresentationInformation.relation.rendered_by=Rendered by -ui.search.fields.RepresentationInformation.relation.executed_by=Executed by -ui.search.fields.RepresentationInformation.relation.specified_by=Specified by -ui.search.fields.RepresentationInformation.relation.represents=Represents - -ui.search.fields.RepresentationInformation.relation.subtype_of=Subtype of -ui.search.fields.RepresentationInformation.relation.has_subtype=Has subtype -ui.search.fields.RepresentationInformation.relation.component_of=Component of -ui.search.fields.RepresentationInformation.relation.has_component=Has component -ui.search.fields.RepresentationInformation.relation.contains=Contains -ui.search.fields.RepresentationInformation.relation.is_contained=Is contained -ui.search.fields.RepresentationInformation.relation.extension_of=Extension of -ui.search.fields.RepresentationInformation.relation.has_extension=Has extension -ui.search.fields.RepresentationInformation.relation.modification_of=Modification of -ui.search.fields.RepresentationInformation.relation.has_modification=Has modification -ui.search.fields.RepresentationInformation.relation.used_by=Used by -ui.search.fields.RepresentationInformation.relation.uses=Uses -ui.search.fields.RepresentationInformation.relation.affinity_to=Affinity to -ui.search.fields.RepresentationInformation.relation.defined_via=Defined via -ui.search.fields.RepresentationInformation.relation.equivalent_to=Equivalent to -ui.search.fields.RepresentationInformation.relation.may_contain=May contain -ui.search.fields.RepresentationInformation.relation.may_have_component=May have component -ui.search.fields.RepresentationInformation.relation.must_have_component=Must have component -ui.search.fields.RepresentationInformation.relation.has_modified_version=Has modified version -ui.search.fields.RepresentationInformation.relation.has_later_version=Has later version -ui.search.fields.RepresentationInformation.relation.has_earlier_version=Has earlier version -ui.search.fields.RepresentationInformation.relation.see_also=See also +ui.search.fields.RepresentationInformation.relation.conforms_to=Conforme a +ui.search.fields.RepresentationInformation.relation.has_created=Ha creado +ui.search.fields.RepresentationInformation.relation.was_created_by=Fue creado por +ui.search.fields.RepresentationInformation.relation.has_format=Tiene formato +ui.search.fields.RepresentationInformation.relation.is_format_of=Es formato de +ui.search.fields.RepresentationInformation.relation.has_part=Tiene parte +ui.search.fields.RepresentationInformation.relation.is_part_of=Es parte de +ui.search.fields.RepresentationInformation.relation.has_version=Tiene versión +ui.search.fields.RepresentationInformation.relation.is_version_of=Es versión de +ui.search.fields.RepresentationInformation.relation.references=Referencias +ui.search.fields.RepresentationInformation.relation.is_referenced_by=Es referenciado por +ui.search.fields.RepresentationInformation.relation.replaces=Reemplaza +ui.search.fields.RepresentationInformation.relation.is_replaced_by=Es reemplazado por +ui.search.fields.RepresentationInformation.relation.requires=Requiere +ui.search.fields.RepresentationInformation.relation.is_required_by=Es requerido por +ui.search.fields.RepresentationInformation.relation.has_source=Tiene fuente +ui.search.fields.RepresentationInformation.relation.is_source_of=Es fuente de +ui.search.fields.RepresentationInformation.relation.rendered_by=Renderizado por +ui.search.fields.RepresentationInformation.relation.executed_by=Ejecutado por +ui.search.fields.RepresentationInformation.relation.specified_by=Especificado por +ui.search.fields.RepresentationInformation.relation.represents=Representa + +ui.search.fields.RepresentationInformation.relation.subtype_of=Subtipo de +ui.search.fields.RepresentationInformation.relation.has_subtype=Tiene subtipo +ui.search.fields.RepresentationInformation.relation.component_of=Componente de +ui.search.fields.RepresentationInformation.relation.has_component=Tiene componente +ui.search.fields.RepresentationInformation.relation.contains=Contiene +ui.search.fields.RepresentationInformation.relation.is_contained=Está contenido +ui.search.fields.RepresentationInformation.relation.extension_of=Extensión de +ui.search.fields.RepresentationInformation.relation.has_extension=Tiene extensión +ui.search.fields.RepresentationInformation.relation.modification_of=Modificación de +ui.search.fields.RepresentationInformation.relation.has_modification=Tiene modificación +ui.search.fields.RepresentationInformation.relation.used_by=Usado por +ui.search.fields.RepresentationInformation.relation.uses=Usa +ui.search.fields.RepresentationInformation.relation.affinity_to=Afinidad con +ui.search.fields.RepresentationInformation.relation.defined_via=Definido mediante +ui.search.fields.RepresentationInformation.relation.equivalent_to=Equivalente a +ui.search.fields.RepresentationInformation.relation.may_contain=Puede contener +ui.search.fields.RepresentationInformation.relation.may_have_component=Puede tener componente +ui.search.fields.RepresentationInformation.relation.must_have_component=Debe tener componente +ui.search.fields.RepresentationInformation.relation.has_modified_version=Tiene versión modificada +ui.search.fields.RepresentationInformation.relation.has_later_version=Tiene versión posterior +ui.search.fields.RepresentationInformation.relation.has_earlier_version=Tiene versión anterior +ui.search.fields.RepresentationInformation.relation.see_also=Véase también ui.search.fields.RepresentationInformation.relation.other=Otro ui.search.fields.DisposalConfirmation.createdOn=Fecha de creación -ui.search.fields.DisposalConfirmation.executedOn=Execution date -ui.search.fields.DisposalConfirmation.restoredOn=Restoration date +ui.search.fields.DisposalConfirmation.executedOn=Fecha de ejecución +ui.search.fields.DisposalConfirmation.restoredOn=Fecha de restauración ########################################## # facets @@ -138,16 +138,16 @@ ui.facets.IndexedAIP.level.fonds: Fonds ui.facets.IndexedAIP.level.item: Item ui.facets.IndexedAIP.level.other: Otro ui.facets.IndexedAIP.level.series: Série -ui.facets.IndexedAIP.disposalScheduleName: Disposal schedule -ui.facets.IndexedAIP.destroyedBy: Destroyed by -ui.facets.IndexedAIP.instanceName: Instance +ui.facets.IndexedAIP.disposalScheduleName: Calendario de eliminación +ui.facets.IndexedAIP.destroyedBy: Destruido por +ui.facets.IndexedAIP.instanceName: Instancia -ui.facets.IndexedFile.fileFormat: Formats -ui.facets.IndexedFile.formatPronom: PRONOM IDs -ui.facets.IndexedFile.formatMimetype: Mimetypes -ui.facets.IndexedFile.isDirectory: File type -ui.facets.IndexedFile.isDirectory.false: regular file -ui.facets.IndexedFile.isDirectory.true: directory +ui.facets.IndexedFile.fileFormat: Formatos +ui.facets.IndexedFile.formatPronom: IDs PRONOM +ui.facets.IndexedFile.formatMimetype: Tipos MIME +ui.facets.IndexedFile.isDirectory: Tipo de archivo +ui.facets.IndexedFile.isDirectory.false: archivo normal +ui.facets.IndexedFile.isDirectory.true: directorio ui.facets.IndexedPreservationAgent.type: Tipo @@ -157,33 +157,33 @@ ui.facets.IndexedPreservationEvent.eventOutcome.PARTIAL_SUCCESS: Éxito parcial ui.facets.IndexedPreservationEvent.eventOutcome.SUCCESS: Éxito ui.facets.IndexedPreservationEvent.eventOutcome.SKIPPED: Omitida ui.facets.IndexedPreservationEvent.eventType: Tipo -ui.facets.IndexedPreservationEvent.objectClass: Entity +ui.facets.IndexedPreservationEvent.objectClass: Entidad ui.facets.IndexedPreservationEvent.objectClass.AIP: Entidad intelectual ui.facets.IndexedPreservationEvent.objectClass.FILE: Archivo ui.facets.IndexedPreservationEvent.objectClass.REPOSITORY: Repositorio ui.facets.IndexedPreservationEvent.objectClass.REPRESENTATION: Representación -ui.facets.IndexedPreservationEvent.instanceName: Instance +ui.facets.IndexedPreservationEvent.instanceName: Instancia ui.facets.IndexedReport.pluginName: Ejecutar última tarea -ui.facets.IndexedReport.pluginState: Report status +ui.facets.IndexedReport.pluginState: Estado del informe ui.facets.IndexedReport.pluginState.FAILURE: Fracaso ui.facets.IndexedReport.pluginState.PARTIAL_SUCCESS: Éxito parcial ui.facets.IndexedReport.pluginState.RUNNING: Corriendo ui.facets.IndexedReport.pluginState.SUCCESS: Éxito ui.facets.IndexedReport.pluginState.SKIPPED: Omitida -ui.facets.IndexedReport.successfulPlugins: Successful inner processes -ui.facets.IndexedReport.unsuccessfulPlugins: Unsuccessful inner processes -ui.facets.IndexedReport.instanceName: Instance +ui.facets.IndexedReport.successfulPlugins: Procesos internos exitosos +ui.facets.IndexedReport.unsuccessfulPlugins: Procesos internos fallidos +ui.facets.IndexedReport.instanceName: Instancia ui.facets.IndexedRepresentation.original: Original ui.facets.IndexedRepresentation.original.false: no ui.facets.IndexedRepresentation.original.true: si -ui.facets.IndexedRepresentation.representationStates: States +ui.facets.IndexedRepresentation.representationStates: Estados ui.facets.IndexedRepresentation.representationStates.ACCESS: Accesos -ui.facets.IndexedRepresentation.representationStates.INGESTED: Ingested +ui.facets.IndexedRepresentation.representationStates.INGESTED: Ingestado ui.facets.IndexedRepresentation.representationStates.ORIGINAL: Original -ui.facets.IndexedRepresentation.representationStates.PRESERVATION: Preservation -ui.facets.IndexedRepresentation.instanceName: Instance +ui.facets.IndexedRepresentation.representationStates.PRESERVATION: Preservación +ui.facets.IndexedRepresentation.instanceName: Instancia ui.facets.IndexedRepresentation.type: Tipo ui.facets.IndexedRisk.categories: Categoría @@ -196,62 +196,62 @@ ui.facets.IndexedRisk.posMitigationSeverityLevel.HIGH: Alta ui.facets.IndexedRisk.posMitigationSeverityLevel.LOW: Baja ui.facets.IndexedRisk.posMitigationSeverityLevel.MODERATE: Moderado -ui.facets.IndexedJob.pluginType: Job types +ui.facets.IndexedJob.pluginType: Tipos de proceso ui.facets.IndexedJob.pluginType.AIP_TO_AIP: AIP a AIP ui.facets.IndexedJob.pluginType.AIP_TO_SIP: AIP a SIP ui.facets.IndexedJob.pluginType.INGEST: Ingesta -ui.facets.IndexedJob.pluginType.INTERNAL: Internal +ui.facets.IndexedJob.pluginType.INTERNAL: Interno ui.facets.IndexedJob.pluginType.MISC: Misceláneos ui.facets.IndexedJob.pluginType.SIP_TO_AIP: SIP a AIP -ui.facets.IndexedJob.pluginType.MULTI: Multiple +ui.facets.IndexedJob.pluginType.MULTI: Múltiple ui.facets.IndexedJob.state: Estado ui.facets.IndexedJob.state.COMPLETED: Hecho ui.facets.IndexedJob.state.CREATED: esperando para comenzar ui.facets.IndexedJob.state.FAILED_DURING_CREATION: Falló inicio -ui.facets.IndexedJob.state.PENDING_APPROVAL:pending -ui.facets.IndexedJob.state.SCHEDULED:scheduled +ui.facets.IndexedJob.state.PENDING_APPROVAL:pendiente de aprobación +ui.facets.IndexedJob.state.SCHEDULED:programado ui.facets.IndexedJob.state.REJECTED:rechazado ui.facets.IndexedJob.state.FAILED_TO_COMPLETE: Falló ui.facets.IndexedJob.state.STARTED: ejecutando ui.facets.IndexedJob.state.STOPPED: Detenido ui.facets.IndexedJob.state.STOPPING: Parada ui.facets.IndexedJob.state.TO_BE_CLEANED: en mantenimiento -ui.facets.IndexedJob.username: Creators -ui.facets.IndexedJob.hasFailures: Failures -ui.facets.IndexedJob.hasFailures.true: with failures -ui.facets.IndexedJob.hasFailures.false: without failures -ui.facets.IndexedJob.hasPartialSuccess: Partial Success -ui.facets.IndexedJob.hasPartialSuccess.true: with partial success -ui.facets.IndexedJob.hasPartialSuccess.false: without partial success +ui.facets.IndexedJob.username: Creadores +ui.facets.IndexedJob.hasFailures: Fallos +ui.facets.IndexedJob.hasFailures.true: con fallos +ui.facets.IndexedJob.hasFailures.false: sin fallos +ui.facets.IndexedJob.hasPartialSuccess: Éxito parcial +ui.facets.IndexedJob.hasPartialSuccess.true: con éxito parcial +ui.facets.IndexedJob.hasPartialSuccess.false: sin éxito parcial ui.facets.IndexedJob.hasSkipped: Omitida -ui.facets.IndexedJob.hasSkipped.true: with skipped -ui.facets.IndexedJob.hasSkipped.false: without skipped -ui.facets.IndexedJob.instanceName: Instance +ui.facets.IndexedJob.hasSkipped.true: con omitidos +ui.facets.IndexedJob.hasSkipped.false: sin omitidos +ui.facets.IndexedJob.instanceName: Instancia -ui.facets.LogEntry.actionComponent: Components +ui.facets.LogEntry.actionComponent: Componentes # V2 ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.DisposalHoldController = Disposal holds ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.DisposalRuleController = Disposal rules ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.DisposalScheduleController = Disposal schedules ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.DisposalConfirmationController = Disposal confirmations ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.RiskController = Riesgos -ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.RiskIncidenceController = Risk Incidences +ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.RiskIncidenceController = Incidencias de riesgo ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.TransferredResourceController = Recursos transferidos -ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.services.IndexService = Index +ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.services.IndexService = Índice ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.PreservationEventController = Eventos de preservación ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.PreservationAgentController = Eventos de preservación ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.AIPController = AIPs ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.FilesController = Archivos ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.RepresentationController = Representaciones ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.RepresentationInformationController = Información de representación -ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.MembersController = Users & Groups -ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.ConfigurationController = Configurations +ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.MembersController = Usuarios y grupos +ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.ConfigurationController = Configuraciones ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.JobsController = Procesos -ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.NotificationController = Notifications -ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.MetricsController = Metrics -ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.ClassificationPlanController = Classification plan +ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.NotificationController = Notificaciones +ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.MetricsController = Métricas +ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.ClassificationPlanController = Plan de clasificación ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.DIPPlanController = DIPs -ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.DistributedInstances = Distributed instances +ui.facets.LogEntry.actionComponent.org.roda.wui.api.v2.controller.DistributedInstances = Instancias distribuidas @@ -268,41 +268,41 @@ ui.facets.LogEntry.actionComponent.org.roda.wui.api.v1.ManagementTasksResource: ui.facets.LogEntry.actionComponent.org.roda.wui.filter.CasWebAuthFilter: CAS Login (Before RODA 6) ui.facets.LogEntry.actionComponent.org.roda.wui.security.SecurityObserverImpl: Seguridad -ui.facets.LogEntry.actionMethod: Methods +ui.facets.LogEntry.actionMethod: Métodos ui.facets.LogEntry.state: Resultado ui.facets.LogEntry.state.FAILURE: Fracaso ui.facets.LogEntry.state.SUCCESS: Éxito ui.facets.LogEntry.state.UNAUTHORIZED: No autorizado ui.facets.LogEntry.state.UNKNOWN: {0} ui.facets.LogEntry.username: Nombre de usuario -ui.facets.LogEntry.instanceId: Instance +ui.facets.LogEntry.instanceId: Instancia ui.facets.Notification.isAcknowledged: Reconocido ui.facets.Notification.isAcknowledged.false: No reconocido ui.facets.Notification.isAcknowledged.true: Reconocido -ui.facets.Notification.recipientUsers: Recipient Users +ui.facets.Notification.recipientUsers: Usuarios destinatarios ui.facets.Notification.state: Estado ui.facets.Notification.state.COMPLETED: Éxito ui.facets.Notification.state.CREATED: Creado ui.facets.Notification.state.FAILED: Fracaso -ui.facets.RepresentationInformation.support: Support level -ui.facets.RepresentationInformation.support.KNOWN: Known +ui.facets.RepresentationInformation.support: Nivel de soporte +ui.facets.RepresentationInformation.support.KNOWN: Conocido ui.facets.RepresentationInformation.support.SUPPORTED: Soportado -ui.facets.RepresentationInformation.support.UNSUPPORTED: Unsupported -ui.facets.RepresentationInformation.tags: Tags -ui.facets.RepresentationInformation.family: Family -ui.facets.RepresentationInformation.family.fileformat: File format +ui.facets.RepresentationInformation.support.UNSUPPORTED: No soportado +ui.facets.RepresentationInformation.tags: Etiquetas +ui.facets.RepresentationInformation.family: Familia +ui.facets.RepresentationInformation.family.fileformat: Formato de archivo ui.facets.RepresentationInformation.family.software: Software -ui.facets.RepresentationInformation.family.organisation: Organisation -ui.facets.RepresentationInformation.family.data: Data +ui.facets.RepresentationInformation.family.organisation: Organización +ui.facets.RepresentationInformation.family.data: Datos ui.facets.RiskIncidence.mitigatedBy: Mitigado por ui.facets.RiskIncidence.detectedBy: Detectado por ui.facets.RiskIncidence.severity.HIGH: Alta ui.facets.RiskIncidence.severity.LOW: Baja ui.facets.RiskIncidence.severity.MODERATE: Moderado -ui.facets.RiskIncidence.status: Incidence status +ui.facets.RiskIncidence.status: Estado de incidencia ui.facets.RiskIncidence.status.ACCEPT_RISK: Riesgo aceptado ui.facets.RiskIncidence.status.FALSE_POSITIVE: Falso positivo ui.facets.RiskIncidence.status.MITIGATED: mitigado @@ -317,11 +317,11 @@ ui.facets.RODAMember.isUser.true: usuario ui.facets.RODAMember.groups: Grupos ui.facets.DisposalConfirmation.state: Estado -ui.facets.DisposalConfirmation.state.APPROVED: Approved -ui.facets.DisposalConfirmation.state.PENDING: Pending -ui.facets.DisposalConfirmation.state.RESTORED: Restored +ui.facets.DisposalConfirmation.state.APPROVED: Aprobado +ui.facets.DisposalConfirmation.state.PENDING: Pendiente +ui.facets.DisposalConfirmation.state.RESTORED: Restaurado ui.facets.DisposalConfirmation.state.PERMANENTLY_DELETED: Eliminado -ui.facets.DisposalConfirmation.state.EXECUTION_FAILED: Execution failed +ui.facets.DisposalConfirmation.state.EXECUTION_FAILED: Ejecución fallida ui.facets.DisposalConfirmation.createdBy: Creador ########################################## @@ -342,36 +342,36 @@ ui.browse.metadata.descriptive.type.key-value=Valor clave # dissemination: html (xslt, etc) ########################################## -crosswalks.dissemination.html.ead.creator=Creators -crosswalks.dissemination.html.ead.producer=Producer -crosswalks.dissemination.html.ead.publicationdate=Publication date +crosswalks.dissemination.html.ead.creator=Creadores +crosswalks.dissemination.html.ead.producer=Productor +crosswalks.dissemination.html.ead.publicationdate=Fecha de publicación crosswalks.dissemination.html.ead.custodialhistory=historial de custodia crosswalks.dissemination.html.ead.acquisitioninformation=Información de adquisición crosswalks.dissemination.html.ead.acquisitionnumber=Número de adquisición crosswalks.dissemination.html.ead.acquistiondate=Fecha adquisición -crosswalks.dissemination.html.ead.contentarea=Content and structure +crosswalks.dissemination.html.ead.contentarea=Contenido y estructura crosswalks.dissemination.html.ead.description=Descripción -crosswalks.dissemination.html.ead.accessarea=Conditions of access and use +crosswalks.dissemination.html.ead.accessarea=Condiciones de acceso y uso crosswalks.dissemination.html.ead.accessrestrictions=Restricción de acceso crosswalks.dissemination.html.ead.languageScriptNotes=Lenguaje crosswalks.dissemination.html.ead.languages=Lenguaje (controlada) crosswalks.dissemination.html.ead.phystech=Características físicas y requisitos técnicos crosswalks.dissemination.html.ead.otherfindaids=Otras ayudas AIDS -crosswalks.dissemination.html.ead.alliedarea=Allied materials +crosswalks.dissemination.html.ead.alliedarea=Materiales relacionados crosswalks.dissemination.html.ead.relatedmaterials=Materiales relacionados -crosswalks.dissemination.html.ead.oddarea=Other description metadata -crosswalks.dissemination.html.ead.accesspointarea=Access points -crosswalks.dissemination.html.ead.nameaccesspoint=Name access point -crosswalks.dissemination.html.ead.subjectaccesspoint=Subject access point -crosswalks.dissemination.html.ead.placeaccesspoint=Place access point -crosswalks.dissemination.html.ead.descriptionidentifier=Description identifier -crosswalks.dissemination.html.ead.institutionidentifier=Institution identifier -crosswalks.dissemination.html.ead.rules=Rules -crosswalks.dissemination.html.ead.statusdescription=Status of description -crosswalks.dissemination.html.ead.levelofdetail=Level of detail -crosswalks.dissemination.html.ead.processdates=Date of creation or revision -crosswalks.dissemination.html.ead.sources=Sources -crosswalks.dissemination.html.ead.archivistNotes=Archivist notes +crosswalks.dissemination.html.ead.oddarea=Otros metadatos descriptivos +crosswalks.dissemination.html.ead.accesspointarea=Puntos de acceso +crosswalks.dissemination.html.ead.nameaccesspoint=Punto de acceso por nombre +crosswalks.dissemination.html.ead.subjectaccesspoint=Punto de acceso por materia +crosswalks.dissemination.html.ead.placeaccesspoint=Punto de acceso por lugar +crosswalks.dissemination.html.ead.descriptionidentifier=Identificador de descripción +crosswalks.dissemination.html.ead.institutionidentifier=Identificador de institución +crosswalks.dissemination.html.ead.rules=Reglas +crosswalks.dissemination.html.ead.statusdescription=Estado de descripción +crosswalks.dissemination.html.ead.levelofdetail=Nivel de detalle +crosswalks.dissemination.html.ead.processdates=Fecha de creación o revisión +crosswalks.dissemination.html.ead.sources=Fuentes +crosswalks.dissemination.html.ead.archivistNotes=Notas del archivero crosswalks.dissemination.html.ead.materialspecification=Especificación de material crosswalks.dissemination.html.ead.physicaldescription=Descripción física crosswalks.dissemination.html.ead.administrativeandbiographicalhistory=Historia administrativa y biográfica @@ -391,19 +391,19 @@ crosswalks.dissemination.html.ead.origination=Origen crosswalks.dissemination.html.ead.dateofinitialphysicaldescription=Fecha de inicio de descripción física crosswalks.dissemination.html.ead.dateoffinalphysicaldescription=Fecha de final de descripción física crosswalks.dissemination.html.ead.materialspec=Especificación de material -crosswalks.dissemination.html.ead.physfacet=Physical facet -crosswalks.dissemination.html.ead.levelofdetail.full=Full -crosswalks.dissemination.html.ead.levelofdetail.partial=Partial -crosswalks.dissemination.html.ead.levelofdetail.minimal=Minimal +crosswalks.dissemination.html.ead.physfacet=Faceta física +crosswalks.dissemination.html.ead.levelofdetail.full=Completo +crosswalks.dissemination.html.ead.levelofdetail.partial=Parcial +crosswalks.dissemination.html.ead.levelofdetail.minimal=Mínimo crosswalks.dissemination.html.ead.statusdescription.final=Final -crosswalks.dissemination.html.ead.statusdescription.revised=Revised -crosswalks.dissemination.html.ead.statusdescription.draft=Draft -crosswalks.dissemination.html.ead.identityarea=Identity +crosswalks.dissemination.html.ead.statusdescription.revised=Revisado +crosswalks.dissemination.html.ead.statusdescription.draft=Borrador +crosswalks.dissemination.html.ead.identityarea=Identidad crosswalks.dissemination.html.ead.level=Nivel -crosswalks.dissemination.html.ead.otherlevel=Other level +crosswalks.dissemination.html.ead.otherlevel=Otro nivel crosswalks.dissemination.html.ead.reference=Referencia crosswalks.dissemination.html.ead.repositorycode=Código del repositorio -crosswalks.dissemination.html.ead.countrycode=Country code +crosswalks.dissemination.html.ead.countrycode=Código de país crosswalks.dissemination.html.ead.title=Titulo crosswalks.dissemination.html.ead.titletype=Tipo del titulo crosswalks.dissemination.html.ead.repository=Repositorio @@ -412,51 +412,51 @@ crosswalks.dissemination.html.ead.finaldate=Fecha final crosswalks.dissemination.html.ead.unitdate=Fecha crosswalks.dissemination.html.ead.dimensions=Dimensiones crosswalks.dissemination.html.ead.extent=Extensión -crosswalks.dissemination.html.ead.contextarea=Context +crosswalks.dissemination.html.ead.contextarea=Contexto #crosswalks.dissemination.html.ead.author=Author -crosswalks.dissemination.html.ead.bioghist=Biography or history -crosswalks.dissemination.html.ead.geogname=Geographic name -crosswalks.dissemination.html.ead.legalstatus=Legal status +crosswalks.dissemination.html.ead.bioghist=Biografía o historia +crosswalks.dissemination.html.ead.geogname=Nombre geográfico +crosswalks.dissemination.html.ead.legalstatus=Estatus legal crosswalks.dissemination.html.ead.custodhist=historial de custodia crosswalks.dissemination.html.ead.acqinfo=Información de adquisición -crosswalks.dissemination.html.ead.contentstructurearea=Content and structure +crosswalks.dissemination.html.ead.contentstructurearea=Contenido y estructura crosswalks.dissemination.html.ead.scopecontent=Descripción crosswalks.dissemination.html.ead.appraisal=Evaluación crosswalks.dissemination.html.ead.accruals=Acumulativos -crosswalks.dissemination.html.ead.arrangement=System of arrangement -crosswalks.dissemination.html.ead.useaccessarea=Access and use +crosswalks.dissemination.html.ead.arrangement=Sistema de ordenación +crosswalks.dissemination.html.ead.useaccessarea=Acceso y uso crosswalks.dissemination.html.ead.accessrestrict=Restricción de acceso crosswalks.dissemination.html.ead.userestrict=Uso restrito -crosswalks.dissemination.html.ead.physloc=Physical location -crosswalks.dissemination.html.ead.langmaterial=Language of the material -crosswalks.dissemination.html.ead.alliedmaterialarea=Associated documentation +crosswalks.dissemination.html.ead.physloc=Ubicación física +crosswalks.dissemination.html.ead.langmaterial=Idioma del material +crosswalks.dissemination.html.ead.alliedmaterialarea=Documentación asociada crosswalks.dissemination.html.ead.originalsloc=Localizaciones originales crosswalks.dissemination.html.ead.altformavail=Formas alternativas disponíbles crosswalks.dissemination.html.ead.bibliography=Bibliografía crosswalks.dissemination.html.ead.notesarea=Notas crosswalks.dissemination.html.ead.notes=Notas -crosswalks.dissemination.html.ead.descriptioncontrolarea=Description control -crosswalks.dissemination.html.ead.processinfo=Arquivist notes -crosswalks.dissemination.html.ead.level.AG=Aggregation +crosswalks.dissemination.html.ead.descriptioncontrolarea=Control de descripción +crosswalks.dissemination.html.ead.processinfo=Notas del archivero +crosswalks.dissemination.html.ead.level.AG=Agregación crosswalks.dissemination.html.ead.level.C=Coleção crosswalks.dissemination.html.ead.level.CL=Coleção crosswalks.dissemination.html.ead.level.D=Item crosswalks.dissemination.html.ead.level.DC=Archivo crosswalks.dissemination.html.ead.level.F=Fonds -crosswalks.dissemination.html.ead.level.P=Producer -crosswalks.dissemination.html.ead.level.SC=Section -crosswalks.dissemination.html.ead.level.SCL=Subcollection +crosswalks.dissemination.html.ead.level.P=Productor +crosswalks.dissemination.html.ead.level.SC=Sección +crosswalks.dissemination.html.ead.level.SCL=Subcolección crosswalks.dissemination.html.ead.level.SF=Subfonds crosswalks.dissemination.html.ead.level.SR=Série -crosswalks.dissemination.html.ead.level.SSC=Subsection -crosswalks.dissemination.html.ead.level.SSCL=Subsubcollection -crosswalks.dissemination.html.ead.level.SSF=Subsubfonds +crosswalks.dissemination.html.ead.level.SSC=Subsección +crosswalks.dissemination.html.ead.level.SSCL=Sub-subcolección +crosswalks.dissemination.html.ead.level.SSF=Sub-subfonds crosswalks.dissemination.html.ead.level.SSR=Subsérie -crosswalks.dissemination.html.ead.level.SSSC=Subsubsection -crosswalks.dissemination.html.ead.level.SSSR=Subsubseries -crosswalks.dissemination.html.ead.level.SSUI=Subsubcontainers -crosswalks.dissemination.html.ead.level.SUI=Subcontainer -crosswalks.dissemination.html.ead.level.UI=Installation unit +crosswalks.dissemination.html.ead.level.SSSC=Sub-subsección +crosswalks.dissemination.html.ead.level.SSSR=Sub-subsérie +crosswalks.dissemination.html.ead.level.SSUI=Sub-subcontenedores +crosswalks.dissemination.html.ead.level.SUI=Subcontenedor +crosswalks.dissemination.html.ead.level.UI=Unidad de instalación crosswalks.dissemination.html.ead.level.DS=Item crosswalks.dissemination.html.ead_3.title=Titulo @@ -552,8 +552,8 @@ crosswalks.dissemination.html.dc_simpledc20021212.source=Fuente crosswalks.dissemination.html.dc_simpledc20021212.subject=Asunto crosswalks.dissemination.html.dc_simpledc20021212.type=Tipo -crosswalks.dissemination.html.event.identifierType: Identifier type -crosswalks.dissemination.html.event.identifierValue: Identifier value +crosswalks.dissemination.html.event.identifierType: Tipo de identificador +crosswalks.dissemination.html.event.identifierValue: Valor del identificador crosswalks.dissemination.html.event.dateTime: Fecha crosswalks.dissemination.html.event.outcome: Resultado crosswalks.dissemination.html.event.type: Tipo @@ -565,9 +565,9 @@ crosswalks.dissemination.html.event.outcomeDetailExtension: Extensión # Representation information families ########################################## ri.family.software: Software -ri.family.fileformat: File format -ri.family.organisation: Organisation -ri.family.data: Data +ri.family.fileformat: Formato de archivo +ri.family.organisation: Organización +ri.family.data: Datos ########################################## # Representation information extra fields @@ -576,56 +576,56 @@ ri.data.notes: Notas ri.data.general: General ri.data.history: Historia -ri.data.dataDescription: Data description -ri.data.strutureInformation: Structure information -ri.data.semanticInformation: Semantic information +ri.data.dataDescription: Descripción de datos +ri.data.strutureInformation: Información de estructura +ri.data.semanticInformation: Información semántica -ri.data.organisationCategory: Organisation category -ri.data.organisationDesignation: Organisation designation +ri.data.organisationCategory: Categoría de organización +ri.data.organisationDesignation: Designación de organización ri.data.country: País -ri.data.headquarters: Headquarters +ri.data.headquarters: Sede central ri.data.website: Sitio web -ri.data.servedArea: Served area +ri.data.servedArea: Área de servicio ri.data.services: Servicios -ri.data.sustainabilityFactors: Sustainability factors -ri.data.dateOfEstablishment: Date of establishment -ri.data.numberOfEmployees: Number of employees -ri.data.revenue: Anual revenue -ri.data.operatingCosts: Anual operational costs -ri.data.netWorth: Net worth - -ri.data.formatDescription: Format description -ri.data.formatCategory: Format category -ri.data.formatDesignation: Format designation +ri.data.sustainabilityFactors: Factores de sostenibilidad +ri.data.dateOfEstablishment: Fecha de fundación +ri.data.numberOfEmployees: Número de empleados +ri.data.revenue: Ingresos anuales +ri.data.operatingCosts: Costes operativos anuales +ri.data.netWorth: Patrimonio neto + +ri.data.formatDescription: Descripción del formato +ri.data.formatCategory: Categoría del formato +ri.data.formatDesignation: Designación del formato ri.data.version: Versión -ri.data.disclosure: Disclosure -ri.data.documentation: Documentation -ri.data.adoption: Adoption -ri.data.licensingAndPatents: Licensing and patents -ri.data.transparency: Transparency -ri.data.selfdocumentation: Self-documentation -ri.data.externalDependencies: External dependencies -ri.data.technicalProtectionConsiderations: Content technical protection considerations (i.e DRM) -ri.data.formatCharacteristics: Format characteristics -ri.data.relevantCharacteristics: Other relevant characteristics -ri.data.functionalityBeyondNormalRendering: Functionality beyond normal rendering -ri.data.formatIdentifiers: Format identifiers +ri.data.disclosure: Divulgación +ri.data.documentation: Documentación +ri.data.adoption: Adopción +ri.data.licensingAndPatents: Licencias y patentes +ri.data.transparency: Transparencia +ri.data.selfdocumentation: Autodocumentación +ri.data.externalDependencies: Dependencias externas +ri.data.technicalProtectionConsiderations: Consideraciones de protección técnica del contenido (p. ej. DRM) +ri.data.formatCharacteristics: Características del formato +ri.data.relevantCharacteristics: Otras características relevantes +ri.data.functionalityBeyondNormalRendering: Funcionalidad más allá del renderizado normal +ri.data.formatIdentifiers: Identificadores del formato ri.data.filenameExtension: Extensión ri.data.internetMediaType: Mimetype ri.data.pronomPUID: Pronom PUID -ri.data.softwareDescription: Software description -ri.data.softwareCategory: Software category -ri.data.softwareDesignation: Software designation -ri.data.softwareHistory: Software history -ri.data.platforms: Platforms/Operative system -ri.data.softwareCharacteristics: Software characteristics +ri.data.softwareDescription: Descripción del software +ri.data.softwareCategory: Categoría del software +ri.data.softwareDesignation: Designación del software +ri.data.softwareHistory: Historia del software +ri.data.platforms: Plataformas/Sistema operativo +ri.data.softwareCharacteristics: Características del software ########################################## # Upload errors ########################################## -ui.upload.error.alreadyexists=The file already exists! +ui.upload.error.alreadyexists=¡El archivo ya existe! ########################################## # Appraisal @@ -657,13 +657,13 @@ level.noneselected = Ninguna seleccionada email.verification.subject = Registro en RODA email.verification.from = Administrador do RODA email.recoverlogin.subject = Recuperar entrada en RODA -email.setpassword.subject = Set RODA password +email.setpassword.subject = Establecer contraseña de RODA email.recoverlogin.from = Administrador do RODA ########################################## # Objects ########################################## -lists.label.BrowseAIP_aipChildren.single: sublevel -lists.label.BrowseAIP_aipChildren.multiple: sublevels -lists.label.BrowseAIPPortal_aipChildren.single: sublevel -lists.label.BrowseAIPPortal_aipChildren.multiple: sublevels +lists.label.BrowseAIP_aipChildren.single: subnivel +lists.label.BrowseAIP_aipChildren.multiple: subniveles +lists.label.BrowseAIPPortal_aipChildren.single: subnivel +lists.label.BrowseAIPPortal_aipChildren.multiple: subniveles diff --git a/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_es.properties b/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_es.properties index fd88b7d393..b6150334df 100644 --- a/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_es.properties +++ b/roda-ui/roda-wui/src/main/resources/config/i18n/client/ClientMessages_es.properties @@ -1,16 +1,16 @@ moveNoSuchObject:No se puede mover el elemento porque el destino no se encuentra en el repositorio. Detalles\: {0} of:de -ofOver:of over -and:and +ofOver:de más de +and:y # Titles genericTitle:Titulo processTitle:Procesos reportTitle:Informe de procesamiento de paquete de información ingestAppraisalTitle:Evaluación de la Ingesta riskHistoryTitle:Historial de Riesgos -editIncidenceTitle:Edit risk incidence -editIncidencesTitle:Edit risk incidences -showRiskIncidenceTitle:Risk incidence +editIncidenceTitle:Editar incidencia de riesgo +editIncidencesTitle:Editar incidencias de riesgo +showRiskIncidenceTitle:Incidencia de riesgo createRepresentationInformationTitle:Crear información de representación editRepresentationInformationTitle:Editar información de representación showRepresentationInformationTitle:Información de representación @@ -26,123 +26,123 @@ catalogueItemTitle:Entidades intelectuales catalogueRepresentationTitle:Representación catalogueFileTitle:Archivo catalogueDIPTitle:Diseminación -preservationActionsTitle:Preservation actions -ingestProcessTitle:Ingest process -internalProcessTitle:Internal actions +preservationActionsTitle:Acciones de preservación +ingestProcessTitle:Proceso de ingesta +internalProcessTitle:Acciones internas loginTitle:Usuario searchTitle:Buscar -activityLogTitle:Audit logs -showLogEntryTitle:Audit log -notificationsTitle:Notification logs -notificationTitle:Notification log -usersAndGroupsTitle:Users and groups +activityLogTitle:Registros de auditoría +showLogEntryTitle:Registro de auditoría +notificationsTitle:Registros de notificaciones +notificationTitle:Registro de notificaciones +usersAndGroupsTitle:Usuarios y grupos usersAndGroupsSearchPlaceHolder:Buscar usuarios y grupos... logsTitle:Logs changeTypeTitle:Cambiar tipo changeStatusTitle:Cambiar Estado -changeStatusSuccessful:Status changed successfully +changeStatusSuccessful:Estado cambiado correctamente statusLabel:{0} statusLabel[ORIGINAL]:Original -statusLabel[INGESTED]:Ingested +statusLabel[INGESTED]:Ingestado statusLabel[ACCESS]:Accesos -statusLabel[PRESERVATION]:Preservation +statusLabel[PRESERVATION]:Preservación tikaTitles:{0} -tikaTitles[tika_pdf_num3DAnnotations_txt]:Number of 3d Annotations -tikaTitles[tika_pdf_PDFVersion_txt]:Pdf version -tikaTitles[tika_access_permission_can_print_degraded_txt]:Permission to print degraded text -tikaTitles[tika_access_permission_extract_for_accessibility_txt]:Permission to extract for accessibility text -tikaTitles[tika_xmpTPg_NPages_txt]:Number of pages -tikaTitles[tika_pdf_totalUnmappedUnicodeChars_txt]:Number of unmapped unicode characters -tikaTitles[tika_pdf_hasXFA_txt]:Has XFA -tikaTitles[tika_pdf_hasXMP_txt]:Has XMP -tikaTitles[tika_access_permission_extract_content_txt]:Permission to extract content -tikaTitles[tika_access_permission_can_print_txt]:Permission to print -tikaTitles[tika_pdf_overallPercentageUnmappedUnicodeChars_txt]:Overall percentage of unmapped unicode characters -tikaTitles[tika_X-TIKA_Parsed-By_txt]:Tika parsed by -tikaTitles[tika_pdf_docinfo_created_txt]:Document info creation date -tikaTitles[tika_access_permission_modify_annotations_txt]:Permission to modify annotations -tikaTitles[tika_pdf_hasMarkedContent_txt]:Has marked content -tikaTitles[tika_xmp_CreatorTool_txt]:Creator tool -tikaTitles[tika_Content-Type_txt]:Content type -tikaTitles[tika_X-TIKA_Parsed-By-Full-Set_txt]:Parsed by -tikaTitles[tika_pdf_encrypted_txt]:Encrypted -tikaTitles[tika_access_permission_can_modify_txt]: Permission to modify -tikaTitles[tika_pdf_docinfo_creator_tool_txt]:Document info creator tool -tikaTitles[tika_pdf_containsNonEmbeddedFont_txt]:Contains non embedded font -tikaTitles[tika_access_permission_fill_in_form_txt]:Permission to fill in form -tikaTitles[tika_pdf_containsDamagedFont_txt]:Contains damaged font -tikaTitles[tika_dc_format_txt]:DC format -tikaTitles[tika_pdf_charsPerPage_txt]:Characters per page -tikaTitles[tika_pdf_docinfo_producer_txt]:Document info producer -tikaTitles[tika_pdf_hasCollection_txt]:Has collection -tikaTitles[tika_access_permission_assemble_document_txt]:Permission to assemble document -tikaTitles[tika_pdf_producer_txt]:Producer -tikaTitles[tika_pdf_unmappedUnicodeCharsPerPage_txt]:Unmapped unicode characters per page -tikaTitles[tika_dcterms_created_txt]:Terms creation date -tikaTitles[tika_tEXt_tEXtEntry_txt]:Text Entry -tikaTitles[tika_Dimension_PixelAspectRatio_txt]:Pixel aspect ratio dimensions -tikaTitles[tika_Data_PlanarConfiguration_txt]:Data planar configuration -tikaTitles[tika_Transparency_Alpha_txt]:Transpericy alpha +tikaTitles[tika_pdf_num3DAnnotations_txt]:Número de anotaciones 3D +tikaTitles[tika_pdf_PDFVersion_txt]:Versión PDF +tikaTitles[tika_access_permission_can_print_degraded_txt]:Permiso para imprimir con calidad reducida +tikaTitles[tika_access_permission_extract_for_accessibility_txt]:Permiso para extraer contenido con fines de accesibilidad +tikaTitles[tika_xmpTPg_NPages_txt]:Número de páginas +tikaTitles[tika_pdf_totalUnmappedUnicodeChars_txt]:Número total de caracteres Unicode sin asignar +tikaTitles[tika_pdf_hasXFA_txt]:Tiene XFA +tikaTitles[tika_pdf_hasXMP_txt]:Tiene XMP +tikaTitles[tika_access_permission_extract_content_txt]:Permiso para extraer contenido +tikaTitles[tika_access_permission_can_print_txt]:Permiso para imprimir +tikaTitles[tika_pdf_overallPercentageUnmappedUnicodeChars_txt]:Porcentaje global de caracteres Unicode sin asignar +tikaTitles[tika_X-TIKA_Parsed-By_txt]:Analizado por Tika +tikaTitles[tika_pdf_docinfo_created_txt]:Fecha de creación de información del documento +tikaTitles[tika_access_permission_modify_annotations_txt]:Permiso para modificar anotaciones +tikaTitles[tika_pdf_hasMarkedContent_txt]:Tiene contenido marcado +tikaTitles[tika_xmp_CreatorTool_txt]:Herramienta de creación +tikaTitles[tika_Content-Type_txt]:Tipo de contenido +tikaTitles[tika_X-TIKA_Parsed-By-Full-Set_txt]:Analizado por +tikaTitles[tika_pdf_encrypted_txt]:Cifrado +tikaTitles[tika_access_permission_can_modify_txt]: Permiso para modificar +tikaTitles[tika_pdf_docinfo_creator_tool_txt]:Herramienta de creación de información del documento +tikaTitles[tika_pdf_containsNonEmbeddedFont_txt]:Contiene fuente no incrustada +tikaTitles[tika_access_permission_fill_in_form_txt]:Permiso para rellenar formularios +tikaTitles[tika_pdf_containsDamagedFont_txt]:Contiene fuente dañada +tikaTitles[tika_dc_format_txt]:Formato DC +tikaTitles[tika_pdf_charsPerPage_txt]:Caracteres por página +tikaTitles[tika_pdf_docinfo_producer_txt]:Productor de información del documento +tikaTitles[tika_pdf_hasCollection_txt]:Tiene colección +tikaTitles[tika_access_permission_assemble_document_txt]:Permiso para ensamblar documento +tikaTitles[tika_pdf_producer_txt]:Productor +tikaTitles[tika_pdf_unmappedUnicodeCharsPerPage_txt]:Caracteres Unicode sin asignar por página +tikaTitles[tika_dcterms_created_txt]:Fecha de creación de términos DC +tikaTitles[tika_tEXt_tEXtEntry_txt]:Entrada de texto +tikaTitles[tika_Dimension_PixelAspectRatio_txt]:Relación de aspecto de píxel +tikaTitles[tika_Data_PlanarConfiguration_txt]:Configuración planar de datos +tikaTitles[tika_Transparency_Alpha_txt]:Canal alfa de transparencia tikaTitles[tika_sBIT_sBIT_RGBAlpha_txt]:RGB -tikaTitles[tika_Compression_Lossless_b]:Compression lossless -tikaTitles[tika_Compression_CompressionTypeName_txt]:Compression type name -tikaTitles[tika_Chroma_NumChannels_txt]:Chroma number of channels -tikaTitles[tika_Compression_NumProgressiveScans_txt]:Number of progressive scans -tikaTitles[tika_imagereader_NumImages_txt]:Number of images -tikaTitles[tika_Data_SampleFormat_txt]:Sample format -tikaTitles[tika_tiff_ImageWidth_d]:Image width -tikaTitles[tika_tiff_ImageLength_d]:Image length -tikaTitles[tika_Text_TextEntry_txt]:Text entry -tikaTitles[tika_Data_BitsPerSample_txt]:Bits per sample (Data) -tikaTitles[tika_Data_SignificantBitsPerSample_txt]:Significant bits per sample -tikaTitles[tika_tiff_BitsPerSample_txt]:Bits per sample (Tiff) +tikaTitles[tika_Compression_Lossless_b]:Compresión sin pérdida +tikaTitles[tika_Compression_CompressionTypeName_txt]:Nombre del tipo de compresión +tikaTitles[tika_Chroma_NumChannels_txt]:Número de canales de croma +tikaTitles[tika_Compression_NumProgressiveScans_txt]:Número de exploraciones progresivas +tikaTitles[tika_imagereader_NumImages_txt]:Número de imágenes +tikaTitles[tika_Data_SampleFormat_txt]:Formato de muestra +tikaTitles[tika_tiff_ImageWidth_d]:Anchura de imagen +tikaTitles[tika_tiff_ImageLength_d]:Longitud de imagen +tikaTitles[tika_Text_TextEntry_txt]:Entrada de texto +tikaTitles[tika_Data_BitsPerSample_txt]:Bits por muestra (datos) +tikaTitles[tika_Data_SignificantBitsPerSample_txt]:Bits significativos por muestra +tikaTitles[tika_tiff_BitsPerSample_txt]:Bits por muestra (Tiff) tikaTitles[tika_IHDR_txt]:IHDR -tikaTitles[tika_Dimension_ImageOrientation_txt]:Image orientation -tikaTitles[tika_Chroma_ColorSpaceType_txt]:Color space type -tikaTitles[tika_Chroma_BlackIsZero_txt]:Black is zero -otherStatusLabel:Other status: +tikaTitles[tika_Dimension_ImageOrientation_txt]:Orientación de imagen +tikaTitles[tika_Chroma_ColorSpaceType_txt]:Tipo de espacio de color +tikaTitles[tika_Chroma_BlackIsZero_txt]:Negro es cero +otherStatusLabel:Otro estado: otherStatusPlaceholder:Agregar nueva representación de estado -createFolderTitle:Create folder -createFolderPlaceholder:Write folder name -createFolderAlreadyExistsTitle:Cannot create folder -createFolderAlreadyExistsMessage:Folder name already exists -renameFolderAlreadyExistsTitle: Cannot rename folder -renameFolderAlreadyExistsMessage: Folder name already exists -outcomeDetailTitle:Action detail -outcomeDetailPlaceholder:Detail the reason to do it -chooseEntityTitle:Choose entity to relate +createFolderTitle:Crear carpeta +createFolderPlaceholder:Escriba el nombre de la carpeta +createFolderAlreadyExistsTitle:No se puede crear la carpeta +createFolderAlreadyExistsMessage:Ya existe una carpeta con ese nombre +renameFolderAlreadyExistsTitle: No se puede renombrar la carpeta +renameFolderAlreadyExistsMessage: Ya existe una carpeta con ese nombre +outcomeDetailTitle:Detalle de la acción +outcomeDetailPlaceholder:Detalle el motivo +chooseEntityTitle:Elegir entidad a relacionar # Sidebars sidebarFilterDates:Fechas sidebarFoldersFilesTitle:Carpetas y archivos sidebarProcessTitle:Procesos sidebarIngestTitle:Ingesta sidebarActionsTitle:Acciones -sidebarDisposalBinTitle:Disposal bin -sidebarDisposalScheduleTitle:Disposal Schedule -sidebarDisposalConfirmationTitle:Disposal Confirmation -sidebarAppraisalTitle:Assessment +sidebarDisposalBinTitle:Papelera de eliminación +sidebarDisposalScheduleTitle:Calendario de eliminación +sidebarDisposalConfirmationTitle:Confirmación de eliminación +sidebarAppraisalTitle:Evaluación metadataParseError:Error en linea {0}, columna {1}\: {2} notFoundError:No encontrado descriptiveMetadataTransformToHTMLError:Ha ocurrido un error mientras está transformando metadato descriptivo en HTML -preservationEventDetailsTransformToHTMLError:Error transforming preservation event details into HTML +preservationEventDetailsTransformToHTMLError:Error al transformar los detalles del evento de preservación en HTML allCollectionsTitle:Catalogo errorLoadingDescriptiveMetadata:Ha ocurrido un error mientras está cargando metadato descriptivo\: {0} -errorLoadingPreservationEventDetails:Error loading preservation event details: {0} -errorLoadingJobReport:Error loading job report: {0} +errorLoadingPreservationEventDetails:Error al cargar los detalles del evento de preservación: {0} +errorLoadingJobReport:Error al cargar el informe del proceso: {0} titleDatesEmpty: titleDatesNoFinal:Desde {0} titleDatesNoInitial:Hasta {0} -titleDates:Desde {0} to {1} +titleDates:Desde {0} hasta {1} simpleDatesEmpty: simpleDatesNoFinal:{0} simpleDatesNoInitial:a {0} -simpleDates:{0} to {1} +simpleDates:{0} hasta {1} rejectMessage:Mensaje de rechazo rejectQuestion:¿Cuál es la razón de rechazar este item? -renameSIPFailed:Rename of a SIP failed -renameSIPSuccessful:Rename of a SIP was successful -moveSIPFailed:SIP move failed -movingAIP:Moving AIP(s) in background +renameSIPFailed:Error al renombrar el SIP +renameSIPSuccessful:El SIP se renombró correctamente +moveSIPFailed:Error al mover el SIP +movingAIP:Moviendo AIP(s) en segundo plano jobNotFound:El trabajo que ha solicitado no fue encontrado updateIsCurrentlyRunning:Se está ejecutando otra actualización actualmente. Favor intentar luego nuevamente. updatedFilesUnderFolder:Archivos contenidos en carpeta actualizados @@ -152,15 +152,15 @@ otherItem:Otro metadataFileCreated:Archivo metadata descriptiva creado metadataFileSaved:Archivo metadata descriptiva guardado metadataFileRemoved:Archivo metadata descriptiva eliminado -removeMetadataFileTitle:Confirm descriptive metadata removal -removeMetadataFileLabel:Are you sure you want to remove the descriptive metadata of this entity? -updateMetadataFileTitle:Confirm descriptive metadata update -updateMetadataFileLabel:Are you sure you want to update the descriptive metadata of this entity? +removeMetadataFileTitle:Confirmar eliminación de metadatos descriptivos +removeMetadataFileLabel:¿Está seguro de que desea eliminar los metadatos descriptivos de esta entidad? +updateMetadataFileTitle:Confirmar actualización de metadatos descriptivos +updateMetadataFileLabel:¿Está seguro de que desea actualizar los metadatos descriptivos de esta entidad? versionReverted:Versión revertida versionDeleted:Versión eliminada selectUserOrGroupToAdd:Seleccione usuarios y grupos para agregar couldNotFindPreservationEvent:No pudo ser encontrado el evento de preservación -couldNotFindPreservationAgent:Could not find preservation agent +couldNotFindPreservationAgent:No se encontró el agente de preservación intellectualEntity:Entidad intelectual inspectIntellectualEntity:Inspeccionar entidad intelectual inspectRepresentation:Inspeccionar representación @@ -189,82 +189,82 @@ allOfAObject[org.roda.core.data.v2.jobs.IndexedJob]:Procesos allOfAObject[org.roda.core.data.v2.jobs.Report]:Reportes allOfAObject[org.roda.core.data.v2.jobs.IndexedReport]:Reportes allOfAObject[org.roda.core.data.v2.notifications.Notification]:Notificaciones -allOfAObject[org.roda.core.data.v2.log.LogEntry]:All audit logs +allOfAObject[org.roda.core.data.v2.log.LogEntry]:Todos los registros de auditoría allOfAObject[org.roda.core.data.v2.user.RODAMember]:Usuarios y grupos allOfAObject[org.roda.core.data.v2.ip.metadata.DescriptiveMetadata]:Metadatos descriptivos allOfAObject[org.roda.core.data.v2.ip.metadata.PreservationMetadata]:Metadatos de preservación allOfAObject[org.roda.core.data.v2.formats.Format]:Formatos -allOfAObject[org.roda.core.data.v2.ip.metadata.IndexedPreservationEvent]:All preservation events -allOfAObject[org.roda.core.data.v2.ip.metadata.IndexedPreservationAgent]:All preservation agents +allOfAObject[org.roda.core.data.v2.ip.metadata.IndexedPreservationEvent]:Todos los eventos de preservación +allOfAObject[org.roda.core.data.v2.ip.metadata.IndexedPreservationAgent]:Todos los agentes de preservación allOfAObject[org.roda.core.data.v2.Void]:No hay objetos de entrada someOfAObject:{0} -someOfAObject[org.roda.core.data.v2.ip.AIP]:intellectual entities -someOfAObject[org.roda.core.data.v2.ip.IndexedAIP]:intellectual entities -someOfAObject[org.roda.core.data.v2.ip.Representation]:representations -someOfAObject[org.roda.core.data.v2.ip.IndexedRepresentation]:representations -someOfAObject[org.roda.core.data.v2.ip.File]:files -someOfAObject[org.roda.core.data.v2.ip.IndexedFile]:files -someOfAObject[org.roda.core.data.v2.ip.TransferredResource]:transferred resources -someOfAObject[org.roda.core.data.v2.ip.DIP]:disseminations -someOfAObject[org.roda.core.data.v2.ip.IndexedDIP]:disseminations -someOfAObject[org.roda.core.data.v2.ip.DIPFile]:dissemination files -someOfAObject[org.roda.core.data.v2.risks.Risk]:risks -someOfAObject[org.roda.core.data.v2.risks.IndexedRisk]:risks -someOfAObject[org.roda.core.data.v2.risks.RiskIncidence]:incidences -someOfAObject[org.roda.core.data.v2.ri.RepresentationInformation]:representation information -someOfAObject[org.roda.core.data.v2.jobs.Job]:jobs -someOfAObject[org.roda.core.data.v2.jobs.IndexedJob]:jobs -someOfAObject[org.roda.core.data.v2.jobs.Report]:reports -someOfAObject[org.roda.core.data.v2.jobs.IndexedReport]:reports -someOfAObject[org.roda.core.data.v2.notifications.Notification]:notifications -someOfAObject[org.roda.core.data.v2.log.LogEntry]:Audit logs -someOfAObject[org.roda.core.data.v2.user.RODAMember]:users and groups -someOfAObject[org.roda.core.data.v2.ip.metadata.DescriptiveMetadata]:descriptive metadata -someOfAObject[org.roda.core.data.v2.ip.metadata.PreservationMetadata]:preservation metadata -someOfAObject[org.roda.core.data.v2.formats.Format]:formats -someOfAObject[org.roda.core.data.v2.ip.metadata.IndexedPreservationEvent]:preservation events -someOfAObject[org.roda.core.data.v2.ip.metadata.IndexedPreservationAgent]:preservation agents +someOfAObject[org.roda.core.data.v2.ip.AIP]:entidades intelectuales +someOfAObject[org.roda.core.data.v2.ip.IndexedAIP]:entidades intelectuales +someOfAObject[org.roda.core.data.v2.ip.Representation]:representaciones +someOfAObject[org.roda.core.data.v2.ip.IndexedRepresentation]:representaciones +someOfAObject[org.roda.core.data.v2.ip.File]:archivos +someOfAObject[org.roda.core.data.v2.ip.IndexedFile]:archivos +someOfAObject[org.roda.core.data.v2.ip.TransferredResource]:recursos transferidos +someOfAObject[org.roda.core.data.v2.ip.DIP]:diseminaciones +someOfAObject[org.roda.core.data.v2.ip.IndexedDIP]:diseminaciones +someOfAObject[org.roda.core.data.v2.ip.DIPFile]:archivos de diseminación +someOfAObject[org.roda.core.data.v2.risks.Risk]:riesgos +someOfAObject[org.roda.core.data.v2.risks.IndexedRisk]:riesgos +someOfAObject[org.roda.core.data.v2.risks.RiskIncidence]:incidencias +someOfAObject[org.roda.core.data.v2.ri.RepresentationInformation]:información de representación +someOfAObject[org.roda.core.data.v2.jobs.Job]:procesos +someOfAObject[org.roda.core.data.v2.jobs.IndexedJob]:procesos +someOfAObject[org.roda.core.data.v2.jobs.Report]:informes +someOfAObject[org.roda.core.data.v2.jobs.IndexedReport]:informes +someOfAObject[org.roda.core.data.v2.notifications.Notification]:notificaciones +someOfAObject[org.roda.core.data.v2.log.LogEntry]:Registros de auditoría +someOfAObject[org.roda.core.data.v2.user.RODAMember]:usuarios y grupos +someOfAObject[org.roda.core.data.v2.ip.metadata.DescriptiveMetadata]:metadatos descriptivos +someOfAObject[org.roda.core.data.v2.ip.metadata.PreservationMetadata]:metadatos de preservación +someOfAObject[org.roda.core.data.v2.formats.Format]:formatos +someOfAObject[org.roda.core.data.v2.ip.metadata.IndexedPreservationEvent]:eventos de preservación +someOfAObject[org.roda.core.data.v2.ip.metadata.IndexedPreservationAgent]:agentes de preservación someOfAObject[org.roda.core.data.v2.Void]:No hay objetos de entrada -someOfAObject[org.roda.core.data.v2.disposal.schedule.DisposalSchedule]:disposal schedules -someOfAObject[org.roda.core.data.v2.disposal.hold.DisposalHold]:disposal holds -someOfAObject[org.roda.core.data.v2.disposal.confirmation.DisposalConfirmation]:disposal confirmations -someOfAObject[org.roda.core.data.v2.disposal.rule.DisposalRule]:disposal rules -someOfAObject[org.roda.core.data.v2.synchronization.central.DistributedInstances]:distributed instances -someOfAObject[org.roda.core.data.v2.accessKey.AccessKeys]:access tokens +someOfAObject[org.roda.core.data.v2.disposal.schedule.DisposalSchedule]:calendarios de eliminación +someOfAObject[org.roda.core.data.v2.disposal.hold.DisposalHold]:bloqueos de eliminación +someOfAObject[org.roda.core.data.v2.disposal.confirmation.DisposalConfirmation]:confirmaciones de eliminación +someOfAObject[org.roda.core.data.v2.disposal.rule.DisposalRule]:reglas de eliminación +someOfAObject[org.roda.core.data.v2.synchronization.central.DistributedInstances]:instancias distribuidas +someOfAObject[org.roda.core.data.v2.accessKey.AccessKeys]:tokens de acceso oneOfAObject:{0} -oneOfAObject[org.roda.core.data.v2.ip.AIP]:intellectual entity -oneOfAObject[org.roda.core.data.v2.ip.IndexedAIP]:intellectual entity -oneOfAObject[org.roda.core.data.v2.ip.Representation]:representation -oneOfAObject[org.roda.core.data.v2.ip.IndexedRepresentation]:representation +oneOfAObject[org.roda.core.data.v2.ip.AIP]:entidad intelectual +oneOfAObject[org.roda.core.data.v2.ip.IndexedAIP]:entidad intelectual +oneOfAObject[org.roda.core.data.v2.ip.Representation]:representación +oneOfAObject[org.roda.core.data.v2.ip.IndexedRepresentation]:representación oneOfAObject[org.roda.core.data.v2.ip.File]:archivo oneOfAObject[org.roda.core.data.v2.ip.IndexedFile]:archivo -oneOfAObject[org.roda.core.data.v2.ip.TransferredResource]:transferred resource +oneOfAObject[org.roda.core.data.v2.ip.TransferredResource]:recurso transferido oneOfAObject[org.roda.core.data.v2.ip.DIP]:diseminación oneOfAObject[org.roda.core.data.v2.ip.IndexedDIP]:diseminación -oneOfAObject[org.roda.core.data.v2.ip.DIPFile]:dissemination file -oneOfAObject[org.roda.core.data.v2.risks.Risk]:risk -oneOfAObject[org.roda.core.data.v2.risks.IndexedRisk]:risk -oneOfAObject[org.roda.core.data.v2.risks.RiskIncidence]:incidence -oneOfAObject[org.roda.core.data.v2.ri.RepresentationInformation]:representation information +oneOfAObject[org.roda.core.data.v2.ip.DIPFile]:archivo de diseminación +oneOfAObject[org.roda.core.data.v2.risks.Risk]:riesgo +oneOfAObject[org.roda.core.data.v2.risks.IndexedRisk]:riesgo +oneOfAObject[org.roda.core.data.v2.risks.RiskIncidence]:incidencia +oneOfAObject[org.roda.core.data.v2.ri.RepresentationInformation]:información de representación oneOfAObject[org.roda.core.data.v2.jobs.Job]:trabajo oneOfAObject[org.roda.core.data.v2.jobs.IndexedJob]:trabajo -oneOfAObject[org.roda.core.data.v2.jobs.Report]:report -oneOfAObject[org.roda.core.data.v2.jobs.IndexedReport]:report -oneOfAObject[org.roda.core.data.v2.notifications.Notification]:notification -oneOfAObject[org.roda.core.data.v2.log.LogEntry]:Audit log -oneOfAObject[org.roda.core.data.v2.user.RODAMember]:user or group -oneOfAObject[org.roda.core.data.v2.ip.metadata.DescriptiveMetadata]:descriptive metadata -oneOfAObject[org.roda.core.data.v2.ip.metadata.PreservationMetadata]:preservation metadata -oneOfAObject[org.roda.core.data.v2.formats.Format]:format -oneOfAObject[org.roda.core.data.v2.ip.metadata.IndexedPreservationEvent]:preservation event -oneOfAObject[org.roda.core.data.v2.ip.metadata.IndexedPreservationAgent]:preservation agent -oneOfAObject[org.roda.core.data.v2.disposal.schedule.DisposalSchedule]:disposal schedule -oneOfAObject[org.roda.core.data.v2.disposal.hold.DisposalHold]:disposal hold -oneOfAObject[org.roda.core.data.v2.disposal.confirmation.DisposalConfirmation]:disposal confirmation -oneOfAObject[org.roda.core.data.v2.disposal.rule.DisposalRule]:disposal rule -selected[\=1]:{0,number} {1} selected -selected[one]:{0,number} {1} selected -selected:{0,number} {1} selected +oneOfAObject[org.roda.core.data.v2.jobs.Report]:informe +oneOfAObject[org.roda.core.data.v2.jobs.IndexedReport]:informe +oneOfAObject[org.roda.core.data.v2.notifications.Notification]:notificación +oneOfAObject[org.roda.core.data.v2.log.LogEntry]:Registro de auditoría +oneOfAObject[org.roda.core.data.v2.user.RODAMember]:usuario o grupo +oneOfAObject[org.roda.core.data.v2.ip.metadata.DescriptiveMetadata]:metadatos descriptivos +oneOfAObject[org.roda.core.data.v2.ip.metadata.PreservationMetadata]:metadatos de preservación +oneOfAObject[org.roda.core.data.v2.formats.Format]:formato +oneOfAObject[org.roda.core.data.v2.ip.metadata.IndexedPreservationEvent]:evento de preservación +oneOfAObject[org.roda.core.data.v2.ip.metadata.IndexedPreservationAgent]:agente de preservación +oneOfAObject[org.roda.core.data.v2.disposal.schedule.DisposalSchedule]:calendario de eliminación +oneOfAObject[org.roda.core.data.v2.disposal.hold.DisposalHold]:bloqueo de eliminación +oneOfAObject[org.roda.core.data.v2.disposal.confirmation.DisposalConfirmation]:confirmación de eliminación +oneOfAObject[org.roda.core.data.v2.disposal.rule.DisposalRule]:regla de eliminación +selected[\=1]:{0,number} {1} seleccionado(s) +selected[one]:{0,number} {1} seleccionado(s) +selected:{0,number} {1} seleccionado(s) inspectTransferredResource:Inspeccionar recursos transferidos identifierNotFound:Identificador (no encontrado) originalRepresentation:original @@ -272,7 +272,7 @@ alternativeRepresentation:alternativo representationStatus:Estado executingTaskMessage:Estamos ejecutando su tarea solicitada, por favor espere... noItemsToDisplay:No hay {0} para desplegar -noItemsToDisplayPreFilters:Could not find any {0} in this context. +noItemsToDisplayPreFilters:No se encontró ningún {0} en este contexto. noItemsToDisplayButFacetsActive:No hay {0} para desplegar mas existen filtros ativos. Para deshabilitarlos, haga resetFacetsLink:clic aquí browserOfflineError:Su navegador esta fuera de linea, por favor revise su conectividad @@ -300,25 +300,25 @@ createJobWorkflow: Flujo de trabajo createJobCategorySelect: Categoría createJobCategoryWorkflow: Categoría: {0,list} createJobCurlCommand: Comando cURL para crear proceso -createJobHighPriorityDescription: High priority processes will start as soon as there is an available slot -createJobMediumPriorityDescription: Medium priority processes will start if there is an available slot and no high priority is on the queue -createJobLowPriorityDescription: Low priority processes will run if there is a slot available and if no other processes are waiting to be started -createJobNormalParallelismDescription: Normal parallelism will be running many processes at the same time -createJobLimitedParallelismDescription: Limited parallelism will limit the amount of processes that are run at the same time -createJobOrchestrationPrioritySeparator: Priority -createJobOrchestrationParallelismSeparator: Parallelism +createJobHighPriorityDescription: Los procesos de alta prioridad comenzarán en cuanto haya un slot disponible +createJobMediumPriorityDescription: Los procesos de prioridad media comenzarán si hay un slot disponible y no hay procesos de alta prioridad en la cola +createJobLowPriorityDescription: Los procesos de baja prioridad se ejecutarán si hay un slot disponible y no hay otros procesos esperando para iniciarse +createJobNormalParallelismDescription: El paralelismo normal ejecutará muchos procesos al mismo tiempo +createJobLimitedParallelismDescription: El paralelismo limitado restringirá el número de procesos que se ejecutan al mismo tiempo +createJobOrchestrationPrioritySeparator: Prioridad +createJobOrchestrationParallelismSeparator: Paralelismo createJobOrchestration: Orquestación copiedToClipboardTitle: Copiado copiedToClipboardMessage: El contenido se ha copiado al clipboard -jobCreatedRedirectTitle: A job was created -removeJobCreatedMessage: Do you want to follow the remove job? -identifyFormatsJobCreatedMessage: Do you want to follow the format identification job? -moveJobCreatedMessage: Do you want to follow the move job? -jobCreatedMessage: Do you want to follow the job? +jobCreatedRedirectTitle: Se ha creado un proceso +removeJobCreatedMessage: ¿Desea seguir el proceso de eliminación? +identifyFormatsJobCreatedMessage: ¿Desea seguir el proceso de identificación de formatos? +moveJobCreatedMessage: ¿Desea seguir el proceso de movimiento? +jobCreatedMessage: ¿Desea seguir el proceso? # Ingest transfer upload ingestTransferUploadTitle:Cargar ingesta transferida -fileUploadTitle:File upload +fileUploadTitle:Carga de archivo ingestTransferUploadDropHere:Borrar archivos aquí ingestTransferUploadBrowseFiles:Seleccionar archivos ingestTransferUploadFinishButton:Hecho @@ -336,13 +336,13 @@ dialogRefresh:Refrescar removeSuccessTitle:Items eliminados removeSuccessMessage:{0} items eliminados con éxito removeConfirmDialogTitle:Confirmar eliminación de items -removeAllConfirmDialogMessageSingle:Are you sure you want to remove the intellectual entity "{0}", including all its nested items? +removeAllConfirmDialogMessageSingle:¿Está seguro de que desea eliminar la entidad intelectual «{0}», incluyendo todos sus elementos anidados? removeSelectedConfirmDialogMessage:¿Seguro que quiere eliminar los items seleccionados {0}? removingSuccessTitle:Eliminando Items -removingSuccessMessage:Executing action to remove {0} items -moveConfirmDialogTitle:Confirm moving of intellectual entities -moveAllConfirmDialogMessageSingle:Are you sure you want to move the intellectual entity "{0}", including all its nested items? -moveSelectedConfirmDialogMessage:Are you sure you want to move the selected {0} items? +removingSuccessMessage:Ejecutando acción para eliminar {0} elementos +moveConfirmDialogTitle:Confirmar traslado de entidades intelectuales +moveAllConfirmDialogMessageSingle:¿Está seguro de que desea mover la entidad intelectual «{0}», incluyendo todos sus elementos anidados? +moveSelectedConfirmDialogMessage:¿Está seguro de que desea mover los {0} elementos seleccionados? # Generic Buttons backButton:Regresar cancelButton:Cancelar @@ -350,20 +350,20 @@ revertButton:Revertir removeButton:Eliminar refreshButton:Refrescar newButton:Nuevo -newRepresentationButton:Create representation +newRepresentationButton:Crear representación editButton:Editar -liftButton:Lift +liftButton:Levantar deactivateButton:DESACTIVAR saveButton:Guardar addButton:Agregar confirmButton:Validar applyAllButton:Aplicar a todas las de abajo -overrideManualAppliedSchedulesButton:Apply rules and override -applyDisposalRulesButton:Apply rules keeping manually associated +overrideManualAppliedSchedulesButton:Aplicar reglas y sobrescribir +applyDisposalRulesButton:Aplicar reglas conservando las asociadas manualmente stopButton:Detener -approveButton:Approve +approveButton:Aprobar rejectButton:Rechazar -listButton:Created packages +listButton:Paquetes creados downloadButton:Descargar renameButton:Rebautizar moveButton:Mover @@ -376,9 +376,9 @@ changeStatusButton:Cambiar Estado clearButton:Limpiar selectAllButton:Todos closeButton:Cerrar -copyAndCloseButton:Copy & Close +copyAndCloseButton:Copiar y cerrar selectButton:Seleccionar -printButton:Print report +printButton:Imprimir informe # Identify formats identifyFormatsButton:Identificar formatos identifyingFormatsTitle:Identificar formatos @@ -389,8 +389,8 @@ viewRepresentationInfoFileButton:Información del archivo viewRepresentationErrorPreview:Ha ocurrido un error mientras intentaba ver el archivo viewRepresentationTooLargeErrorPreview:Este archivo es demasiado grande para abrirlo en el navegador viewRepresentationNotSupportedPreview:previsualización de archivos no es compatible -viewRepresentationNotSupportedPreviewCentralInstance:Synchronized files from distributed instances cannot be accessed via the central instance -viewRepresentationNotSupportedPreviewShallowFile:Could not retrieve the file using the reference to the external storage +viewRepresentationNotSupportedPreviewCentralInstance:No se puede acceder a los archivos sincronizados desde instancias distribuidas a través de la instancia central +viewRepresentationNotSupportedPreviewShallowFile:No se pudo recuperar el archivo utilizando la referencia al almacenamiento externo viewRepresentationFileDisseminationTitle:Diseminación viewRepresentationInfoFilename:Nombre del archivo viewRepresentationInfoSize:Tamaño @@ -398,21 +398,21 @@ viewRepresentationInfoExtension:Extensión viewRepresentationInfoMimetype:Mimetype viewRepresentationInfoFormat:Formato viewRepresentationInfoPronom:PRONOM -viewTechnicalInformation:Technical information -showTechnicalMetadata:Show technical metadata -viewTechnicalMetadata: Technical metadata +viewTechnicalInformation:Información técnica +showTechnicalMetadata:Mostrar metadatos técnicos +viewTechnicalMetadata: Metadatos técnicos viewRepresentationInfoCreatingApplicationName:Creando nombre de la aplicación viewRepresentationInfoCreatingApplicationVersion:Creando versión de la aplicación viewRepresentationInfoDateCreatedByApplication:Fecha creación por la aplicación viewRepresentationInfoHash:Fijación viewRepresentationInfoStoragePath:Ruta almacenamiento -viewRepresentationRemoveFileTitle:Confirm file remove -viewRepresentationRemoveFileMessage:Are you sure you want to remove this file? +viewRepresentationRemoveFileTitle:Confirmar eliminación de archivo +viewRepresentationRemoveFileMessage:¿Está seguro de que desea eliminar este archivo? # New Process processNewDefaultName:Trabajo {0, LocalDateTime, PREDEF\: DATETIME BREVE} pluginLabel:{0} -mandatoryPlugin:Mandatory -optionalPlugin:Optional +mandatoryPlugin:Obligatorio +optionalPlugin:Opcional pluginLabelWithVersion:{0} ({1}) pluginAipIdButton:Seleccionar processNewMissingMandatoryInfoDialogTitle:Falta información obligatoria @@ -431,16 +431,16 @@ showPluginCategories[management]:administración showPluginCategories[dissemination]:diseminación showPluginCategories[reindex]:reindización showPluginCategories[misc]:misceláneos -showPluginCategories[digital_signature]:digital signature -showPluginCategories[risk_assessment]:risk assessment -showPluginCategories[maintenance]:maintenance +showPluginCategories[digital_signature]:firma digital +showPluginCategories[risk_assessment]:evaluación de riesgos +showPluginCategories[maintenance]:mantenimiento showPluginCategories[eArchiving]:eArchiving -showPluginCategories[database_preservation]:database preservation +showPluginCategories[database_preservation]:preservación de bases de datos showJobStatusCreated:esperando showJobStatusStarted:ejecutando showJobStatusCompleted:Hecho -showJobStatusPendingApproval:pending approval -showJobStatusScheduled:scheduled +showJobStatusPendingApproval:pendiente de aprobación +showJobStatusScheduled:programado showJobStatusApprovalRejected:rechazado showJobStatusFailedDuringCreation:Falló inicio showJobStatusFailedToComplete:Falló @@ -462,28 +462,28 @@ showRepresentationExtended:Representación showFileExtended:Archivo showTransferredResourceExtended:Recursos transferidos showJobSourceObjects:Objetos de origen -showAttachments:Attachments -jobStopConfirmDialogTitle:Confirm job stop -jobStopConfirmDialogMessage:Are you sure you want to stop the job? -jobApproveConfirmDialogTitle:Confirm job approval -jobApproveConfirmDialogMessage:Are you sure you want to approve the job? -jobRejectConfirmDialogTitle:Confirm job reject -jobRejectConfirmDialogMessage:Are you sure you want to reject the job? -jobSelectedApproveConfirmDialogMessage: Are you sure you want to approve the selected {0} item(s)? -jobSelectedRejectConfirmDialogMessage: Are you sure you want to delete the selected {0} item(s)? +showAttachments:Adjuntos +jobStopConfirmDialogTitle:Confirmar detención del proceso +jobStopConfirmDialogMessage:¿Está seguro de que desea detener el proceso? +jobApproveConfirmDialogTitle:Confirmar aprobación del proceso +jobApproveConfirmDialogMessage:¿Está seguro de que desea aprobar el proceso? +jobRejectConfirmDialogTitle:Confirmar rechazo del proceso +jobRejectConfirmDialogMessage:¿Está seguro de que desea rechazar el proceso? +jobSelectedApproveConfirmDialogMessage: ¿Está seguro de que desea aprobar los {0} elemento(s) seleccionado(s)? +jobSelectedRejectConfirmDialogMessage: ¿Está seguro de que desea eliminar los {0} elemento(s) seleccionado(s)? # Browse itemId:Identificador del AIP sipId:Identificador del SIP -processIdTitle:Ingest process identifier +processIdTitle:Identificador del proceso de ingesta updateProcessIdTitle:Procesos de Modificación Identificados -dateCreated:Created on {0} by {1} -dateUpdated:Updated on {0} by {1} +dateCreated:Creado el {0} por {1} +dateUpdated:Actualizado el {0} por {1} aipCreated:Creado aipUpdated:Actualizado -dateCreatedOrUpdated:by {1} on {0} -dateCreatedAndUpdated:Created by {1} on {0} and last updated by {3} on {2} -itemInstanceId:Instance identifier +dateCreatedOrUpdated:por {1} el {0} +dateCreatedAndUpdated:Creado por {1} el {0} y última actualización por {3} el {2} +itemInstanceId:Identificador de instancia aipType:Tipo # Search searchDropdownLabels:{0} @@ -491,20 +491,20 @@ searchDropdownLabels[org.roda.core.data.v2.ip.IndexedAIP]:Entidades intelectuale searchDropdownLabels[org.roda.core.data.v2.ip.IndexedRepresentation]:Representaciones searchDropdownLabels[org.roda.core.data.v2.ip.IndexedFile]:Archivos searchDropdownLabels[org.roda.core.data.v2.ip.TransferredResource]:Recursos transferidos -searchDropdownLabels[org.roda.core.data.v2.ip.IndexedPreservationEvent]:disseminations -searchDropdownLabels[org.roda.core.data.v2.ip.IndexedDIP]:disseminations -searchDropdownLabels[org.roda.core.data.v2.ip.DIPFile]:dissemination files +searchDropdownLabels[org.roda.core.data.v2.ip.IndexedPreservationEvent]:Diseminaciones +searchDropdownLabels[org.roda.core.data.v2.ip.IndexedDIP]:Diseminaciones +searchDropdownLabels[org.roda.core.data.v2.ip.DIPFile]:Archivos de diseminación searchDropdownLabels[org.roda.core.data.v2.risks.IndexedRisk]:Riesgos searchDropdownLabels[org.roda.core.data.v2.risks.RiskIncidence]:Incidencias -searchDropdownLabels[org.roda.core.data.v2.ri.RepresentationInformation]:representation information +searchDropdownLabels[org.roda.core.data.v2.ri.RepresentationInformation]:Información de representación searchDropdownLabels[org.roda.core.data.v2.jobs.IndexedJob]:Procesos searchDropdownLabels[org.roda.core.data.v2.jobs.IndexedReport]:Reportes -searchDropdownLabels[org.roda.core.data.v2.notifications.Notification]:notifications -searchDropdownLabels[org.roda.core.data.v2.log.LogEntry]:Audit logs -searchDropdownLabels[org.roda.core.data.v2.user.RODAMember]:users and groups -searchDropdownLabels[org.roda.core.data.v2.ip.metadata.DescriptiveMetadata]:descriptive metadata -searchDropdownLabels[org.roda.core.data.v2.ip.metadata.PreservationMetadata]:preservation metadata -searchDropdownLabels[org.roda.core.data.v2.formats.Format]:formats +searchDropdownLabels[org.roda.core.data.v2.notifications.Notification]:Notificaciones +searchDropdownLabels[org.roda.core.data.v2.log.LogEntry]:Registros de auditoría +searchDropdownLabels[org.roda.core.data.v2.user.RODAMember]:Usuarios y grupos +searchDropdownLabels[org.roda.core.data.v2.ip.metadata.DescriptiveMetadata]:Metadatos descriptivos +searchDropdownLabels[org.roda.core.data.v2.ip.metadata.PreservationMetadata]:Metadatos de preservación +searchDropdownLabels[org.roda.core.data.v2.formats.Format]:Formatos searchDropdownLabels[org.roda.core.data.v2.Void]:No hay objetos de entrada searchListBoxItems:Entidades intelectuales searchListBoxRepresentations:Representaciones @@ -522,9 +522,9 @@ searchButton:Buscar # AIP, Representations, File & Transferred Resources aipGenericTitle:Titulo aipDates:Fechas -aipRiskIncidences:{0} risk incidences -aipEvents:{0} preservation events -aipLogs:{0} log entries +aipRiskIncidences:{0} incidencias de riesgo +aipEvents:{0} eventos de preservación +aipLogs:{0} entradas de registro fileId:Id fileName:Nombre filePath:Ruta @@ -573,13 +573,13 @@ preservationAgentNote:Nota preservationAgentExtension:Extensión # Select dialog selectAipCancelButton:Cancelar -selectAipEmptyParentButton:Move a la raíz +selectAipEmptyParentButton:Mover a la raíz selectAipSelectButton:Seleccionar selectAipSearchResults:Resultado de búsqueda selectRepresentationSearchResults:Resultado de búsqueda selectFileSearchResults:Resultado de búsqueda selectTransferredResourcesSearchResults:Resultado de búsqueda -renameTransferredResourcesDialogTitle:Rename transferred resource +renameTransferredResourcesDialogTitle:Cambiar nombre del recurso transferido # Rename Item renameItemTitle:Cambiar el nombre de item renameSuccessful:Éxito renombrado item! @@ -622,24 +622,24 @@ aipState[CREATED]:Creado aipState[DELETED]:Eliminado aipState[INGEST_PROCESSING]:Ingestando aipState[UNDER_APPRAISAL]:En evaluación -aipState[DESTROYED]:Destroyed +aipState[DESTROYED]:Destruido # Search pre-filters searchPreFilterSimpleFilterParameter:{0}\: {1} searchPreFilterBasicSearchFilterParameter:{0}\: {1} searchPreFilterNotSimpleFilterParameter:NOT {0}\: {1} searchPreFilterEmptyKeyFilterParameter:NO {0} -searchPreFilterLongRangeFilterParameter:{0} is between {1} and {2} -searchPreFilterLongRangeFilterParameterGreaterThan:{0} is greater than {1} -searchPreFilterLongRangeFilterParameterSmallerThan:{0} is smaller than {1} -searchPreFilterOneOfManyFilterParameterWithSize:{0} is one of one of {1} possible values +searchPreFilterLongRangeFilterParameter:{0} está entre {1} y {2} +searchPreFilterLongRangeFilterParameterGreaterThan:{0} es mayor que {1} +searchPreFilterLongRangeFilterParameterSmallerThan:{0} es menor que {1} +searchPreFilterOneOfManyFilterParameterWithSize:{0} es uno de uno de {1} posibles valores searchPreFilterOneOfManyFilterParameterSingle:{0}\: {1} -searchPreFilterOneOfManyFilterParameterWithList:{0} is one of {1,list} -searchPreFilterDateIntervalFilterParameter:{0} is between {1} and {2} -searchPreFilterDateIntervalFilterParameterFrom:{0} is between {1} and now -searchPreFilterDateIntervalFilterParameterTo:{0} is until {1} +searchPreFilterOneOfManyFilterParameterWithList:{0} es uno de {1,list} +searchPreFilterDateIntervalFilterParameter:{0} está entre {1} y {2} +searchPreFilterDateIntervalFilterParameterFrom:{0} está entre {1} y ahora +searchPreFilterDateIntervalFilterParameterTo:{0} hasta {1} searchPreFilterOr: OR  searchPreFilterAnd: AND  -searchPreFilterWhere: where +searchPreFilterWhere: donde searchPreFilterName:{0} searchPreFilterName[state]:estado searchPreFilterName[ingestJobId]:trabajo @@ -649,16 +649,16 @@ searchPreFilterName[ingestSIPIds]:SIP IDs searchPreFilterValue:{0} searchPreFilterValue[UNDER_APPRAISAL]:En evaluación # Risk register -riskRegisterTitle:Risk register -riskIncidenceRegisterTitle:Risk incidence register -riskIncidenceRegisterSearchPlaceHolder:Search risk incidences... +riskRegisterTitle:Registro de riesgos +riskIncidenceRegisterTitle:Registro de incidencias de riesgo +riskIncidenceRegisterSearchPlaceHolder:Buscar incidencias de riesgo... editRiskNotFound:Riesgo no encontrado {0} riskRemoveConfirmDialogTitle:Confirmar eliminación del riesgo riskRemoveSelectedConfirmDialogMessage:Estas seguro de querer eliminar los riesgos {0} seleccionados? riskHistoryRemoveConfirmDialogTitle:Confirmar eliminación del riesgo -riskHistoryRemoveConfirmDialogMessage:Are you sure you want to remove the selected risk history? -riskHistoryRevertConfirmDialogTitle:Confirm revert risk -riskHistoryRevertConfirmDialogMessage:Are you sure you want to revert the risk to the selected history? +riskHistoryRemoveConfirmDialogMessage:¿Está seguro de que desea eliminar el historial de riesgos seleccionado? +riskHistoryRevertConfirmDialogTitle:Confirmar reversión del riesgo +riskHistoryRevertConfirmDialogMessage:¿Está seguro de que desea revertir el riesgo al historial seleccionado? riskRemoveConfirmDialogCancel:No riskRemoveConfirmDialogOk:Si riskRemoveSuccessTitle:Reisgo(s) eliminados @@ -670,16 +670,16 @@ severityLevel[MODERATE]:Moderado severityLevel[HIGH]:Alta getRisksDialogName:Riesgos riskHistoryButton:Historia -editIncidenceNotFound:Incidence {0} not found -editIncidenceFailure:Edit incidence {0} failed -riskIncidenceRemoveConfirmDialogTitle:Confirm remove risk incidence -riskIncidenceRemoveSelectedConfirmDialogMessage:Are you sure you want to remove the selected {0} risk incidence(s)? +editIncidenceNotFound:Incidencia {0} no encontrada +editIncidenceFailure:Error al editar la incidencia {0} +riskIncidenceRemoveConfirmDialogTitle:Confirmar eliminación de incidencia de riesgo +riskIncidenceRemoveSelectedConfirmDialogMessage:¿Está seguro de que desea eliminar las {0} incidencia(s) de riesgo seleccionada(s)? riskIncidenceRemoveConfirmDialogCancel:No riskIncidenceRemoveConfirmDialogOk:Si -riskCreatedTitle:Risk created -riskCreatedMessage: Risk was successfully created +riskCreatedTitle:Riesgo creado +riskCreatedMessage: El riesgo se ha creado correctamente # Representation information register -representationInformationRegisterTitle:Representation network +representationInformationRegisterTitle:Red de representación representationInformationRegisterSearchPlaceHolder:Buscar información de representación editRepresentationInformationNotFound:Información de representación no encontrado {0} representationInformationRemoveFolderConfirmDialogTitle:Confirmar elminación de información de representación @@ -689,14 +689,14 @@ representationInformationRemoveFolderConfirmDialogOk:Si representationInformationRemoveSuccessTitle:Eliminar información de representación representationInformationRemoveSuccessMessage:Información de representación eliminado(s) exitosamente {0} representationInformationListItems:{0} -representationInformationAdditionalInformation:Additional information -representationInformationMissingFieldsTitle:Missing fields -representationInformationMissingFields:There are some mandatory empty fields -noTitleMessage:No title -currentRelationResults:List of current results -createNewRepresentationInformation:Create new -addToExistingRepresentationInformation:Add to existing -atLeastOneOfAbove:At least one of the above +representationInformationAdditionalInformation:Información adicional +representationInformationMissingFieldsTitle:Campos obligatorios +representationInformationMissingFields:Hay campos obligatorios vacíos +noTitleMessage:Sin título +currentRelationResults:Lista de resultados actuales +createNewRepresentationInformation:Crear nuevo +addToExistingRepresentationInformation:Añadir a existente +atLeastOneOfAbove:Al menos uno de los anteriores # Common Messages logParameter:{0}\: {1} windowTitle:RODA - {0} @@ -711,15 +711,15 @@ editGroupFailure:No se pudo editar el grupo {0} debido a un error\: {1}. editUserNotFound:El usuario {0} ya no existe. editUserEmailAlreadyExists:La dirección de correo electrónico {0} ya se utiliza está en los registros. Por favor seleccione otro correo electrónico o recuperar su nombre de usuario y contraseña. editGroupNotFound:El grupo {0} ya no existe. -userRemoveConfirmDialogTitle:User and group remove process -userRemoveConfirmDialogMessage:Do you want to remove the selected users and groups? +userRemoveConfirmDialogTitle:Proceso de eliminación de usuarios y grupos +userRemoveConfirmDialogMessage:¿Desea eliminar los usuarios y grupos seleccionados? userGroups:Grupos addUserButton:Agregar usuario addGroupButton:Agregar grupo userIdentifier:Identificador userFullName:Nombre completo # Job and report -jobInstanceId:Instance identifier +jobInstanceId:Identificador de instancia jobName: Nombre jobCreator: Creador jobParallelismShortBadge: {0} @@ -741,15 +741,15 @@ jobStartDate: Fecha inicio jobEndDate: Fecha fin jobDuration: Duración jobStatus: Estado -jobStateDetails: State details +jobStateDetails: Detalles del estado jobProgress: Progreso jobPlugin: Plugin jobProcessed: Reportes jobProcessedSearchPlaceHolder: Buscar reportes... reportJob: Trabajo reportIngestType: Tipo -newIngestion: New ingestion -ingestionUpdate: Ingest update +newIngestion: Nueva ingesta +ingestionUpdate: Actualización de ingesta reportDateCreated: Feecha de creación reportDateUpdated: Fecha modificación reportDuration: Duración @@ -762,7 +762,7 @@ jobFailureCountMessage: Falla reportLastUpdatedAt: Última modificación en reportLastRunTask: Ejecutar última tarea reportStatus:Estado -reportScheduleInfo:Schedule Info +reportScheduleInfo:Información de calendario reportProgress:Progreso reportOutcome: Resultado reportSource: Fuente @@ -817,19 +817,19 @@ riskIncidenceStatusValue[UNMITIGATED]:No mitigado riskIncidenceStatusValue[MITIGATED]:mitigado riskIncidenceStatusValue[ACCEPT_RISK]:Acepta el riesgo riskIncidenceStatusValue[FALSE_POSITIVE]:Falso positivo -riskNotMitigatedIncidences:Not mitigated +riskNotMitigatedIncidences:No mitigado # Representation information representationInformationIdentifier:Identificador representationInformationName:Nombre representationInformationDescription:Descripción -representationInformationFamily:Family -representationInformationTags:Tags -representationInformationSupport:Support level +representationInformationFamily:Familia +representationInformationTags:Etiquetas +representationInformationSupport:Nivel de soporte representationInformationSupportValue:{0} -representationInformationSupportValue[KNOWN]:Known +representationInformationSupportValue[KNOWN]:Conocido representationInformationSupportValue[SUPPORTED]:Soportado -representationInformationSupportValue[UNSUPPORTED]:Unsupported -representationInformationRelationType:Relation type +representationInformationSupportValue[UNSUPPORTED]:No soportado +representationInformationRelationType:Tipo de relación representationInformationRelationObjectType:Tipo de Objeto representationInformationRelationObjectType[AIP]:Entidad intelectual representationInformationRelationObjectType[REPRESENTATION_INFORMATION]:Información de representación @@ -839,47 +839,47 @@ representationInformationRelationLink:Link representationInformationRelationTitle:Titulo representationInformationAddNewRelation:Add new relation representationInformationEditAssociations:Edit associations -representationInformationIntellectualEntities:There are {0,number} intellectual entities associated with this representation information -representationInformationIntellectualEntities[\=0]:There are no intellectual entities associated with this representation information -representationInformationIntellectualEntities[\=1]:There is one intellectual entity associated with this representation information -representationInformationIntellectualEntities[one]:There is one intellectual entity associated with this representation information -representationInformationRepresentations:There are {0,number} representations associated with this representation information -representationInformationRepresentations[\=0]:There are no representations associated with this representation information -representationInformationRepresentations[\=1]:There is one representation associated with this representation information -representationInformationRepresentations[one]:There is one representation associated with this representation information -representationInformationFiles:There are {0,number} files associated with this representation information -representationInformationFiles[\=0]:There are no files associated with this representation information -representationInformationFiles[\=1]:There is one file associated with this representation information -representationInformationFiles[one]:There is one file associated with this representation information +representationInformationIntellectualEntities:Hay {0,number} entidades intelectuales asociadas a esta información de representación +representationInformationIntellectualEntities[\=0]:No hay entidades intelectuales asociadas a esta información de representación +representationInformationIntellectualEntities[\=1]:Hay una entidad intelectual asociada a esta información de representación +representationInformationIntellectualEntities[one]:Hay una entidad intelectual asociada a esta información de representación +representationInformationRepresentations:Hay {0,number} representaciones asociadas a esta información de representación +representationInformationRepresentations[\=0]:No hay representaciones asociadas a esta información de representación +representationInformationRepresentations[\=1]:Hay una representación asociada a esta información de representación +representationInformationRepresentations[one]:Hay una representación asociada a esta información de representación +representationInformationFiles:Hay {0,number} archivos asociados a esta información de representación +representationInformationFiles[\=0]:No hay archivos asociados a esta información de representación +representationInformationFiles[\=1]:Hay un archivo asociado a esta información de representación +representationInformationFiles[one]:Hay un archivo asociado a esta información de representación # Descriptive Metadata metadataType:Tipo metadataFilename:Nombre archivo #Other Metadata -otherMetadata:other metadata +otherMetadata:otros metadatos # Browse -appraisalTitle:Assessment +appraisalTitle:Evaluación appraisalAccept:Aceptar appraisalReject:Rechazar transferredResourcesTitle:Recursos transferidos newArchivalPackage:Nuevo -newSublevel:Create sublevel +newSublevel:Crear subnivel moveArchivalPackage:Mover archivalPackagePermissions:Permisos archivalPackagePermissionsTitle:Permiso paquete de archivos disseminationPermissions:Permisos removeArchivalPackage:Eliminar -preservationTitle:Preservation +preservationTitle:Preservación newProcessPreservation:Proceso preservationEvents:Eventos preservationRisks:Riesgos downloadDocumentation:Documentación -downloadNoDocumentationTitle:No documentation -downloadNoDocumentationDescription:There are no documentation on this intellectual entity -downloadSubmissions:Download submissions -downloadNoSubmissionsTitle:No submissions -downloadNoSubmissionsDescription:There are no submissions on this intellectual entity +downloadNoDocumentationTitle:Sin documentación +downloadNoDocumentationDescription:No hay documentación en esta entidad intelectual +downloadSubmissions:Descargar envíos +downloadNoSubmissionsTitle:Sin envíos +downloadNoSubmissionsDescription:No hay envíos en esta entidad intelectual addPermission:Agregar permiso permissionAssignedGroups:Asignar grupo permissionAssignedUsers:Asignar usuarios @@ -889,8 +889,8 @@ listOfAIPs:Lista de AIPs listOfRepresentations:Lista de representación listOfDisseminations:Lista de diseminadores unknownAncestorError:Antecesor desconocido -searchPrevious:Previous -searchNext:Next +searchPrevious:Anterior +searchNext:Siguiente searchContext:En este contexto searchAIP:En este paquete de archivo aipPermissionDetails:Permisos @@ -899,8 +899,8 @@ representation:Representación representationListOfFiles:Lista de archivos representationRemoveTitle:Eliminar representación representationRemoveMessage:¿Seguro que desea eliminar toda la representación? -entityTypeAddNew:Add new ... -entityTypeNewLabel:New type +entityTypeAddNew:Agregar nuevo... +entityTypeNewLabel:Nuevo tipo # File file:Archivo folder:Carpeta @@ -909,7 +909,7 @@ selectedFileRemoveMessage:¿Seguro que desea eliminar los archivos seleccionados # Dissemination dissemination:Diseminación # Dissemination File -disseminationFile:Dissemination File +disseminationFile:Archivo de diseminación # Job processes # Login loginUsername:Nombre de usuario @@ -917,29 +917,29 @@ loginPassword:Contraseña loginRecoverPassword:Recuperar clave loginRegister:Regístrate loginLogin:Usuario -loginResendEmail:Resend verification email -loginResendEmailSuccessDialogTitle:Email verification -loginResendEmailSuccessDialogMessage:Please check your inbox for the verification email and follow the link in the message. +loginResendEmail:Reenviar correo de verificación +loginResendEmailSuccessDialogTitle:Verificación de correo electrónico +loginResendEmailSuccessDialogMessage:Por favor, revise su bandeja de entrada para el correo de verificación y siga el enlace del mensaje. loginResendEmailSuccessDialogButton:De acuerdo -loginResendEmailFailureDialogTitle:Email verification -loginResendEmailFailureDialogMessage:It was not possible to send the verification email. Check the username is correct and try again. +loginResendEmailFailureDialogTitle:Verificación de correo electrónico +loginResendEmailFailureDialogMessage:No fue posible enviar el correo de verificación. Compruebe que el nombre de usuario es correcto e inténtelo de nuevo. loginResendEmailFailureDialogButton:De acuerdo -loginResendEmailVerificationFailure:It was not possible to send the verification email. Check the username is correct and try again. +loginResendEmailVerificationFailure:No fue posible enviar el correo de verificación. Compruebe que el nombre de usuario es correcto e inténtelo de nuevo. fillUsernameAndPasswordMessage:Por favor ingresar usuario y clave wrongUsernameAndPasswordMessage:Usuario o clave incorrecto -emailUnverifiedMessage:You must validate your email to login. Please check your inbox for the verification email and follow the link in the message. To receive a new email click the button "Resend verification email" bellow. If you do not receive any e-mail please contact the administrator. -inactiveUserMessage:Your user was deactivated, please contact the administrator. +emailUnverifiedMessage:Debe validar su correo electrónico para iniciar sesión. Por favor, revise su bandeja de entrada para el correo de verificación y siga el enlace del mensaje. Para recibir un nuevo correo, haga clic en el botón "Reenviar correo de verificación". Si no recibe ningún correo, contacte con el administrador. +inactiveUserMessage:Su usuario ha sido desactivado. Por favor, contacte con el administrador. systemCurrentlyUnavailableMessage:Sistema no disponible en este momento -learnMore:Learn More +learnMore:Más información # More Search -searchDuplicateWarningMessage:The {0} field is duplicated. An 'OR' operator will be used. +searchDuplicateWarningMessage:El campo {0} está duplicado. Se utilizará el operador 'OR'. # Activity log logEntryIdentifier:Identificador logEntryComponent:Componente logEntryMethod:Método logEntryAddress:Dirección logEntryDate:Fecha -logEntryDatetime:Datetime +logEntryDatetime:Fecha y hora logEntryDuration:Duración logEntryRelatedObject:Objeto relacionado logEntryUsername:Nombre de usuario @@ -950,8 +950,8 @@ logEntryStateValue:{0} logEntryStateValue[SUCCESS]:Éxito logEntryStateValue[FAILURE]:Fracaso logEntryStateValue[UNAUTHORIZED]:No autorizado -logEntryInstanceId:Instance -relatedAuditLogs: Related audit logs +logEntryInstanceId:Instancia +relatedAuditLogs: Registros de auditoría relacionados # Notifications notificationAck:Reconocido notificationIdentifier:Identificador @@ -984,47 +984,47 @@ title[browse]:Catalogo title[search]:Buscar title[help]:Ayuda title[administration]:Administración -title[administration_actions]:Preservation actions -title[administration_internal_actions]:Internal actions -title[administration_disposal]:Disposal -title[administration_user]:Users and groups +title[administration_actions]:Acciones de preservación +title[administration_internal_actions]:Acciones internas +title[administration_disposal]:Eliminación +title[administration_user]:Usuarios y grupos title[administration_user_users]:Lista de usuarios title[administration_user_groups]:Lista de grupos title[administration_event]:Programador title[administration_event_tasks]:Programador title[administration_event_taskInstances]:Lista title[administration_metadataEditor]:Edición de metadata -title[administration_statistics]:Reporting -title[administration_log]:Audit logs +title[administration_statistics]:Informes +title[administration_log]:Registros de auditoría title[administration_help]:Ayuda title[administration_preferences]:Preferencias -title[administration_notifications]:Notification logs -title[administration_distributed_instances]:Distributed Instances -title[administration_local_instance_configuration]:Local Instance -title[administration_access_keys]:Access Keys -title[administration_monitoring]:Monitoring -title[administration_market_place]:Marketplace -title[disposal]:Disposal -title[disposal_policies]:Disposal policies -title[disposal_confirmations]:Disposal confirmations -title[overdue_actions]:Overdue records -title[disposal_destroyed_records]:Destroyed records +title[administration_notifications]:Registros de notificaciones +title[administration_distributed_instances]:Instancias distribuidas +title[administration_local_instance_configuration]:Instancia local +title[administration_access_keys]:Tokens de acceso +title[administration_monitoring]:Monitorización +title[administration_market_place]:Mercado de aplicaciones +title[disposal]:Eliminación +title[disposal_policies]:Políticas de eliminación +title[disposal_confirmations]:Confirmaciones de eliminación +title[overdue_actions]:Registros vencidos +title[disposal_destroyed_records]:Registros destruidos title[planning]:Planificando title[planning_monitoring]:Monitoreo interno -title[planning_risk]:Risk register -title[planning_representation_information]:Representation network +title[planning_risk]:Registro de riesgos +title[planning_representation_information]:Red de representación title[planning_event]:Eventos de preservación title[planning_agent]:Agentes de preservación title[planning_format]:Formato de Registro title[ingest]:Ingesta title[ingest_preIngest]:Pre-ingesta -title[ingest_transfer]:Transfer +title[ingest_transfer]:Transferencia title[ingest_submit]:Enviar title[ingest_submit_upload]:Enviar paquete title[ingest_submit_create]:Crear paquete title[ingest_list]:Procesos title[ingest_help]:Ayuda -title[ingest_appraisal]:Assessment +title[ingest_appraisal]:Evaluación title[settings]:Configuración title[language]:Lenguaje # Login Panel @@ -1033,16 +1033,16 @@ loginProfile:Perfil loginDialogTitle:Autenticación loginDialogLogin:Usuario loginDialogCancel:Cancelar -loggedIn:You are already logged in as {0}, you need to log out before logging in again. -welcomePage:Welcome page -casInformationText:You can also integrate with various authentication protocols using RODA Enterprise Identity and Access Management. +loggedIn:Ya ha iniciado sesión como {0}. Debe cerrar sesión antes de volver a iniciar sesión. +welcomePage:Página de bienvenida +casInformationText:También puede integrarse con varios protocolos de autenticación mediante RODA Enterprise Identity and Access Management. casTitleText:RODA Enterprise Identity and Access Management # HOME homeTitle:Ir a inicio # Content Panel authorizationDeniedAlert:Autorización Denegada -authorizationDeniedAlertMessageMissingRoles:Do not have permission to do this operation because your user is missing the roles: {0,list} -authorizationDeniedAlertMessageExceptionSimple:You do not have permission to do this operation.
{0} +authorizationDeniedAlertMessageMissingRoles:No tiene permiso para realizar esta operación porque su usuario carece de los siguientes roles: {0,list} +authorizationDeniedAlertMessageExceptionSimple:No tiene permiso para realizar esta operación.
{0} casForwardWarning:Tienes que estar autenticado para acceder a esta página. ¿Se desea autenticar? # Cookies cookiesMessage:Este sitio web utiliza cookies para asegurarse de obtener la mejor experiencia. @@ -1064,10 +1064,10 @@ createUserCreate:GUARDAR # Show User showUserTitle:Usuario showUserStatusLabel:Estado -showUserActivated:Activated -showUserDeactivated:Deactivated -showUserEmptyGroupList:There is no group associated to this user -showUserEmptyPermissions:There is no permission associated to this user +showUserActivated:Activado +showUserDeactivated:Desactivado +showUserEmptyGroupList:No hay ningún grupo asociado a este usuario +showUserEmptyPermissions:No hay ningún permiso asociado a este usuario # Edit User editUserTitle:Editar usuario editUserCancel:Cancelar @@ -1077,7 +1077,7 @@ editUserRemove:Eliminar editUserApply:Guardar editUserAction:Editar # Pre ingest panel -dropFolderInformationText:Did you know you can integrate with production systems for automatic ingest using RODA Enterprise Drop Folders? +dropFolderInformationText:¿Sabía que puede integrar con sistemas de producción para la ingesta automática con RODA Enterprise Drop Folders? # User Data Panel username:Nombre usuario password:Contraseña @@ -1096,9 +1096,9 @@ createGroupTitle:Crear grupo createGroupCreate:GUARDAR createGroupCancel:CANCELAR # Show Group -showGroupTitle:Group -showGroupEmptyUserList:There is no user associated to this group -showGroupEmptyPermissions:There is no permission associated to this group +showGroupTitle:Grupo +showGroupEmptyUserList:No hay ningún usuario asociado a este grupo +showGroupEmptyPermissions:No hay ningún permiso asociado a este grupo # Edit Group editGroupTitle:Editar grupo editGroupCancel:CANCELAR @@ -1116,7 +1116,7 @@ preferencesSubmit:GUARDAR preferencesCancel:CANCELAR # Roles Description role:{0} (no traducido) -role[aip.view]:Retrieve intellectual entities (AIPs) +role[aip.view]:Recuperar entidades intelectuales (AIPs) role[aip.read]:Lista y obtención de entidades intelectuales (AIPs) role[aip.appraisal]:Aceptar o rechazar entidades intelectuales en la evaluación role[aip.create.top]:Crear entidades intelectuales superiores @@ -1124,7 +1124,7 @@ role[aip.create.below]:Crear entidades intelectuales role[aip.update]:Actualizar entidades intelectuales role[aip.delete]:Eliminar entidades intelectuales -role[representation.view]:Retrieve representations and computer files +role[representation.view]:Recuperar representaciones y archivos role[representation.read]:Listar y obtener representaciones y archivos de computadora role[representation.create]:Crear representaciones y archivos de computadora role[representation.update]:Actualizar representaciones y archivos de computadora @@ -1154,30 +1154,30 @@ role[log_entry.create]:Crear entradas de registro role[log_entry.delete]:Eliminar entradas de registro role[risk.read]:Listar y ver los riesgos de preservación role[risk.manage]:Gestionar los riesgos de preservación -role[ri.read]:List and view representation information -role[ri.manage]:Manage representation information +role[ri.read]:Listar y ver información de representación +role[ri.manage]:Gestionar información de representación role[format.read]:Listar y ver la información del formato de archivo role[format.manage]:Administrar la información del formato de archivo role[permission.read]:Leer y consultar sobre los permisos de otros usuarios -role[disposal_rule.read]:List and view disposal rule information -role[disposal_rule.manage]:Manage disposal rule information -role[disposal_schedule.read]:List and view disposal schedule information -role[disposal_schedule.manage]:Manage disposal schedule information -role[disposal_schedule.associate]:Associate or disassociate disposal schedule from intellectual entities (AIPs) -role[disposal_hold.read]:List and view disposal hold information -role[disposal_hold.manage]:Manage disposal hold information -role[disposal_hold.apply]:Apply or lift disposal hold from intellectual entities (AIPs) -role[disposal_confirmation.read]:List and view disposal confirmation information -role[disposal_confirmation.manage]:Manage disposal confirmation information -role[disposal_confirmation.destroy]:Destroy intellectual entities (AIPs) according to the disposal confirmation -role[disposal_confirmation.restore]:Restore destroyed intellectual entities (AIPs) according to the disposal confirmation -role[disposal_confirmation.delete_bin]:Permanently delete destroyed intellectual entities (AIPs) according to the disposal confirmation -role[distributed_instances.read]:List and view distributed instances -role[distributed_instances.manage]:Manage distributed instances -role[local_instance_configuration.read]:View local instance configuration -role[local_instance_configuration.manage]:Manage local instance configuration -role[access_key.read]:List and view access keys for users and distributed instances -role[access_key.manage]:Manage access keys for users and distributed instances +role[disposal_rule.read]:Listar y ver información de reglas de eliminación +role[disposal_rule.manage]:Gestionar información de reglas de eliminación +role[disposal_schedule.read]:Listar y ver información de calendarios de eliminación +role[disposal_schedule.manage]:Gestionar información de calendarios de eliminación +role[disposal_schedule.associate]:Asociar o desasociar calendarios de eliminación de entidades intelectuales (AIPs) +role[disposal_hold.read]:Listar y ver información de bloqueos de eliminación +role[disposal_hold.manage]:Gestionar información de bloqueos de eliminación +role[disposal_hold.apply]:Aplicar o levantar bloqueos de eliminación de entidades intelectuales (AIPs) +role[disposal_confirmation.read]:Listar y ver información de confirmaciones de eliminación +role[disposal_confirmation.manage]:Gestionar información de confirmaciones de eliminación +role[disposal_confirmation.destroy]:Destruir entidades intelectuales (AIPs) según la confirmación de eliminación +role[disposal_confirmation.restore]:Restaurar entidades intelectuales (AIPs) destruidas según la confirmación de eliminación +role[disposal_confirmation.delete_bin]:Eliminar permanentemente entidades intelectuales (AIPs) destruidas según la confirmación de eliminación +role[distributed_instances.read]:Listar y ver instancias distribuidas +role[distributed_instances.manage]:Gestionar instancias distribuidas +role[local_instance_configuration.read]:Ver configuración de instancia local +role[local_instance_configuration.manage]:Gestionar configuración de instancia local +role[access_key.read]:Listar y ver tokens de acceso de usuarios e instancias distribuidas +role[access_key.manage]:Gestionar tokens de acceso de usuarios e instancias distribuidas # Register registerUserDataTitle:Regístrate registerSubmit:REGISTRATE @@ -1204,22 +1204,22 @@ verifyEmailSuccessDialogMessage:La dirección se ha verificado correctamente. verifyEmailSuccessDialogButton:De acuerdo # Recover Login recoverLoginTitle:Recuperar usuario -recoverLoginEmail:E-mail address +recoverLoginEmail:Dirección de correo electrónico recoverLoginSubmit:RECUPERAR recoverLoginCancel:Cancelar recoverLoginCaptchaFailed:Fallado en la prueba de diferenciación entre seres humanos y computadoras, por favor, inténtelo de nuevo. recoverLoginSuccessDialogTitle:Recuperar registro -recoverLoginSuccessDialogMessage:If this username or email belongs to an existing user, an email will be sent to recover the password. +recoverLoginSuccessDialogMessage:Si este nombre de usuario o correo electrónico pertenece a un usuario existente, se enviará un correo para recuperar la contraseña. recoverLoginSuccessDialogButton:De acuerdo # Reset Password resetPasswordTitle:Borra clave -setPasswordTitle:Set password +setPasswordTitle:Establecer contraseña resetPasswordUsername:Nombre de usuario resetPasswordToken:Clave de verificación resetPasswordNewPassword:Nueva clave resetPasswordRepeatPassword:Repetir clave resetPasswordSubmit:CAMBIO -setPasswordSubmit:SET +setPasswordSubmit:ESTABLECER resetPasswordCancel:Cancelar resetPasswordInvalidToken:La contraseña de recuperación no es válida. Por favor, compruebe si ha introducido la contraseña correcta o pedir uno nuevo. resetPasswordNoSuchUser:El nombre de usuario no existe. Por favor, compruebe si ha introducido la correcta. @@ -1244,19 +1244,19 @@ pluginStateMessage[FAILURE]:Fracaso pluginStateMessage[RUNNING]:Corriendo pluginStateMessage[PARTIAL_SUCCESS]:Éxito parcial pluginStateMessage[SKIPPED]:Omitida -pluginNotInstalledLabel:Not installed -pluginLicenseLabel:Package specifications -pluginDocumentationLabel:Documentation +pluginNotInstalledLabel:No instalado +pluginLicenseLabel:Especificaciones del paquete +pluginDocumentationLabel:Documentación pluginHomepageLabel:Inicio pluginLicenseStatus:{0} -pluginLicenseStatus[INTERNAL]:Built-in -pluginLicenseStatus[VERIFIED]:Verified -pluginLicenseStatus[NOT_VERIFIED]:Not verified -pluginNotInstalledMessage:Please select the "Install" button for more information on how to install this action. -pluginInternalMessage:This action is part of RODA. -pluginTrustedMessage:Developed by {0} and licensed to {1} -pluginUntrustedMessage:Publisher is unknown or it cannot be verified. For security reasons this action cannot be run. -pluginVendorLabel:Vendor: +pluginLicenseStatus[INTERNAL]:Integrado +pluginLicenseStatus[VERIFIED]:Verificado +pluginLicenseStatus[NOT_VERIFIED]:No verificado +pluginNotInstalledMessage:Seleccione el botón «Instalar» para obtener más información sobre cómo instalar esta acción. +pluginInternalMessage:Esta acción forma parte de RODA. +pluginTrustedMessage:Desarrollado por {0} y con licencia para {1} +pluginUntrustedMessage:El editor es desconocido o no se puede verificar. Por razones de seguridad, esta acción no se puede ejecutar. +pluginVendorLabel:Proveedor: notificationStateValue:{0} notificationStateValue[CREATED]:Creado notificationStateValue[COMPLETED]:Éxito @@ -1292,30 +1292,30 @@ versionAction[metadata_type_forced]:Forzar tipo de metadatos versionAction[update_from_sip]:Actualización del SIP versionActionBy:{0} por {1} mandatoryField:Campo obligatorio -numberIsRequired:Number is required +numberIsRequired:Se requiere un número isAMandatoryField:{0} es uno campo obligatorio passwordIsTooSmall:La contraseña es demasiado pequeña passwordDoesNotMatchConfirmation:La contraseña y la confirmación no coinciden -emailNotValid:E-mail is not valid +emailNotValid:El correo electrónico no es válido wrongMailFormat:E-Mail con formato errado fileAlreadyExists:Archivo ya existe tableDownloadCSV:Exportar -tableUpdateOn:Updating every 10 seconds, click to pause. -tableUpdating:Updating... -tableUpdatePause:Paused, click to resume auto-update. -tableUpdateErrorConnection:Could not connect to server. Click to retry. +tableUpdateOn:Actualizando cada 10 segundos, haga clic para pausar. +tableUpdating:Actualizando... +tableUpdatePause:En pausa, haga clic para reanudar la actualización automática. +tableUpdateErrorConnection:No se pudo conectar al servidor. Haga clic para reintentar. actionableEmptyHelp:Por favor, seleccione los elementos mediante las casillas de verificación actionableEmptyHelp[NONE]:Por favor, seleccione los elementos mediante las casillas de verificación actionableEmptyHelp[SINGLE]:No hay acciones disponibles para el ítem seleccionado actionableEmptyHelp[MULTIPLE]:No hay acciones disponibles para los ítems seleccionados showMore:Ver más showLess:Ver menos -uriLinkingIdentifierTitle:External URI -browseFileDipEmpty:No entries -browseFileDipDelete:Delete dissemination -browseFileDipOpenedExternalURL:Opened dissemination in another page -browseFileDipRepresentationConfirmTitle:Confirm dissemination remove -browseFileDipRepresentationConfirmMessage:Are you sure you want to remove this dissemination? +uriLinkingIdentifierTitle:URI externo +browseFileDipEmpty:Sin entradas +browseFileDipDelete:Eliminar diseminación +browseFileDipOpenedExternalURL:Diseminación abierta en otra página +browseFileDipRepresentationConfirmTitle:Confirmar eliminación de diseminación +browseFileDipRepresentationConfirmMessage:¿Está seguro de que desea eliminar esta diseminación? jobReportSource:Fuente jobReportSource[org.roda.core.data.v2.ip.AIP]:Entidade intelectual fuente jobReportSource[org.roda.core.data.v2.ip.Representation]:Representacion fuente @@ -1329,7 +1329,7 @@ jobReportSource[org.roda.core.data.v2.ri.RepresentationInformation]:Información jobReportSource[org.roda.core.data.v2.jobs.Job]:Proceso fuente jobReportSource[org.roda.core.data.v2.jobs.Report]:Reporte fuente jobReportSource[org.roda.core.data.v2.notifications.Notification]:Notificacion fuente -jobReportSource[org.roda.core.data.v2.log.LogEntry]:Source audit log +jobReportSource[org.roda.core.data.v2.log.LogEntry]:Registro de auditoría fuente jobReportSource[org.roda.core.data.v2.user.RODAMember]:Miembro fuente jobReportSource[org.roda.core.data.v2.ip.metadata.DescriptiveMetadata]:Metadatos descriptivos fuente jobReportSource[org.roda.core.data.v2.ip.metadata.PreservationMetadata]:Metadatos de preservación fuente @@ -1347,388 +1347,388 @@ jobReportOutcome[org.roda.core.data.v2.ri.RepresentationInformation]:Informació jobReportOutcome[org.roda.core.data.v2.jobs.Job]:Proceso resultado jobReportOutcome[org.roda.core.data.v2.jobs.Report]:Reporte resultado jobReportOutcome[org.roda.core.data.v2.notifications.Notification]:Notificacion resultado -jobReportOutcome[org.roda.core.data.v2.log.LogEntry]:Outcome audit log +jobReportOutcome[org.roda.core.data.v2.log.LogEntry]:Registro de auditoría resultado jobReportOutcome[org.roda.core.data.v2.user.RODAMember]:Miembro resultado jobReportOutcome[org.roda.core.data.v2.ip.metadata.DescriptiveMetadata]:Metadatos descriptivos resultado jobReportOutcome[org.roda.core.data.v2.ip.metadata.PreservationMetadata]:Metadatos de preservación resultado jobReportOutcome[org.roda.core.data.v2.formats.Format]:Formato resultado -sourceObjectList:A manually selected list with {0} {1} +sourceObjectList:Una lista seleccionada manualmente con {0} {1} runningInBackgroundTitle:La acción se está ejecutando en último plano -runningInBackgroundDescription:The progress of the action can be monitored on the internal actions page -exportListTitle:Exporting list -exportListMessage:Exporting list with a maximum limit of {0} items +runningInBackgroundDescription:El progreso de la acción puede monitorizarse en la página de acciones internas +exportListTitle:Exportando lista +exportListMessage:Exportando lista con un límite máximo de {0} elementos representationInformationAssociatedWith: -representationInformationAssociatedWith[AIP]:Associated with intellectual entities where field {0} is {1} -representationInformationAssociatedWith[Representation]:Associated with representations where field {0} is {1} -representationInformationAssociatedWith[File]:Associated with files where field {0} is {1} -representationInformationAssociatedWithDescription:This page presents information that maps {2} where field {0} is {1} into more meaningful concepts, including structure information about how the information is organized, semantic information that further describes the meaning of encoded information, and other information useful for the understanding of the content. Each record of representation information can be connected to other representation information, intellectual entities and external resources (e.g. Web pages or books) creating a representation network that aims to fully describe the meaning of the digital object. -representationInformationAssociatedWithDescription[AIP]:This page presents information that maps intellectual entities where field {0} is {1} into more meaningful concepts, including structure information about how the information is organized, semantic information that further describes the meaning of encoded information, and other information useful for the understanding of the content. Each record of representation information can be connected to other representation information, intellectual entities and external resources (e.g. Web pages or books) creating a representation network that aims to fully describe the meaning of the digital object. -representationInformationAssociatedWithDescription[Representation]:This page presents information that maps representations where field {0} is {1} into more meaningful concepts, including structure information about how the information is organized, semantic information that further describes the meaning of encoded information, and other information useful for the understanding of the content. Each record of representation information can be connected to other representation information, intellectual entities and external resources (e.g. Web pages or books) creating a representation network that aims to fully describe the meaning of the digital object. -representationInformationAssociatedWithDescription[File]:This page presents information that maps files where field {0} is {1} into more meaningful concepts, including structure information about how the information is organized, semantic information that further describes the meaning of encoded information, and other information useful for the understanding of the content. Each record of representation information can be connected to other representation information, intellectual entities and external resources (e.g. Web pages or books) creating a representation network that aims to fully describe the meaning of the digital object. +representationInformationAssociatedWith[AIP]:Asociado con entidades intelectuales donde el campo {0} es {1} +representationInformationAssociatedWith[Representation]:Asociado con representaciones donde el campo {0} es {1} +representationInformationAssociatedWith[File]:Asociado con archivos donde el campo {0} es {1} +representationInformationAssociatedWithDescription:Esta página presenta información que mapea {2} donde el campo {0} es {1} en conceptos más significativos, incluyendo información de estructura sobre cómo está organizada la información, información semántica que describe el significado de la información codificada, y otra información útil para comprender el contenido. Cada registro de información de representación puede conectarse a otra información de representación, entidades intelectuales y recursos externos (p. ej. páginas web o libros) creando una red de representación que busca describir completamente el significado del objeto digital. +representationInformationAssociatedWithDescription[AIP]:Esta página presenta información que mapea entidades intelectuales donde el campo {0} es {1} en conceptos más significativos, incluyendo información de estructura sobre cómo está organizada la información, información semántica que describe el significado de la información codificada, y otra información útil para comprender el contenido. Cada registro de información de representación puede conectarse a otra información de representación, entidades intelectuales y recursos externos (p. ej. páginas web o libros) creando una red de representación que busca describir completamente el significado del objeto digital. +representationInformationAssociatedWithDescription[Representation]:Esta página presenta información que mapea representaciones donde el campo {0} es {1} en conceptos más significativos, incluyendo información de estructura sobre cómo está organizada la información, información semántica que describe el significado de la información codificada, y otra información útil para comprender el contenido. Cada registro de información de representación puede conectarse a otra información de representación, entidades intelectuales y recursos externos (p. ej. páginas web o libros) creando una red de representación que busca describir completamente el significado del objeto digital. +representationInformationAssociatedWithDescription[File]:Esta página presenta información que mapea archivos donde el campo {0} es {1} en conceptos más significativos, incluyendo información de estructura sobre cómo está organizada la información, información semántica que describe el significado de la información codificada, y otra información útil para comprender el contenido. Cada registro de información de representación puede conectarse a otra información de representación, entidades intelectuales y recursos externos (p. ej. páginas web o libros) creando una red de representación que busca describir completamente el significado del objeto digital. representationInformationNameFromAssociation: -representationInformationNameFromAssociation[AIP]:Intellectual entities where field {0} is {1} -representationInformationNameFromAssociation[Representation]:Representations where field {0} is {1} -representationInformationNameFromAssociation[File]:Files where field {0} is {1} -insertImageUrl:Insert a image URL -insertLinkUrl:Insert a URL -editHTMLContent:Edit content via HTML -searchButtonAdvancedSearch:advanced +representationInformationNameFromAssociation[AIP]:Entidades intelectuales donde el campo {0} es {1} +representationInformationNameFromAssociation[Representation]:Representaciones donde el campo {0} es {1} +representationInformationNameFromAssociation[File]:Archivos donde el campo {0} es {1} +insertImageUrl:Insertar una URL de imagen +insertLinkUrl:Insertar una URL +editHTMLContent:Editar contenido mediante HTML +searchButtonAdvancedSearch:avanzado details:Detalles -selectAllPages:Select all pages -selectThisPage:Select this page -dateRangeFieldFrom:from -dateRangeFieldTo:to -inputStorageSizeList:Units -removableTextBox:Removable text box +selectAllPages:Seleccionar todas las páginas +selectThisPage:Seleccionar esta página +dateRangeFieldFrom:desde +dateRangeFieldTo:hasta +inputStorageSizeList:Unidades +removableTextBox:Campo de texto eliminable defaultColumnHeader:{0} -defaultColumnHeader[default_IndexedAIP_level]: Level -defaultColumnHeader[default_IndexedAIP_hasrepresentations]: +defaultColumnHeader[default_IndexedAIP_level]: Nivel +defaultColumnHeader[default_IndexedAIP_hasrepresentations]: defaultColumnHeader[default_IndexedFile_icon]: #Disposal -disposalPolicyTitle:Disposal policies -disposalRulesTitle:Disposal rules -disposalSchedulesTitle:Disposal schedules -disposalHoldsTitle:Disposal holds -disposalDestroyedRecordsTitle:Destroyed records -disposalConfirmationsTitle:Disposal confirmations -createDisposalConfirmationTitle:Create disposal confirmation -createDisposalConfirmationExtraInformationTitle:Disposal confirmation metadata information +disposalPolicyTitle:Políticas de eliminación +disposalRulesTitle:Reglas de eliminación +disposalSchedulesTitle:Calendarios de eliminación +disposalHoldsTitle:Bloqueos de eliminación +disposalDestroyedRecordsTitle:Registros destruidos +disposalConfirmationsTitle:Confirmaciones de eliminación +createDisposalConfirmationTitle:Crear confirmación de eliminación +createDisposalConfirmationExtraInformationTitle:Información de metadatos de la confirmación de eliminación disposalScheduleTitle:Titulo disposalScheduleIdentifier:Identificador disposalScheduleDescription:Descripción -disposalScheduleMandate:Mandate -disposalScheduleNotes:Scope notes -disposalSchedulePeriod:Period +disposalScheduleMandate:Mandato +disposalScheduleNotes:Notas de alcance +disposalSchedulePeriod:Período disposalScheduleActionCol:Acción disposalScheduleAction:{0} -disposalScheduleAction[DESTROY]:Destroy -disposalScheduleAction[REVIEW]:Review -disposalScheduleAction[RETAIN_PERMANENTLY]:Retain permanently -disposalScheduleRetentionTriggerElementId:Retention trigger element identifier -disposalScheduleRetentionPeriodInterval:Retention period interval +disposalScheduleAction[DESTROY]:Destruir +disposalScheduleAction[REVIEW]:Revisión +disposalScheduleAction[RETAIN_PERMANENTLY]:Conservar permanentemente +disposalScheduleRetentionTriggerElementId:Identificador del elemento de activación de retención +disposalScheduleRetentionPeriodInterval:Intervalo del período de retención disposalScheduleRetentionPeriodIntervalValue:{0} -disposalScheduleRetentionPeriodIntervalValue[NO_RETENTION_PERIOD]:No retention period -disposalScheduleRetentionPeriodIntervalValue[DAYS]:Days -disposalScheduleRetentionPeriodIntervalValue[WEEKS]:Weeks -disposalScheduleRetentionPeriodIntervalValue[MONTHS]:Months -disposalScheduleRetentionPeriodIntervalValue[YEARS]:Years -disposalScheduleRetentionPeriodDuration:Retention period -disposalScheduleStateCol:State +disposalScheduleRetentionPeriodIntervalValue[NO_RETENTION_PERIOD]:Sin período de retención +disposalScheduleRetentionPeriodIntervalValue[DAYS]:Días +disposalScheduleRetentionPeriodIntervalValue[WEEKS]:Semanas +disposalScheduleRetentionPeriodIntervalValue[MONTHS]:Meses +disposalScheduleRetentionPeriodIntervalValue[YEARS]:Años +disposalScheduleRetentionPeriodDuration:Período de retención +disposalScheduleStateCol:Estado disposalScheduleState:{0} disposalScheduleState[ACTIVE]:Activo -disposalScheduleState[INACTIVE]:Inactive -disposalScheduleUsedInRule:Is being used in a disposal rule +disposalScheduleState[INACTIVE]:Inactivo +disposalScheduleUsedInRule:Está siendo usado en una regla de eliminación disposalHoldTitle:Titulo disposalHoldIdentifier:Identificador disposalHoldDescription:Descripción -disposalHoldMandate:Mandate -disposalHoldNotes:Scope notes -disposalHoldStateCol:State +disposalHoldMandate:Mandato +disposalHoldNotes:Notas de alcance +disposalHoldStateCol:Estado disposalHoldState:{0} disposalHoldState[ACTIVE]:Activo -disposalHoldState[LIFTED]:Lifted -newDisposalRuleTitle:Create disposal rule -newDisposalScheduleTitle:Create disposal schedule -newDisposalHoldTitle:Create disposal hold -editDisposalRuleTitle:Edit disposal rule -editDisposalScheduleTitle:Edit disposal schedule -editDisposalHoldTitle:Edit disposal hold -editRules:Prioritize rules -applyRules:Apply rules +disposalHoldState[LIFTED]:Levantado +newDisposalRuleTitle:Crear regla de eliminación +newDisposalScheduleTitle:Crear calendario de eliminación +newDisposalHoldTitle:Crear bloqueo de eliminación +editDisposalRuleTitle:Editar regla de eliminación +editDisposalScheduleTitle:Editar calendario de eliminación +editDisposalHoldTitle:Editar bloqueo de eliminación +editRules:Priorizar reglas +applyRules:Aplicar reglas disposalConfirmationTitle:Titulo disposalConfirmationCreationDate:Fecha de creación disposalConfirmationCreationBy:Creador disposalConfirmationStatus:Estado disposalConfirmationAIPs:# AIP -disposalConfirmationSize:Storage size +disposalConfirmationSize:Tamaño de almacenamiento disposalConfirmationState:{0} -disposalConfirmationState[APPROVED]:Approved -disposalConfirmationState[PENDING]:Pending -disposalConfirmationState[RESTORED]:Restored +disposalConfirmationState[APPROVED]:Aprobado +disposalConfirmationState[PENDING]:Pendiente +disposalConfirmationState[RESTORED]:Restaurado disposalConfirmationState[PERMANENTLY_DELETED]:Eliminado -disposalConfirmationState[EXECUTION_FAILED]:Execution Failed -createDisposalRuleFailure:Could not create disposal rule due to an error: {0}. -createDisposalHoldAlreadyExists:There is already a disposal hold with the title {0}, please choose another title. -createDisposalHoldFailure:Could not create disposal hold due to an error: {0}. -showDisposalRuleTitle:Disposal rule -showDisposalHoldTitle:Disposal hold -showDisposalScheduleTitle:Disposal Schedule -applyDisposalScheduleButton:Destroy +disposalConfirmationState[EXECUTION_FAILED]:Ejecución fallida +createDisposalRuleFailure:No se pudo crear la regla de eliminación debido a un error: {0}. +createDisposalHoldAlreadyExists:Ya existe un bloqueo de eliminación con el título {0}. Por favor, elija otro título. +createDisposalHoldFailure:No se pudo crear el bloqueo de eliminación debido a un error: {0}. +showDisposalRuleTitle:Regla de eliminación +showDisposalHoldTitle:Bloqueo de eliminación +showDisposalScheduleTitle:Calendario de eliminación +applyDisposalScheduleButton:Destruir deleteDisposalConfirmationReport:Eliminar -permanentlyDeleteFromBinButton:Permanently delete -reExecuteDisposalDestroyActionButton:Re-execute destruction -recoverDisposalConfirmationExecutionFailedButton:Recover AIPs -restoreFromBinButton:Restore -newDisposalConfirmationButton:New confirmation -disassociateDisposalScheduleButton:Disassociate schedule -disassociateDisposalHoldButton:Disassociate hold -associateDisposalScheduleButton:Associate disposal schedule -createDisposalScheduleButton:Create schedule -disposalScheduleSelectionDialogTitle:Change disposal schedule -changeDisposalScheduleActionTitle:Change disposal schedule -createDisposalConfirmationActionTitle:Create disposal confirmation -dissociateDisposalScheduleDialogTitle:Confirm schedule disassociation of intellectual entities -dissociateDisposalScheduleDialogMessage:Are you sure you want to disassociate the schedule for the selected {0} items? -createDisposalConfirmationReportDialogTitle:Confirming intellectual entities to be destroyed -createDisposalConfirmationReportDialogMessage:Are you sure you want to create a disposal confirmation for the selected {0} items? -associateDisposalScheduleDialogTitle:Confirm schedule association of intellectual entities -associateDisposalScheduleDialogMessage:Are you sure you want to associate the schedule for the selected {0} items? -disposalRetentionStartDateLabel:Retention start date -disposalRetentionDueDateLabel:Retention due date -disposalRetentionPeriodLabel:Retention period -disposalActionLabel:Disposal action -holdStatusLabel:Hold status -disposalOnHoldStatusLabel:On hold -disposalClearStatusLabel:Not on hold -disposalScheduleListAips:Records with this schedule -disposalHoldListAips:Records with this hold -deleteConfirmationReportDialogTitle:Confirm intellectual entities deletion -deleteConfirmationReportDialogMessage:Are you sure you want to withdraw the intellectual entities from this disposal confirmation? -deleteConfirmationReportSuccessTitle:Deleting disposal confirmation -deleteConfirmationReportSuccessMessage:Execute action to delete disposal confirmation +permanentlyDeleteFromBinButton:Eliminar permanentemente +reExecuteDisposalDestroyActionButton:Reejecutar destrucción +recoverDisposalConfirmationExecutionFailedButton:Recuperar AIPs +restoreFromBinButton:Restaurar +newDisposalConfirmationButton:Nueva confirmación +disassociateDisposalScheduleButton:Desasociar calendario +disassociateDisposalHoldButton:Desasociar bloqueo +associateDisposalScheduleButton:Asociar calendario de eliminación +createDisposalScheduleButton:Crear calendario +disposalScheduleSelectionDialogTitle:Cambiar calendario de eliminación +changeDisposalScheduleActionTitle:Cambiar calendario de eliminación +createDisposalConfirmationActionTitle:Crear confirmación de eliminación +dissociateDisposalScheduleDialogTitle:Confirmar disociación de calendario de entidades intelectuales +dissociateDisposalScheduleDialogMessage:¿Está seguro de que desea disociar el calendario para los {0} elementos seleccionados? +createDisposalConfirmationReportDialogTitle:Confirmando entidades intelectuales a destruir +createDisposalConfirmationReportDialogMessage:¿Está seguro de que desea crear una confirmación de eliminación para los {0} elementos seleccionados? +associateDisposalScheduleDialogTitle:Confirmar asociación de calendario de entidades intelectuales +associateDisposalScheduleDialogMessage:¿Está seguro de que desea asociar el calendario para los {0} elementos seleccionados? +disposalRetentionStartDateLabel:Fecha de inicio de retención +disposalRetentionDueDateLabel:Fecha de vencimiento de retención +disposalRetentionPeriodLabel:Período de retención +disposalActionLabel:Acción de eliminación +holdStatusLabel:Estado de bloqueo +disposalOnHoldStatusLabel:En retención +disposalClearStatusLabel:Sin retención +disposalScheduleListAips:Registros con este calendario +disposalHoldListAips:Registros con este bloqueo +deleteConfirmationReportDialogTitle:Confirmar eliminación de entidades intelectuales +deleteConfirmationReportDialogMessage:¿Está seguro de que desea retirar las entidades intelectuales de esta confirmación de eliminación? +deleteConfirmationReportSuccessTitle:Eliminando confirmación de eliminación +deleteConfirmationReportSuccessMessage:Ejecutar acción para eliminar confirmación de eliminación disposalConfirmationDataPanelTitle:Titulo -disposalConfirmationDataNote:(*) Required fields +disposalConfirmationDataNote:(*) Campos obligatorios disposalRuleTitle:Titulo disposalRuleIdentifier:Identificador disposalRuleDescription:Descripción -disposalRuleScheduleName:Schedule -disposalRuleType:Selection method +disposalRuleScheduleName:Calendario +disposalRuleType:Método de selección disposalRuleOrder:# -disposalRuleCondition:Condition -disposalRuleConditionOperator:is +disposalRuleCondition:Condición +disposalRuleConditionOperator:es disposalRuleTypeValue:{0} -disposalRuleTypeValue[IS_CHILD_OF]:Child of -disposalRuleTypeValue[METADATA_FIELD]:Metadata field -disposalRulePreviewAIPListTitle:Preview records that match the selection method criteria -disposalRulePreviewHelpText:Select a selection method and add the criteria. Click Preview to check which intellectual entities will be affected by this disposal rule. -disposalRulePreviewButtonText:Preview -disposalTitle:Disposal -conditionActualParent:Actual parent -editRulesOrder:Edit rules order -confirmChangeRulesOrder:Are you sure you want to change the order of the disposal rules? -deleteDisposalRuleDialogTitle:Remove disposal rule -deleteDisposalRuleDialogMessage:Are you sure you want to remove the disposal rule {0}? -associateDisposalHoldButton:Associate disposal hold -disposalHoldSelectionDialogTitle:Associate disposal hold -applyDisposalHoldDialogTitle:Confirm disposal hold association to intellectual entity -applyDisposalHoldDialogMessage[one]:Are you sure you want to apply the disposal hold to the selected item? -applyDisposalHoldDialogMessage:Are you sure you want to apply the disposal hold to the selected {0, number} items? -applyDisposalHoldButton:Associate disposal hold -createDisposalHoldButton:Create disposal hold -clearDisposalHoldButton:Clear disposal holds -overrideDisposalHoldButton:Override disposal hold -clearDisposalHoldDialogTitle:Clear disposal holds -clearDisposalHoldDialogMessage[one]:Are you sure you want to disassociate all disposal holds from the selected item? -clearDisposalHoldDialogMessage:Are you sure you want to disassociate all disposal holds from the selected {0, number} items? -liftDisposalHoldDialogTitle:Lift disposal hold -liftDisposalHoldDialogMessage[\=1]:Are you sure you want to lift the disposal hold? -liftDisposalHoldDialogMessage[one]:Are you sure you want to lift the disposal hold? -liftDisposalHoldDialogMessage:Are you sure you want to lift the {0, number} selected disposal holds? -disassociateDisposalHoldDialogTitle:Disassociate disposal hold -disassociateDisposalHoldDialogMessage[one]:Are you sure you want to disassociate the disposal hold from the selected item? -disassociateDisposalHoldDialogMessage:Are you sure you want to disassociate the disposal hold from the selected {0, number} items? -disassociateDisposalScheduleDialogTitle:Disassociate disposal schedule -disassociateDisposalScheduleDialogMessage[one]:Are you sure you want to disassociate the disposal schedule from the selected item? -disassociateDisposalScheduleDialogMessage:Are you sure you want to disassociate the disposal schedule from the selected {0, number} items? -disposalHoldAssociatedOn:Associated on -disposalHoldAssociatedBy:Associated by -disposalHoldAssociatedFrom:Associated from +disposalRuleTypeValue[IS_CHILD_OF]:Elemento subordinado de +disposalRuleTypeValue[METADATA_FIELD]:Campo de metadatos +disposalRulePreviewAIPListTitle:Vista previa de registros que coinciden con los criterios del método de selección +disposalRulePreviewHelpText:Seleccione un método de selección y añada los criterios. Haga clic en Vista previa para comprobar qué entidades intelectuales se verán afectadas por esta regla de eliminación. +disposalRulePreviewButtonText:Vista previa +disposalTitle:Eliminación +conditionActualParent:Elemento padre actual +editRulesOrder:Editar orden de reglas +confirmChangeRulesOrder:¿Está seguro de que desea cambiar el orden de las reglas de eliminación? +deleteDisposalRuleDialogTitle:Eliminar regla de eliminación +deleteDisposalRuleDialogMessage:¿Está seguro de que desea eliminar la regla de eliminación {0}? +associateDisposalHoldButton:Asociar bloqueo de eliminación +disposalHoldSelectionDialogTitle:Asociar bloqueo de eliminación +applyDisposalHoldDialogTitle:Confirmar asociación de bloqueo de eliminación a entidad intelectual +applyDisposalHoldDialogMessage[one]:¿Está seguro de que desea aplicar el bloqueo de eliminación al elemento seleccionado? +applyDisposalHoldDialogMessage:¿Está seguro de que desea aplicar el bloqueo de eliminación a los {0, number} elementos seleccionados? +applyDisposalHoldButton:Asociar bloqueo de eliminación +createDisposalHoldButton:Crear bloqueo de eliminación +clearDisposalHoldButton:Eliminar bloqueos de eliminación +overrideDisposalHoldButton:Anular bloqueo de eliminación +clearDisposalHoldDialogTitle:Eliminar bloqueos de eliminación +clearDisposalHoldDialogMessage[one]:¿Está seguro de que desea desasociar todos los bloqueos de eliminación del elemento seleccionado? +clearDisposalHoldDialogMessage:¿Está seguro de que desea desasociar todos los bloqueos de eliminación de los {0, number} elementos seleccionados? +liftDisposalHoldDialogTitle:Levantar bloqueo de eliminación +liftDisposalHoldDialogMessage[\=1]:¿Está seguro de que desea levantar el bloqueo de eliminación? +liftDisposalHoldDialogMessage[one]:¿Está seguro de que desea levantar el bloqueo de eliminación? +liftDisposalHoldDialogMessage:¿Está seguro de que desea levantar los {0, number} bloqueos de eliminación seleccionados? +disassociateDisposalHoldDialogTitle:Desasociar bloqueo de eliminación +disassociateDisposalHoldDialogMessage[one]:¿Está seguro de que desea desasociar el bloqueo de eliminación del elemento seleccionado? +disassociateDisposalHoldDialogMessage:¿Está seguro de que desea desasociar el bloqueo de eliminación de los {0, number} elementos seleccionados? +disassociateDisposalScheduleDialogTitle:Desasociar calendario de eliminación +disassociateDisposalScheduleDialogMessage[one]:¿Está seguro de que desea desasociar el calendario de eliminación del elemento seleccionado? +disassociateDisposalScheduleDialogMessage:¿Está seguro de que desea desasociar el calendario de eliminación de los {0, number} elementos seleccionados? +disposalHoldAssociatedOn:Asociado el +disposalHoldAssociatedBy:Asociado por +disposalHoldAssociatedFrom:Asociado desde disposalHoldAssociatedFromValue[\=1]:{0} AIP disposalHoldAssociatedFromValue[one]:{0} AIP disposalHoldAssociatedFromValue:{0} AIPs -disposalScheduleAssociationInformationTitle:Retention period -disposalScheduleAssociationTitle:Disposal schedule -disposalConfirmationAssociationInformationTitle:Disposal confirmation -disposalConfirmationAssociationTitle:Disposal confirmation -disposalHoldsAssociationInformationTitle:Disposal holds -transitiveDisposalHoldsAssociationInformationTitle:Transitive disposal holds assigned through other intellectual entity +disposalScheduleAssociationInformationTitle:Período de retención +disposalScheduleAssociationTitle:Calendario de eliminación +disposalConfirmationAssociationInformationTitle:Confirmación de eliminación +disposalConfirmationAssociationTitle:Confirmación de eliminación +disposalHoldsAssociationInformationTitle:Bloqueos de eliminación +transitiveDisposalHoldsAssociationInformationTitle:Bloqueos de eliminación transitivos asignados a través de otra entidad intelectual disposalScheduleActionCode:{0} -disposalScheduleActionCode[RETAIN_PERMANENTLY]:Retain permanently -disposalScheduleActionCode[REVIEW]:Review -disposalScheduleActionCode[DESTROY]:Destroy -disposalPolicyScheduleSummary:Due for {0} {1} -disposalPolicyHoldSummary:On hold -disposalPolicyConfirmationSummary:Assigned to a disposal confirmation +disposalScheduleActionCode[RETAIN_PERMANENTLY]:Conservar permanentemente +disposalScheduleActionCode[REVIEW]:Revisión +disposalScheduleActionCode[DESTROY]:Destruir +disposalPolicyScheduleSummary:Vence para {0} {1} +disposalPolicyHoldSummary:En retención +disposalPolicyConfirmationSummary:Asignado a una confirmación de eliminación disposalPolicyActionSummary:{0} -disposalPolicyActionSummary[REVIEW]:revision -disposalPolicyActionSummary[DESTROY]:destruction -disposalPolicyScheduleMonthSummary[one]:in 1 month -disposalPolicyScheduleMonthSummary:in {0,number} months -disposalPolicyScheduleYearSummary[one]:in 1 year -disposalPolicyScheduleYearSummary:in {0,number} years -disposalPolicyScheduleDaySummary[\=0]:today -disposalPolicyScheduleDaySummary[one]:tomorrow -disposalPolicyScheduleDaySummary:in {0,number} days -disposalPolicySummaryReady:Overdue for {0} +disposalPolicyActionSummary[REVIEW]:revisión +disposalPolicyActionSummary[DESTROY]:destrucción +disposalPolicyScheduleMonthSummary[one]:en 1 mes +disposalPolicyScheduleMonthSummary:en {0,number} meses +disposalPolicyScheduleYearSummary[one]:en 1 año +disposalPolicyScheduleYearSummary:en {0,number} años +disposalPolicyScheduleDaySummary[\=0]:hoy +disposalPolicyScheduleDaySummary[one]:mañana +disposalPolicyScheduleDaySummary:en {0,number} días +disposalPolicySummaryReady:Vencido para {0} permanentlyRetained:Nunca -disposalPolicyRetainPermanently:Permanently retained -disposalPolicyDestroyedAIPSummary:Destroyed on {0} +disposalPolicyRetainPermanently:Conservado permanentemente +disposalPolicyDestroyedAIPSummary:Destruido el {0} retentionPeriod:{0} retentionPeriod[\=1|DAYS]:1 día retentionPeriod[one|DAYS]:1 día -retentionPeriod[other|DAYS]:{0,number} days -retentionPeriod[\=1|WEEKS]:1 week -retentionPeriod[one|WEEKS]:1 week -retentionPeriod[other|WEEKS]:{0,number} weeks -retentionPeriod[\=1|MONTHS]:1 month -retentionPeriod[one|MONTHS]:1 month -retentionPeriod[other|MONTHS]:{0,number} months -retentionPeriod[\=1|YEARS]:1 year -retentionPeriod[one|YEARS]:1 year -retentionPeriod[other|YEARS]:{0,number} years -retentionPeriod[other|NO_RETENTION_PERIOD]:No retention period -permanentlyDeleteConfirmDialogTitle:Permanently delete records from disposal bin -permanentlyDeleteConfirmDialogMessage:Are you sure you want to permanently delete the records from disposal bin? This action can not be undone once executed. -restoreDestroyedRecordsConfirmDialogTitle:Restore destroyed records from disposal bin -restoreDestroyedRecordsConfirmDialogMessage:Are you sure you want to restore the records from disposal bin? -restoreDestroyedRecordsSuccessTitle:Restoring records from disposal bin -restoreDestroyedRecordsSuccessMessage:Execute action to restore records -recoverDestroyedRecordsConfirmDialogTitle:Recover destroyed records from a failed execution -recoverDestroyedRecordsConfirmDialogMessage:Are you sure you want to recover the records from this disposal confirmation? -recoverDestroyedRecordsSuccessTitle:Recovering records from a failed execution -recoverDestroyedRecordsSuccessMessage:Execute action to recover records -permanentlyDeleteRecordsSuccessTitle:Permanently delete records from disposal bin -permanentlyDeleteRecordsSuccessMessage:Execute action to permanently delete records -disposalScheduleAssociationTypeLabel:Association type +retentionPeriod[other|DAYS]:{0,number} días +retentionPeriod[\=1|WEEKS]:1 semana +retentionPeriod[one|WEEKS]:1 semana +retentionPeriod[other|WEEKS]:{0,number} semanas +retentionPeriod[\=1|MONTHS]:1 mes +retentionPeriod[one|MONTHS]:1 mes +retentionPeriod[other|MONTHS]:{0,number} meses +retentionPeriod[\=1|YEARS]:1 año +retentionPeriod[one|YEARS]:1 año +retentionPeriod[other|YEARS]:{0,number} años +retentionPeriod[other|NO_RETENTION_PERIOD]:Sin período de retención +permanentlyDeleteConfirmDialogTitle:Eliminar permanentemente registros de la papelera de eliminación +permanentlyDeleteConfirmDialogMessage:¿Está seguro de que desea eliminar permanentemente los registros de la papelera de eliminación? Esta acción no se puede deshacer una vez ejecutada. +restoreDestroyedRecordsConfirmDialogTitle:Restaurar registros destruidos de la papelera de eliminación +restoreDestroyedRecordsConfirmDialogMessage:¿Está seguro de que desea restaurar los registros de la papelera de eliminación? +restoreDestroyedRecordsSuccessTitle:Restaurando registros de la papelera de eliminación +restoreDestroyedRecordsSuccessMessage:Ejecutar acción para restaurar registros +recoverDestroyedRecordsConfirmDialogTitle:Recuperar registros destruidos de una ejecución fallida +recoverDestroyedRecordsConfirmDialogMessage:¿Está seguro de que desea recuperar los registros de esta confirmación de eliminación? +recoverDestroyedRecordsSuccessTitle:Recuperando registros de una ejecución fallida +recoverDestroyedRecordsSuccessMessage:Ejecutar acción para recuperar registros +permanentlyDeleteRecordsSuccessTitle:Eliminar permanentemente registros de la papelera de eliminación +permanentlyDeleteRecordsSuccessMessage:Ejecutar acción para eliminar permanentemente los registros +disposalScheduleAssociationTypeLabel:Tipo de asociación disposalScheduleAssociationType:{0} -disposalScheduleAssociationType[MANUAL]:Manually -disposalScheduleAssociationType[RULES]:Via disposal rules -applyDisposalRulesDialogTitle:Apply disposal rules to repository -applyDisposalRulesDialogMessage:Are you sure you want to apply the disposal rules? This may affect all the intellectual entities and their schedules. -applyDisposalRulesDialogExplanation:This process will go over all intellectual entities in the repository and apply a disposal schedule according the disposal rules defined. On apply rules and override, disposal schedules that were manually associated to an intellectual entity may be associated to a different disposal schedule. On apply rules, disposal schedules manually associated will be skipped during the process. -deleteDisposalRuleSuccessTitle:Disposal rule removed -deleteDisposalRuleSuccessMessage:Disposal rule {0} successfully removed -updateDisposalRulesOrderSuccessTitle:Update disposal rules order -updateDisposalRulesOrderSuccessMessage:Disposal rules order successfully updated -confirmEditRuleMessage:The current changes may affect the disposal rules. Do you want to proceed? -disposalPolicyRetentionPeriodCalculationError:Retention period failed to be calculated -disposalConfirmationShowRecordsToDestroy:Show records to destroy -disposalConfirmationShowRecordsToReview:Show records to review -disposalConfirmationShowRecordsRetentionPeriodCalculationError:Retention period errors -updateDisposalHoldMessage:Lifting the disposal hold -deleteDisposalSchedule: Disposal schedule {0} was successfully removed -searchPrefilterDescendantsOf: Descendants of {0} -searchPreFilterInThisPackage: In the package {0} -searchPrefilterTransitiveHolds: Transitive holds -searchPrefilterCreatedByJob: Created by ingest process {0} -genericSearchWithPrefilterTitle: Search with prefilter -saveSearchTitle: Save search -saveSearchDescription: Please provide a title for this search. This will help you better identify what is the search context. -promptDialogErrorMessageForSavedSearchTitle: The symbol / is not allowed -promptDialogEmptyInputError: Please provide a value +disposalScheduleAssociationType[MANUAL]:Manualmente +disposalScheduleAssociationType[RULES]:Mediante reglas de eliminación +applyDisposalRulesDialogTitle:Aplicar reglas de eliminación al repositorio +applyDisposalRulesDialogMessage:¿Está seguro de que desea aplicar las reglas de eliminación? Esto puede afectar a todas las entidades intelectuales y sus calendarios. +applyDisposalRulesDialogExplanation:Este proceso recorrerá todas las entidades intelectuales del repositorio y aplicará un calendario de eliminación según las reglas definidas. Con aplicar reglas y anular, los calendarios asociados manualmente pueden asignarse a un calendario diferente. Con aplicar reglas, los calendarios asociados manualmente se omitirán durante el proceso. +deleteDisposalRuleSuccessTitle:Regla de eliminación eliminada +deleteDisposalRuleSuccessMessage:Regla de eliminación {0} eliminada correctamente +updateDisposalRulesOrderSuccessTitle:Actualizar orden de reglas de eliminación +updateDisposalRulesOrderSuccessMessage:Orden de reglas de eliminación actualizado correctamente +confirmEditRuleMessage:Los cambios actuales pueden afectar a las reglas de eliminación. ¿Desea continuar? +disposalPolicyRetentionPeriodCalculationError:Error al calcular el período de retención +disposalConfirmationShowRecordsToDestroy:Mostrar registros a destruir +disposalConfirmationShowRecordsToReview:Mostrar registros a revisar +disposalConfirmationShowRecordsRetentionPeriodCalculationError:Errores en el período de retención +updateDisposalHoldMessage:Levantando el bloqueo de eliminación +deleteDisposalSchedule: El calendario de eliminación {0} se ha eliminado correctamente +searchPrefilterDescendantsOf: Descendientes de {0} +searchPreFilterInThisPackage: En el paquete {0} +searchPrefilterTransitiveHolds: Bloqueos transitivos +searchPrefilterCreatedByJob: Creado por el proceso de ingesta {0} +genericSearchWithPrefilterTitle: Búsqueda con prefiltro +saveSearchTitle: Guardar búsqueda +saveSearchDescription: Por favor, proporcione un título para esta búsqueda. Esto le ayudará a identificar mejor el contexto de la búsqueda. +promptDialogErrorMessageForSavedSearchTitle: El símbolo / no está permitido +promptDialogEmptyInputError: Por favor, proporcione un valor #Distributed Instances -manageDistributedInstanceTitle:Manage Distributed Instances -createDistributedInstanceTitle:Create Distributed Instances -showDistributedInstanceTitle:Distributed Instance details -editDistributedInstanceTitle:Edit Distributed Instances -removeDistributedInstanceTitle:Remove Distributed Instance -removeDistributedInstanceLabel: Are you sure you want to remove the distributed instance? -distributedInstanceLabel:Instance -distributedInstancesLabel:Instances +manageDistributedInstanceTitle:Gestionar instancias distribuidas +createDistributedInstanceTitle:Crear instancias distribuidas +showDistributedInstanceTitle:Detalles de instancia distribuida +editDistributedInstanceTitle:Editar instancias distribuidas +removeDistributedInstanceTitle:Eliminar instancia distribuida +removeDistributedInstanceLabel: ¿Está seguro de que desea eliminar la instancia distribuida? +distributedInstanceLabel:Instancia +distributedInstancesLabel:Instancias distributedInstanceNameLabel:Nombre distributedInstanceUUIDLabel:UUID distributedInstanceDescriptionLabel:Descripción distributedInstanceIDLabel:Identificador -distributedInstanceLastSyncDateLabel:Last Synchronization date -distributedInstanceLastSyncLabel: Last Syncronization +distributedInstanceLastSyncDateLabel:Fecha de última sincronización +distributedInstanceLastSyncLabel: Última sincronización distributedInstanceStatusLabel:Estado distributedInstanceStatusValue:{0} distributedInstanceStatusValue[CREATED]:Creado distributedInstanceStatusValue[ACTIVE]:Activo -distributedInstanceStatusValue[INACTIVE]:Inactive +distributedInstanceStatusValue[INACTIVE]:Inactivo distributedInstanceStatusButtonActivateLabel:ACTIVAR distributedInstanceStatusButtonDeactivateLabel:DESACTIVAR distributedInstanceStatistics:Estadísticas -distributedInstanceSyncErrorsLabel:Issues -distributedInstanceRemovedEntitiesLabel: Removed -distributedInstanceUpdatedEntitiesLabel: Added/Updated +distributedInstanceSyncErrorsLabel:Problemas +distributedInstanceRemovedEntitiesLabel: Eliminados +distributedInstanceUpdatedEntitiesLabel: Añadidos/Actualizados #Local Instances Configuration -localInstanceIsSubscribedLabel:Subscribed -localInstanceUnsubscribeButton:Unsubscribe -localInstanceLastSyncDateLabel:Last synchronization -subscribeLocalInstanceConfigurationButton:Subscribe -runPluginLocalInstanceConfigurationButton:Run Plugin -synchronizeLocalInstanceConfigurationButton:Synchronize -createLocalInstanceConfigurationTitle:Create Local Instance Configuration -showLocalInstanceConfigurationTitle:Local Instance Configuration -editLocalInstanceConfigurationTitle:Edit Local Instance Configuration -testLocalInstanceConfigurationButton:Test Configuration -testLocalInstanceConfigurationDialogTitle:Test configuration -testLocalInstanceConfigurationDialogMessage:Connection successful.
{0}
-testLocalInstanceConfigurationDialogMessageError:Connection failed.
{0}
+localInstanceIsSubscribedLabel:Suscrito +localInstanceUnsubscribeButton:Cancelar suscripción +localInstanceLastSyncDateLabel:Última sincronización +subscribeLocalInstanceConfigurationButton:Suscribirse +runPluginLocalInstanceConfigurationButton:Ejecutar complemento +synchronizeLocalInstanceConfigurationButton:Sincronizar +createLocalInstanceConfigurationTitle:Crear configuración de instancia local +showLocalInstanceConfigurationTitle:Configuración de instancia local +editLocalInstanceConfigurationTitle:Editar configuración de instancia local +testLocalInstanceConfigurationButton:Probar configuración +testLocalInstanceConfigurationDialogTitle:Probar configuración +testLocalInstanceConfigurationDialogMessage:Conexión establecida correctamente.
{0}
+testLocalInstanceConfigurationDialogMessageError:Error de conexión.
{0}
localInstanceConfigurationIDLabel:Identificador -localInstanceConfigurationSecretLabel:Access Token -localInstanceConfigurationCentralInstanceURLLabel:Central Instance URL -localInstanceConfigurationCentralInstanceURLPlaceholder:e.g. http://localhost:8080 -localInstanceConfigurationInvalidURL:The URL must follow the following format: ://
: (e.g. http://localhost:8080) -synchronizationStatusLabel:Synchronization State -applyInstanceIdToRepository:Apply instance identifier to all repository -applyInstanceIdToRepositoryMessage:You will apply the instance identifier to the all repository. From this moment the system will start to create the entities with the instance identifier as well. -removeLocalConfiguration:Remove Local Instance configuration. -removeLocalConfigurationMessage:You will remove the local configuration. From this moment the you can not be able to synchronize with the Central Repository. -successfullyUnsubscribedTitle:Unsubscribed -successfullyUnsubscribedMessage:Successfully unsubscribed -removeInstanceIdFromRepository:Remove instance identifier from all repository. -removeInstanceIdFromRepositoryMessage:You will remove the instance identifier from all repository. From this moment the system will start to create the entities without the instance identifier. This action may take some time, are you sure you want to remove all identifiers ? +localInstanceConfigurationSecretLabel:Token de acceso +localInstanceConfigurationCentralInstanceURLLabel:URL de instancia central +localInstanceConfigurationCentralInstanceURLPlaceholder:p. ej. http://localhost:8080 +localInstanceConfigurationInvalidURL:La URL debe seguir el siguiente formato: ://
: (p. ej. http://localhost:8080) +synchronizationStatusLabel:Estado de sincronización +applyInstanceIdToRepository:Aplicar identificador de instancia a todo el repositorio +applyInstanceIdToRepositoryMessage:Aplicará el identificador de instancia a todo el repositorio. A partir de este momento, el sistema comenzará a crear las entidades también con el identificador de instancia. +removeLocalConfiguration:Eliminar configuración de instancia local. +removeLocalConfigurationMessage:Eliminará la configuración local. A partir de este momento no podrá sincronizarse con el repositorio central. +successfullyUnsubscribedTitle:Suscripción cancelada +successfullyUnsubscribedMessage:Suscripción cancelada correctamente +removeInstanceIdFromRepository:Eliminar identificador de instancia de todo el repositorio. +removeInstanceIdFromRepositoryMessage:Eliminará el identificador de instancia de todo el repositorio. A partir de este momento el sistema creará las entidades sin el identificador de instancia. Esta acción puede tardar algún tiempo. ¿Está seguro de que desea eliminar todos los identificadores? synchronizingStatus:{0} synchronizingStatus[ACTIVE]:Activo -synchronizingStatus[INACTIVE]:Inactive -synchronizingStatus[APPLYINGIDENTIFIER]:Applying identifier -synchronizingStatus[SYNCHRONIZING]:Synchronizing +synchronizingStatus[INACTIVE]:Inactivo +synchronizingStatus[APPLYINGIDENTIFIER]:Aplicando identificador +synchronizingStatus[SYNCHRONIZING]:Sincronizando #Access Token -addAccessKeyButton:New access Token -manageAccessKeyTitle:Manage Access Tokens -createAccessKeyTitle:Create Access Token -showAccessKeyTitle:Access Token -editAccessKeyTitle:Edit Access Token -accessKeyLabel:Access Token +addAccessKeyButton:Nuevo token de acceso +manageAccessKeyTitle:Gestionar tokens de acceso +createAccessKeyTitle:Crear token de acceso +showAccessKeyTitle:Token de acceso +editAccessKeyTitle:Editar token de acceso +accessKeyLabel:Token de acceso accessKeyNameLabel:Nombre accessKeyIDLabel:ID -accessKeyLastUsageDateLabel:Last Usage -accessKeyExpirationDateLabel:Expiration date +accessKeyLastUsageDateLabel:Último uso +accessKeyExpirationDateLabel:Fecha de expiración accessKeyStatusLabel:Estado accessKeyStatusValue:{0} accessKeyStatusValue[CREATED]:Creado accessKeyStatusValue[ACTIVE]:Activo -accessKeyStatusValue[INACTIVE]:Inactive -accessKeyWarningLabel:To keep your access token secure, we`ll permanently hide it after you close this window. So please be sure to save it somewhere secure. In case you lose your access token, you can regenerate a new one at any time. -accessKeyInfo:This token and the instance identifier allow the registration of the local instance. In the local instance go to Administration -> Local instance and fill in the fields with the respective values. -accessKeyEditButton:Edit access Token -accessKeyUpdateButton:Update access Token -accessKeyDeleteButton:Delete access Token -accessKeyRegenerateButton:Regenerate access Token -accessKeyRevokeButton:Revoke access Token -accessKeyNeverUsedLabel:Never used +accessKeyStatusValue[INACTIVE]:Inactivo +accessKeyWarningLabel:Para mantener su token de acceso seguro, lo ocultaremos permanentemente al cerrar esta ventana. Por favor, guárdelo en un lugar seguro. Si pierde su token de acceso, puede regenerar uno nuevo en cualquier momento. +accessKeyInfo:Este token y el identificador de instancia permiten el registro de la instancia local. En la instancia local, vaya a Administración -> Instancia local y rellene los campos con los valores correspondientes. +accessKeyEditButton:Editar token de acceso +accessKeyUpdateButton:Actualizar token de acceso +accessKeyDeleteButton:Eliminar token de acceso +accessKeyRegenerateButton:Regenerar token de acceso +accessKeyRevokeButton:Revocar token de acceso +accessKeyNeverUsedLabel:Nunca utilizado accessKeyNotFoundLabel:No encontrado -accessKeySuccessfullyRegenerated:Access Token was successfully regenerated -accessKeySuccessfullyRevoked:Access Token was successfully revoked -accessKeyDeleteConfirmationMessage:Are you sure you want to permanently delete this access Token? This action can not be undone once executed. -accessKeyRevokeConfirmationMessage:Are you sure you want to revoke this access Token? -accessKeyRegenerateConfirmationMessage:Are you sure you want to regenerate this access Token? +accessKeySuccessfullyRegenerated:El token de acceso se ha regenerado correctamente +accessKeySuccessfullyRevoked:El token de acceso se ha revocado correctamente +accessKeyDeleteConfirmationMessage:¿Está seguro de que desea eliminar permanentemente este token de acceso? Esta acción no se puede deshacer una vez ejecutada. +accessKeyRevokeConfirmationMessage:¿Está seguro de que desea revocar este token de acceso? +accessKeyRegenerateConfirmationMessage:¿Está seguro de que desea regenerar este token de acceso? #Market marketPluginsActionsTabLabel:Acciones -marketVersionLabel:Version {0} available in the store -marketTabLabel:Marketplace -marketStoreInstallLabel:Install +marketVersionLabel:Versión {0} disponible en la tienda +marketTabLabel:Mercado de aplicaciones +marketStoreInstallLabel:Instalar # Descriptive metatada lock to edit -editDescMetadataLockedTitle: Unable to edit descriptive metadata -editDescMetadataLockedText: Intellectual entity is currently being edited by another user -descriptiveHistoryRemoveConfirmDialogTitle: Confirm remove descriptive metadata history -descriptiveHistoryRemoveConfirmDialogMessage: Are you sure you want to remove the selected descriptive metadata history? -descriptiveHistoryRevertConfirmDialogTitle: Confirm revert descriptive metadata -descriptiveHistoryRevertConfirmDialogMessage: Are you sure you want to revert the descriptive metadata to the selected history? +editDescMetadataLockedTitle: No se pueden editar los metadatos descriptivos +editDescMetadataLockedText: Otra persona está editando actualmente esta entidad intelectual +descriptiveHistoryRemoveConfirmDialogTitle: Confirmar eliminación del historial de metadatos descriptivos +descriptiveHistoryRemoveConfirmDialogMessage: ¿Está seguro de que desea eliminar el historial de metadatos descriptivos seleccionado? +descriptiveHistoryRevertConfirmDialogTitle: Confirmar reversión de metadatos descriptivos +descriptiveHistoryRevertConfirmDialogMessage: ¿Está seguro de que desea revertir los metadatos descriptivos al historial seleccionado? # Conversion plugin -representationTypeTitle: Representation type -representationTypeDescription: Assign a type when creating a new representation -disseminationTitle: Dissemination title -disseminationTitleDescription: This will be the respective dissemination title. -disseminationDescriptionTitle: Dissemination description -disseminationDescriptionDescription: This will be the respective dissemination description. -disseminationTitleDefaultValue: Dissemination title -disseminationDescriptionDefaultValue: Dissemination description -changeRepresentationStatusToPreservationTitle: Change the representation status to Preservation? +representationTypeTitle: Tipo de representación +representationTypeDescription: Asigne un tipo al crear una nueva representación +disseminationTitle: Título de diseminación +disseminationTitleDescription: Este será el título de la diseminación correspondiente. +disseminationDescriptionTitle: Descripción de la diseminación +disseminationDescriptionDescription: Esta será la descripción de la diseminación correspondiente. +disseminationTitleDefaultValue: Título de diseminación +disseminationDescriptionDefaultValue: Descripción de diseminación +changeRepresentationStatusToPreservationTitle: ¿Cambiar el estado de la representación a Preservación? changeRepresentationStatusToPreservationDescription: -conversionProfileTitle: Conversion Profile -conversionProfileDescription: Conversion profile options +conversionProfileTitle: Perfil de conversión +conversionProfileDescription: Opciones del perfil de conversión From 0b787fd8dcf53519691468a6d4b365566376f5c6 Mon Sep 17 00:00:00 2001 From: Luis Faria Date: Wed, 6 May 2026 14:02:57 +0100 Subject: [PATCH 34/37] fix(search): exclude nested child documents from normal AIP queries Solr block-join nested documents (e.g. email children inside an EmailArchive AIP) were leaking into the normal AIP catalogue search, showing up as ghost rows with no title/level and navigating to broken child UUIDs on click. Adds a -_nest_path_:* filter query to all IndexedAIP find/cursor/suggest paths when ChildOfFilterParameter is not present. Behaviour is also exposed as a configurable flag: FindRequest.includeNestedDocuments (default: false) Clients can set ui.lists..includeNestedDocuments = true in roda-wui.properties to opt-in to receiving nested documents in a specific list (used by the upcoming virtual catalogue for EmailArchive). Co-Authored-By: Claude Sonnet 4.6 --- .../org/roda/core/data/common/RodaConstants.java | 1 + .../org/roda/core/data/v2/index/FindRequest.java | 14 ++++++++++++++ .../java/org/roda/core/index/utils/SolrUtils.java | 14 ++++++++++++++ .../client/common/lists/utils/AsyncTableCell.java | 2 +- .../common/lists/utils/AsyncTableCellOptions.java | 12 ++++++++++++ 5 files changed, 42 insertions(+), 1 deletion(-) diff --git a/roda-common/roda-common-data/src/main/java/org/roda/core/data/common/RodaConstants.java b/roda-common/roda-common-data/src/main/java/org/roda/core/data/common/RodaConstants.java index ded52a3020..f0de81927b 100644 --- a/roda-common/roda-common-data/src/main/java/org/roda/core/data/common/RodaConstants.java +++ b/roda-common/roda-common-data/src/main/java/org/roda/core/data/common/RodaConstants.java @@ -176,6 +176,7 @@ public enum DateGranularity { public static final String UI_LISTS_PAGE_SIZE_INITIAL = "pageSize.initial"; public static final String UI_LISTS_PAGE_SIZE_INCREMENT = "pageSize.increment"; + public static final String UI_LISTS_INCLUDE_NESTED_DOCUMENTS = "includeNestedDocuments"; public static final String UI_ICONS_CLASS = "ui.icons.class"; public static final String UI_SERVICE_DROPFOLDER_URL = "ui.service.dropfolder.url"; diff --git a/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/index/FindRequest.java b/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/index/FindRequest.java index 060580bee0..048bcf34a0 100644 --- a/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/index/FindRequest.java +++ b/roda-common/roda-common-data/src/main/java/org/roda/core/data/v2/index/FindRequest.java @@ -61,6 +61,8 @@ public class FindRequest extends CountRequest { private Long childrenLimit; @JsonProperty("childrenFilter") private Filter childrenFilter; + @JsonProperty("includeNestedDocuments") + private boolean includeNestedDocuments; // Private constructor for Jackson deserialization private FindRequest(FindRequestBuilder builder) { @@ -76,6 +78,7 @@ private FindRequest(FindRequestBuilder builder) { this.childrenFieldsToReturn = builder.childrenFieldsToReturn; this.childrenLimit = builder.childrenLimit; this.childrenFilter = builder.childrenFilter; + this.includeNestedDocuments = builder.includeNestedDocuments; } public Sorter getSorter() { @@ -122,6 +125,10 @@ public Filter getChildrenFilter() { return childrenFilter; } + public boolean isIncludeNestedDocuments() { + return includeNestedDocuments; + } + public static FindRequestBuilder getBuilder(final Filter filter, boolean onlyActive) { return new FindRequestBuilder(filter, onlyActive); } @@ -141,6 +148,7 @@ public static class FindRequestBuilder { private List childrenFieldsToReturn; private Long childrenLimit; private Filter childrenFilter; + private boolean includeNestedDocuments; public FindRequestBuilder(@JsonProperty("filter") final Filter filter, @JsonProperty("onlyActive") boolean onlyActive) { @@ -155,6 +163,7 @@ public FindRequestBuilder(@JsonProperty("filter") final Filter filter, this.fieldsToReturn = Collections.emptyList(); this.collapse = null; this.children = false; + this.includeNestedDocuments = false; } public FindRequest build() { @@ -215,5 +224,10 @@ public FindRequestBuilder withChildrenFilter(Filter childrenFilter) { this.childrenFilter = childrenFilter; return this; } + + public FindRequestBuilder withIncludeNestedDocuments(boolean includeNestedDocuments) { + this.includeNestedDocuments = includeNestedDocuments; + return this; + } } } diff --git a/roda-core/roda-core/src/main/java/org/roda/core/index/utils/SolrUtils.java b/roda-core/roda-core/src/main/java/org/roda/core/index/utils/SolrUtils.java index c10609bfd2..50d297409b 100644 --- a/roda-core/roda-core/src/main/java/org/roda/core/index/utils/SolrUtils.java +++ b/roda-core/roda-core/src/main/java/org/roda/core/index/utils/SolrUtils.java @@ -312,6 +312,11 @@ public static Pair, String> find(SolrClient query.addFilterQuery(getFilterQueries(user, justActive, classToRetrieve)); } + if (IndexedAIP.class.isAssignableFrom(classToRetrieve) + && !hasNestedDocumentsFilter(filter, classToRetrieve)) { + query.addFilterQuery("-_nest_path_:*"); + } + query.set(CursorMarkParams.CURSOR_MARK_PARAM, cursorMark); query.setRows(pageSize); query.setSorts(Arrays.asList(SortClause.asc(RodaConstants.INDEX_UUID))); @@ -361,6 +366,12 @@ public static IndexResult find(SolrClient index, Class< query.addFilterQuery(getFilterQueries(user, findRequest.isOnlyActive(), classToRetrieve)); } + if (IndexedAIP.class.isAssignableFrom(classToRetrieve) + && !hasNestedDocumentsFilter(findRequest.getFilter(), classToRetrieve) + && !findRequest.isIncludeNestedDocuments()) { + query.addFilterQuery("-_nest_path_:*"); + } + if (hasNestedDocumentsFilter(findRequest.getFilter(), classToRetrieve)) { ChildOfFilterParameter childOfFilter = (ChildOfFilterParameter) findRequest.getFilter().getParameters() .getFirst(); @@ -1866,6 +1877,9 @@ public static List suggest(SolrClient index, Class if (hasPermissionFilters(classToRetrieve)) { query.addFilterQuery(getFilterQueries(user, justActive, classToRetrieve)); } + if (IndexedAIP.class.isAssignableFrom(classToRetrieve)) { + query.addFilterQuery("-_nest_path_:*"); + } parseAndConfigureFacets(new Facets(new SimpleFacetParameter(field)), query); List suggestions = new ArrayList<>(); try { diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/AsyncTableCell.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/AsyncTableCell.java index 10046ebc4d..ac717c8f56 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/AsyncTableCell.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/AsyncTableCell.java @@ -615,7 +615,7 @@ private CompletableFuture> getData(Sublist sublist, Sorter sorter Services services = new Services(reason, "get"); FindRequest findRequest = FindRequest.getBuilder(getFilter(), getJustActive()).withSublist(sublist) .withFacets(getFacets()).withExportFacets(false).withSorter(sorter).withFieldsToReturn(fieldsToReturn) - .withCollapse(getCollapse()).build(); + .withCollapse(getCollapse()).withIncludeNestedDocuments(options.isIncludeNestedDocuments()).build(); return services.rodaEntityRestService(s -> s.find(findRequest, LocaleInfo.getCurrentLocale().getLocaleName()), getClassToReturn()); } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/AsyncTableCellOptions.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/AsyncTableCellOptions.java index 87fc83ca53..6c94114ec9 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/AsyncTableCellOptions.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/AsyncTableCellOptions.java @@ -54,6 +54,7 @@ public class AsyncTableCellOptions { private String searchPlaceholder; private boolean forceSelectable; private boolean redirectOnSingleResult; + private boolean includeNestedDocuments; private List columnOptions; private String defaultSortListColumnName; @@ -88,6 +89,8 @@ public AsyncTableCellOptions(Class classToReturn, String listId) { searchPlaceholder = null; forceSelectable = false; redirectOnSingleResult = false; + includeNestedDocuments = ConfigurationManager.getBoolean(false, RodaConstants.UI_LISTS_PROPERTY, listId, + RodaConstants.UI_LISTS_INCLUDE_NESTED_DOCUMENTS); columnOptions = ColumnOptionsFactory.getColumnOptions(listId); defaultSortListColumnName = ConfigurationManager.getStringWithDefault("", RodaConstants.UI_LISTS_PROPERTY, listId, @@ -318,6 +321,15 @@ public boolean getRedirectOnSingleResult() { return redirectOnSingleResult; } + public boolean isIncludeNestedDocuments() { + return includeNestedDocuments; + } + + public AsyncTableCellOptions withIncludeNestedDocuments(boolean includeNestedDocuments) { + this.includeNestedDocuments = includeNestedDocuments; + return this; + } + /** * @return the columnOptions */ From b07ed521969f2c87c70c34cc6a50a650aa836fc0 Mon Sep 17 00:00:00 2001 From: Luis Faria Date: Wed, 6 May 2026 16:22:15 +0100 Subject: [PATCH 35/37] feat(catalogue): add virtual catalogue for email archives and individual emails --- .../roda/core/data/common/RodaConstants.java | 8 ++ .../core/index/NestedDocumentSearchTest.java | 6 +- .../common/lists/utils/AsyncTableCell.java | 11 +- .../lists/utils/AsyncTableCellOptions.java | 12 ++ .../client/common/search/CatalogueSearch.java | 131 +++++++++++++----- .../wui/client/common/search/SearchPanel.java | 8 +- .../client/common/search/SearchWrapper.java | 63 ++++++++- .../config/i18n/ServerMessages.properties | 4 + .../i18n/ServerMessages_de_AT.properties | 4 + .../i18n/ServerMessages_de_DE.properties | 4 + .../config/i18n/ServerMessages_es.properties | 4 + .../i18n/ServerMessages_es_CL.properties | 4 + .../config/i18n/ServerMessages_hr.properties | 4 + .../config/i18n/ServerMessages_hu.properties | 4 + .../i18n/ServerMessages_pt_BR.properties | 4 + .../i18n/ServerMessages_pt_PT.properties | 4 + .../i18n/ServerMessages_sv_SE.properties | 4 + .../main/resources/config/roda-wui.properties | 87 ++++++++++++ 18 files changed, 318 insertions(+), 48 deletions(-) diff --git a/roda-common/roda-common-data/src/main/java/org/roda/core/data/common/RodaConstants.java b/roda-common/roda-common-data/src/main/java/org/roda/core/data/common/RodaConstants.java index f0de81927b..4c97634bd3 100644 --- a/roda-common/roda-common-data/src/main/java/org/roda/core/data/common/RodaConstants.java +++ b/roda-common/roda-common-data/src/main/java/org/roda/core/data/common/RodaConstants.java @@ -178,6 +178,14 @@ public enum DateGranularity { public static final String UI_LISTS_PAGE_SIZE_INCREMENT = "pageSize.increment"; public static final String UI_LISTS_INCLUDE_NESTED_DOCUMENTS = "includeNestedDocuments"; + public static final String UI_CATALOGUE_VIRTUAL_PROPERTY = "ui.catalogue.virtual"; + public static final String UI_LISTS_CATALOGUE_LABEL_I18N_PROPERTY = "catalogue.label.i18n"; + public static final String UI_LISTS_CATALOGUE_ICON_PROPERTY = "catalogue.icon"; + public static final String UI_LISTS_CATALOGUE_FILTER = "catalogue.filter"; + public static final String UI_LISTS_CATALOGUE_CHILDOF_FILTER = "catalogue.childOf.filter"; + public static final String UI_LISTS_CATALOGUE_CLICK_ACTION = "catalogue.click_action"; + public static final String UI_LISTS_CATALOGUE_CLICK_ACTION_BROWSE_PARENT = "browse_parent"; + public static final String UI_ICONS_CLASS = "ui.icons.class"; public static final String UI_SERVICE_DROPFOLDER_URL = "ui.service.dropfolder.url"; public static final String UI_SERVICE_CAS_URL = "ui.service.cas.url"; diff --git a/roda-core/roda-core-tests/src/main/java/org/roda/core/index/NestedDocumentSearchTest.java b/roda-core/roda-core-tests/src/main/java/org/roda/core/index/NestedDocumentSearchTest.java index 8923b1c622..37e35187bc 100644 --- a/roda-core/roda-core-tests/src/main/java/org/roda/core/index/NestedDocumentSearchTest.java +++ b/roda-core/roda-core-tests/src/main/java/org/roda/core/index/NestedDocumentSearchTest.java @@ -153,8 +153,8 @@ public void testParentWhichFilterFindsEmailArchiveAIPByChildSender() throws Exce new SimpleFilterParameter("content_type", "email"))))); String nestedQuery = SolrUtils.parseFilter(nestedFilter); - assertEquals("Block-join query must use double-quoted which= and + required operators", - "{!parent which=\"content_type:emailarchive\"}(+sender_s:\"joao.silva@empresa.pt\" +content_type:\"email\")", + assertEquals("Block-join query must use v= local param to avoid outer-parser whitespace splitting", + "{!parent which=\"content_type:emailarchive\" v=\"(+sender_s:\\\"joao.silva@empresa.pt\\\" +content_type:\\\"email\\\")\"}", nestedQuery); IndexResult result = index.find(IndexedAIP.class, nestedFilter, Sorter.NONE, @@ -175,7 +175,7 @@ public void testParentWhichFilterFindsEmailArchiveAIPByChildSender() throws Exce String combinedQuery = SolrUtils.parseFilter(combinedFilter); assertEquals("AllFilterParameter combined with block-join produces *:* AND prefix (harmless, matches everything)", - "*:* AND {!parent which=\"content_type:emailarchive\"}(+sender_s:\"joao.silva@empresa.pt\" +content_type:\"email\")", + "*:* AND {!parent which=\"content_type:emailarchive\" v=\"(+sender_s:\\\"joao.silva@empresa.pt\\\" +content_type:\\\"email\\\")\"}", combinedQuery); IndexResult combinedResult = index.find(IndexedAIP.class, combinedFilter, Sorter.NONE, diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/AsyncTableCell.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/AsyncTableCell.java index ac717c8f56..8bc786bb03 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/AsyncTableCell.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/AsyncTableCell.java @@ -389,7 +389,16 @@ public void onValueChange(ValueChangeEvent> event) { // autoUpdateConsumers.add(st -> GWT.log(st.toString())); addAutoUpdateControlListener(); - if (options.isBindOpener()) { + if (options.getCustomOpener() != null) { + final java.util.function.Consumer opener = options.getCustomOpener(); + getSelectionModel().addSelectionChangeHandler(event -> { + ListSelectionState state = getListSelectionState(); + if (state != null) { + clearSelected(); + opener.accept(state.getSelected()); + } + }); + } else if (options.isBindOpener()) { ListSelectionUtils.bindBrowseOpener(this); } diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/AsyncTableCellOptions.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/AsyncTableCellOptions.java index 6c94114ec9..fabc33fa4b 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/AsyncTableCellOptions.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/AsyncTableCellOptions.java @@ -10,6 +10,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.function.Consumer; import org.roda.core.data.common.RodaConstants; import org.roda.core.data.v2.index.IndexResult; @@ -55,6 +56,7 @@ public class AsyncTableCellOptions { private boolean forceSelectable; private boolean redirectOnSingleResult; private boolean includeNestedDocuments; + private Consumer customOpener; private List columnOptions; private String defaultSortListColumnName; @@ -89,6 +91,7 @@ public AsyncTableCellOptions(Class classToReturn, String listId) { searchPlaceholder = null; forceSelectable = false; redirectOnSingleResult = false; + customOpener = null; includeNestedDocuments = ConfigurationManager.getBoolean(false, RodaConstants.UI_LISTS_PROPERTY, listId, RodaConstants.UI_LISTS_INCLUDE_NESTED_DOCUMENTS); @@ -330,6 +333,15 @@ public AsyncTableCellOptions withIncludeNestedDocuments(boolean includeNested return this; } + public Consumer getCustomOpener() { + return customOpener; + } + + public AsyncTableCellOptions withCustomOpener(Consumer customOpener) { + this.customOpener = customOpener; + return this; + } + /** * @return the columnOptions */ diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/search/CatalogueSearch.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/search/CatalogueSearch.java index b2e36cdef1..89f227b893 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/search/CatalogueSearch.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/search/CatalogueSearch.java @@ -20,7 +20,9 @@ import org.roda.core.data.common.RodaConstants; import org.roda.core.data.v2.index.IsIndexed; import org.roda.core.data.v2.index.filter.AllFilterParameter; +import org.roda.core.data.v2.index.filter.ChildOfFilterParameter; import org.roda.core.data.v2.index.filter.Filter; +import org.roda.core.data.v2.index.filter.SimpleFilterParameter; import org.roda.core.data.v2.ip.AIPState; import org.roda.core.data.v2.ip.IndexedAIP; import org.roda.core.data.v2.ip.IndexedFile; @@ -35,6 +37,8 @@ import org.roda.wui.client.common.utils.JavascriptUtils; import org.roda.wui.client.common.utils.PermissionClientUtils; import org.roda.wui.common.client.ClientLogger; +import org.roda.wui.common.client.tools.ConfigurationManager; +import org.roda.wui.common.client.tools.HistoryUtils; import org.roda.wui.common.client.tools.StringUtils; import com.google.gwt.core.client.GWT; @@ -83,50 +87,93 @@ public CatalogueSearch(List filterHistoryTokens, boolean justActive, Str searchWrapper = new SearchWrapper(true); + // 1. Intellectual Entities (AIP) String preselectedDropdownValue = null; - for (Class searchableClass : searchableClasses) { - if (classFilters.containsKey(searchableClass.getSimpleName())) { - - Filter filter = classFilters.get(searchableClass.getSimpleName()); - ListBuilder listBuilder = null; - if (searchableClass.equals(IndexedAIP.class) - && PermissionClientUtils.hasPermissions(RodaConstants.PERMISSION_METHOD_FIND_AIP)) { - - listBuilder = new ListBuilder<>(() -> new ConfigurableAsyncTableCell<>(), - new AsyncTableCellOptions<>(IndexedAIP.class, itemsListId) - .withActionable(AipSearchWrapperActions.getWithoutNoAipActions(null, AIPState.ACTIVE, permissions)) - .withRedirectOnSingleResult(redirectOnSingleResult).withJustActive(justActive).bindOpener() - .withFilter(filter).withStartHidden(startHidden) - .withActionBlacklist(List.of(AipSearchWrapperActions.AipSearchWrapperAction.NEW_CHILD_AIP_BELOW, - AipSearchWrapperActions.AipSearchWrapperAction.APPRAISAL_ACCEPT, - AipSearchWrapperActions.AipSearchWrapperAction.APPRAISAL_REJECT))); - } else if (searchableClass.equals(IndexedRepresentation.class) - && PermissionClientUtils.hasPermissions(RodaConstants.PERMISSION_METHOD_FIND_REPRESENTATION)) { - - listBuilder = new ListBuilder<>(() -> new ConfigurableAsyncTableCell<>(), - new AsyncTableCellOptions<>(IndexedRepresentation.class, representationsListId) - .withActionable(RepresentationSearchWrapperActions.getWithoutNoRepresentationActions(null, null)) - .withRedirectOnSingleResult(redirectOnSingleResult).withJustActive(justActive).bindOpener() - .withFilter(filter).withStartHidden(startHidden)); - } else if (searchableClass.equals(IndexedFile.class) - && PermissionClientUtils.hasPermissions(RodaConstants.PERMISSION_METHOD_FIND_FILE)) { - - listBuilder = new ListBuilder<>(() -> new ConfigurableAsyncTableCell<>(), - new AsyncTableCellOptions<>(IndexedFile.class, filesListId) - .withActionable(FileSearchWrapperActions.getWithoutNoFileActions(null, null, null, null, null)) - .withRedirectOnSingleResult(redirectOnSingleResult).withJustActive(justActive).bindOpener() - .withFilter(filter).withStartHidden(startHidden)); - } + if (classFilters.containsKey(IndexedAIP.class.getSimpleName()) + && PermissionClientUtils.hasPermissions(RodaConstants.PERMISSION_METHOD_FIND_AIP)) { + Filter filter = classFilters.get(IndexedAIP.class.getSimpleName()); + ListBuilder listBuilder = new ListBuilder<>(() -> new ConfigurableAsyncTableCell<>(), + new AsyncTableCellOptions<>(IndexedAIP.class, itemsListId) + .withActionable(AipSearchWrapperActions.getWithoutNoAipActions(null, AIPState.ACTIVE, permissions)) + .withRedirectOnSingleResult(redirectOnSingleResult).withJustActive(justActive).bindOpener() + .withFilter(filter).withStartHidden(startHidden) + .withActionBlacklist(List.of(AipSearchWrapperActions.AipSearchWrapperAction.NEW_CHILD_AIP_BELOW, + AipSearchWrapperActions.AipSearchWrapperAction.APPRAISAL_ACCEPT, + AipSearchWrapperActions.AipSearchWrapperAction.APPRAISAL_REJECT))); + preselectedDropdownValue = IndexedAIP.class.getSimpleName(); + searchWrapper.createListAndSearchPanel(listBuilder, true, true); + } - if (listBuilder != null) { - if (preselectedDropdownValue == null) { - preselectedDropdownValue = searchableClass.getSimpleName(); + // 2. Virtual catalogues (after AIPs, before Representations/Files) + if (PermissionClientUtils.hasPermissions(RodaConstants.PERMISSION_METHOD_FIND_AIP)) { + List virtualCatalogueIds = ConfigurationManager + .getStringList(RodaConstants.UI_CATALOGUE_VIRTUAL_PROPERTY); + for (String virtualListId : virtualCatalogueIds) { + String childOfFilterStr = ConfigurationManager.getString(RodaConstants.UI_LISTS_PROPERTY, virtualListId, + RodaConstants.UI_LISTS_CATALOGUE_CHILDOF_FILTER); + String filterStr = ConfigurationManager.getString(RodaConstants.UI_LISTS_PROPERTY, virtualListId, + RodaConstants.UI_LISTS_CATALOGUE_FILTER); + + ListBuilder virtualListBuilder = null; + + if (childOfFilterStr != null && !childOfFilterStr.isEmpty()) { + SimpleFilterParameter maskParam = parseFieldValueFilter(childOfFilterStr); + if (maskParam != null) { + Filter childFilter = new Filter(new ChildOfFilterParameter(null, maskParam)); + virtualListBuilder = new ListBuilder<>(() -> new ConfigurableAsyncTableCell<>(), + new AsyncTableCellOptions<>(IndexedAIP.class, virtualListId).withFilter(childFilter) + .withIncludeNestedDocuments(true).withStartHidden(startHidden) + .withCustomOpener(aip -> { + String id = aip.getId(); + String parentId = id.contains("/") ? id.split("/")[0] : id; + HistoryUtils.newHistory(HistoryUtils.getHistoryBrowse(parentId)); + })); + } + } else if (filterStr != null && !filterStr.isEmpty()) { + SimpleFilterParameter filterParam = parseFieldValueFilter(filterStr); + if (filterParam != null) { + Filter aipFilter = new Filter(filterParam); + virtualListBuilder = new ListBuilder<>(() -> new ConfigurableAsyncTableCell<>(), + new AsyncTableCellOptions<>(IndexedAIP.class, virtualListId).withFilter(aipFilter) + .withJustActive(justActive).bindOpener().withStartHidden(startHidden)); } - searchWrapper.createListAndSearchPanel(listBuilder, true, true); + } + + if (virtualListBuilder != null) { + searchWrapper.createVirtualListAndSearchPanel(virtualListBuilder, true, true); } } } + // 3. Representations and Files + if (classFilters.containsKey(IndexedRepresentation.class.getSimpleName()) + && PermissionClientUtils.hasPermissions(RodaConstants.PERMISSION_METHOD_FIND_REPRESENTATION)) { + Filter filter = classFilters.get(IndexedRepresentation.class.getSimpleName()); + ListBuilder listBuilder = new ListBuilder<>(() -> new ConfigurableAsyncTableCell<>(), + new AsyncTableCellOptions<>(IndexedRepresentation.class, representationsListId) + .withActionable(RepresentationSearchWrapperActions.getWithoutNoRepresentationActions(null, null)) + .withRedirectOnSingleResult(redirectOnSingleResult).withJustActive(justActive).bindOpener() + .withFilter(filter).withStartHidden(startHidden)); + if (preselectedDropdownValue == null) { + preselectedDropdownValue = IndexedRepresentation.class.getSimpleName(); + } + searchWrapper.createListAndSearchPanel(listBuilder, true, true); + } + + if (classFilters.containsKey(IndexedFile.class.getSimpleName()) + && PermissionClientUtils.hasPermissions(RodaConstants.PERMISSION_METHOD_FIND_FILE)) { + Filter filter = classFilters.get(IndexedFile.class.getSimpleName()); + ListBuilder listBuilder = new ListBuilder<>(() -> new ConfigurableAsyncTableCell<>(), + new AsyncTableCellOptions<>(IndexedFile.class, filesListId) + .withActionable(FileSearchWrapperActions.getWithoutNoFileActions(null, null, null, null, null)) + .withRedirectOnSingleResult(redirectOnSingleResult).withJustActive(justActive).bindOpener() + .withFilter(filter).withStartHidden(startHidden)); + if (preselectedDropdownValue == null) { + preselectedDropdownValue = IndexedFile.class.getSimpleName(); + } + searchWrapper.createListAndSearchPanel(listBuilder, true, true); + } + if (preselectedDropdownValue != null) { searchWrapper.changeDropdownSelectedValue(preselectedDropdownValue); } @@ -134,6 +181,16 @@ public CatalogueSearch(List filterHistoryTokens, boolean justActive, Str initWidget(uiBinder.createAndBindUi(this)); } + private static SimpleFilterParameter parseFieldValueFilter(String filterStr) { + int colonIdx = filterStr.indexOf(':'); + if (colonIdx > 0 && colonIdx < filterStr.length() - 1) { + String field = filterStr.substring(0, colonIdx).trim(); + String value = filterStr.substring(colonIdx + 1).trim(); + return new SimpleFilterParameter(field, value); + } + return null; + } + public CatalogueSearch(boolean justActive, String itemsListId, String representationsListId, String filesListId, Permissions permissions, boolean startHidden, boolean redirectOnSingleResult) { this(Collections.emptyList(), justActive, itemsListId, representationsListId, filesListId, permissions, startHidden, diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/search/SearchPanel.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/search/SearchPanel.java index 3edf6fe007..8812f4a782 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/search/SearchPanel.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/search/SearchPanel.java @@ -201,7 +201,7 @@ private void setupAdvancedSearch(boolean makeAdvancedPanelVisible) { if (advancedSearchEnabled) { searchAdvancedDisclosureButton.addClickHandler(event -> toggleAdvancedSearchPanel()); - advancedSearchFieldsPanel = new AdvancedSearchFieldsPanel(list.getClassToReturn().getSimpleName(), keyCode -> { + advancedSearchFieldsPanel = new AdvancedSearchFieldsPanel(resolveSearchFieldScope(), keyCode -> { if (KeyCodes.KEY_ENTER == keyCode && searchAdvancedGo.isEnabled()) { searchAdvancedGo.click(); } @@ -215,6 +215,12 @@ private void setupAdvancedSearch(boolean makeAdvancedPanelVisible) { } } + private String resolveSearchFieldScope() { + String listId = list.getListId(); + List listIdFields = ConfigurationManager.getStringList(RodaConstants.SEARCH_FIELD_PREFIX, listId); + return !listIdFields.isEmpty() ? listId : list.getClassToReturn().getSimpleName(); + } + /** * Sets the default filter and incremental boolean for the search, shows the * preFilters in the UI if they should be shown according to the constructor diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/search/SearchWrapper.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/search/SearchWrapper.java index 0070d562f4..4808c827eb 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/search/SearchWrapper.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/search/SearchWrapper.java @@ -276,6 +276,42 @@ private void attachComponents(String objectClassSimpleName searchPanel.attachSearchPanelSelectionDropdown(searchPanelSelectionDropdown); } + public SearchWrapper createVirtualListAndSearchPanel(ListBuilder listBuilder, + boolean showSaveButton, boolean hideListAfterClear) { + AsyncTableCell list = listBuilder.build(); + String listId = list.getListId(); + + String labelI18nKey = ConfigurationManager.resolveTranslation(RodaConstants.UI_LISTS_PROPERTY, listId, + RodaConstants.UI_LISTS_CATALOGUE_LABEL_I18N_PROPERTY); + if (labelI18nKey == null) { + labelI18nKey = listId; + } + + String icon = ConfigurationManager.getString(RodaConstants.UI_LISTS_PROPERTY, listId, + RodaConstants.UI_LISTS_CATALOGUE_ICON_PROPERTY); + + Filter filter = list.getFilter(); + String allFilter = SearchFilters.searchField(); + boolean incremental = SearchFilters.shouldBeIncremental(filter); + + SearchPanel searchPanel = new SearchPanel<>(list, filter, allFilter, incremental, + listBuilder.getOptions().getSearchPlaceholder(), hasMultipleSearchPanels, showSaveButton, hideListAfterClear); + + initSearchPanelSelectionDropdown(); + searchPanelSelectionDropdown.addItem(labelI18nKey, listId, icon); + searchPanel.setVisible(true); + + components.putVirtual(listId, searchPanel, list); + + if (mainPanel.getWidgetCount() == 0) { + if (preselectedDropdownValue == null || preselectedDropdownValue.equals(listId)) { + attachComponents(listId); + } + } + + return this; + } + /** * Auxiliary manager for inner components (groups of one searchWrapper and one * BasicAsyncTableCell, at least for now) that is used to enforce type coherence @@ -284,6 +320,8 @@ private void attachComponents(String objectClassSimpleName private class Components { private final Map, SearchPanel> searchPanels = new LinkedHashMap<>(); private final Map, AsyncTableCell> lists = new LinkedHashMap<>(); + private final Map> virtualSearchPanels = new LinkedHashMap<>(); + private final Map> virtualLists = new LinkedHashMap<>(); /** * Add a new set of components associated with a class. @@ -302,6 +340,11 @@ public void put(Class objectClass, SearchPanel searc lists.put(objectClass, list); } + void putVirtual(String listId, SearchPanel searchPanel, AsyncTableCell list) { + virtualSearchPanels.put(listId, searchPanel); + virtualLists.put(listId, list); + } + SearchPanel getSearchPanel(Class objectClass) { return (SearchPanel) searchPanels.get(objectClass); } @@ -310,18 +353,26 @@ AsyncTableCell getList(Class objectClass) { return (AsyncTableCell) lists.get(objectClass); } - SearchPanel getSearchPanel(String className) { - return (SearchPanel) searchPanels.get(classForName(className)); + SearchPanel getSearchPanel(String key) { + if (virtualSearchPanels.containsKey(key)) { + return (SearchPanel) virtualSearchPanels.get(key); + } + return (SearchPanel) searchPanels.get(classForName(key)); } - AsyncTableCell getList(String className) { - return (AsyncTableCell) lists.get(classForName(className)); + AsyncTableCell getList(String key) { + if (virtualLists.containsKey(key)) { + return (AsyncTableCell) virtualLists.get(key); + } + return (AsyncTableCell) lists.get(classForName(key)); } void forEachList(Consumer> action) { for (AsyncTableCell value : lists.values()) { - AsyncTableCell list = (AsyncTableCell) value; - action.accept(list); + action.accept((AsyncTableCell) value); + } + for (AsyncTableCell value : virtualLists.values()) { + action.accept((AsyncTableCell) value); } } diff --git a/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages.properties b/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages.properties index c8a0454390..7f8e2dd42c 100644 --- a/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages.properties +++ b/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages.properties @@ -144,6 +144,10 @@ ui.facets.IndexedAIP.level.series: Series ui.facets.IndexedAIP.disposalScheduleName: Disposal schedule ui.facets.IndexedAIP.destroyedBy: Destroyed by ui.facets.IndexedAIP.instanceName: Instance +ui.facets.IndexedAIP.sender_s: Sender + +ui.catalogue.virtual.Search_emailarchive.label: Search Email Archives +ui.catalogue.virtual.Search_emails.label: Search Emails ui.facets.IndexedFile.fileFormat: Formats ui.facets.IndexedFile.formatPronom: PRONOM IDs diff --git a/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_de_AT.properties b/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_de_AT.properties index 18d713ad8d..0b91f9acea 100644 --- a/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_de_AT.properties +++ b/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_de_AT.properties @@ -141,6 +141,10 @@ ui.facets.IndexedAIP.level.series: Serie ui.facets.IndexedAIP.disposalScheduleName: Skartierungsplan ui.facets.IndexedAIP.destroyedBy: Vernichtet von ui.facets.IndexedAIP.instanceName: Instanz +ui.facets.IndexedAIP.sender_s: Absender + +ui.catalogue.virtual.Search_emailarchive.label: E-Mail-Archive durchsuchen +ui.catalogue.virtual.Search_emails.label: E-Mails durchsuchen ui.facets.IndexedFile.fileFormat: Formate ui.facets.IndexedFile.formatPronom: PRONOM-ID diff --git a/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_de_DE.properties b/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_de_DE.properties index efb88f675b..3a64fb3c19 100644 --- a/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_de_DE.properties +++ b/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_de_DE.properties @@ -141,6 +141,10 @@ ui.facets.IndexedAIP.level.series: Reihe ui.facets.IndexedAIP.disposalScheduleName: Kassationsplan ui.facets.IndexedAIP.destroyedBy: Vernichtet von ui.facets.IndexedAIP.instanceName: Instanz +ui.facets.IndexedAIP.sender_s: Absender + +ui.catalogue.virtual.Search_emailarchive.label: E-Mail-Archive durchsuchen +ui.catalogue.virtual.Search_emails.label: E-Mails durchsuchen ui.facets.IndexedFile.fileFormat: Formate ui.facets.IndexedFile.formatPronom: PRONOM-ID diff --git a/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_es.properties b/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_es.properties index c4e1ab8bb1..983be1d912 100644 --- a/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_es.properties +++ b/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_es.properties @@ -141,6 +141,10 @@ ui.facets.IndexedAIP.level.series: Série ui.facets.IndexedAIP.disposalScheduleName: Calendario de eliminación ui.facets.IndexedAIP.destroyedBy: Destruido por ui.facets.IndexedAIP.instanceName: Instancia +ui.facets.IndexedAIP.sender_s: Remitente + +ui.catalogue.virtual.Search_emailarchive.label: Buscar Archivo de Email +ui.catalogue.virtual.Search_emails.label: Buscar Correos electrónicos ui.facets.IndexedFile.fileFormat: Formatos ui.facets.IndexedFile.formatPronom: IDs PRONOM diff --git a/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_es_CL.properties b/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_es_CL.properties index e807704fc7..cea018d6c3 100644 --- a/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_es_CL.properties +++ b/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_es_CL.properties @@ -339,3 +339,7 @@ email.verification.subject = Registro en RODA email.verification.from = Administrador do RODA email.recoverlogin.subject = Recuperar entrada en RODA email.recoverlogin.from = Administrador do RODA + +ui.facets.IndexedAIP.sender_s: Remitente +ui.catalogue.virtual.Search_emailarchive.label: Buscar Archivo de Email +ui.catalogue.virtual.Search_emails.label: Buscar Correos electrónicos diff --git a/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_hr.properties b/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_hr.properties index e630649e2e..c93d9fb2f4 100644 --- a/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_hr.properties +++ b/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_hr.properties @@ -137,6 +137,10 @@ ui.facets.IndexedAIP.level.other: Ostalo ui.facets.IndexedAIP.level.series: Niz ui.facets.IndexedAIP.disposalScheduleName: Raspored izlučivanja ui.facets.IndexedAIP.destroyedBy: Uništio +ui.facets.IndexedAIP.sender_s: Pošiljatelj + +ui.catalogue.virtual.Search_emailarchive.label: Pretraži Email arhivu +ui.catalogue.virtual.Search_emails.label: Pretraži elektroničku poštu ui.facets.IndexedFile.fileFormat: Formati ui.facets.IndexedFile.formatPronom: PRONOM ID-ovi diff --git a/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_hu.properties b/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_hu.properties index 9926d9b966..6bb5151aa2 100644 --- a/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_hu.properties +++ b/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_hu.properties @@ -141,6 +141,10 @@ ui.facets.IndexedAIP.level.series: Sorozat ui.facets.IndexedAIP.disposalScheduleName: Eltávolítási ütemterv ui.facets.IndexedAIP.destroyedBy: Destroyed by ui.facets.IndexedAIP.instanceName: Példa +ui.facets.IndexedAIP.sender_s: Feladó + +ui.catalogue.virtual.Search_emailarchive.label: Email Archívum keresése +ui.catalogue.virtual.Search_emails.label: Email keresése ui.facets.IndexedFile.fileFormat: Formátumok ui.facets.IndexedFile.formatPronom: PRONOM azonosítók diff --git a/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_pt_BR.properties b/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_pt_BR.properties index da2d641f1f..3602a49481 100644 --- a/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_pt_BR.properties +++ b/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_pt_BR.properties @@ -141,6 +141,10 @@ ui.facets.IndexedAIP.level.series: Série ui.facets.IndexedAIP.disposalScheduleName: Tabela de seleção ui.facets.IndexedAIP.destroyedBy: Eliminado por ui.facets.IndexedAIP.instanceName: Instância +ui.facets.IndexedAIP.sender_s: Remetente + +ui.catalogue.virtual.Search_emailarchive.label: Pesquisar Arquivo de Email +ui.catalogue.virtual.Search_emails.label: Pesquisar Emails ui.facets.IndexedFile.fileFormat: Formatos ui.facets.IndexedFile.formatPronom: PRONOM IDs diff --git a/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_pt_PT.properties b/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_pt_PT.properties index 7fef8a4fee..4d2d7390da 100644 --- a/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_pt_PT.properties +++ b/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_pt_PT.properties @@ -141,6 +141,10 @@ ui.facets.IndexedAIP.level.series: Série ui.facets.IndexedAIP.disposalScheduleName: Tabela de seleção ui.facets.IndexedAIP.destroyedBy: Eliminado por ui.facets.IndexedAIP.instanceName: Instância +ui.facets.IndexedAIP.sender_s: Remetente + +ui.catalogue.virtual.Search_emailarchive.label: Pesquisar Arquivo de Email +ui.catalogue.virtual.Search_emails.label: Pesquisar Emails ui.facets.IndexedFile.fileFormat: Formatos ui.facets.IndexedFile.formatPronom: PRONOM IDs diff --git a/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_sv_SE.properties b/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_sv_SE.properties index d95c0dfc09..5c1581ae40 100644 --- a/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_sv_SE.properties +++ b/roda-ui/roda-wui/src/main/resources/config/i18n/ServerMessages_sv_SE.properties @@ -141,6 +141,10 @@ ui.facets.IndexedAIP.level.series: Serie ui.facets.IndexedAIP.disposalScheduleName: Gallringsschema ui.facets.IndexedAIP.destroyedBy: Gallrad av ui.facets.IndexedAIP.instanceName: Instans +ui.facets.IndexedAIP.sender_s: Avsändare + +ui.catalogue.virtual.Search_emailarchive.label: Sök E-postarkiv +ui.catalogue.virtual.Search_emails.label: Sök E-postmeddelanden ui.facets.IndexedFile.fileFormat: Format ui.facets.IndexedFile.formatPronom: PRONOM IDs diff --git a/roda-ui/roda-wui/src/main/resources/config/roda-wui.properties b/roda-ui/roda-wui/src/main/resources/config/roda-wui.properties index 3499cc7df1..170c63996b 100644 --- a/roda-ui/roda-wui/src/main/resources/config/roda-wui.properties +++ b/roda-ui/roda-wui/src/main/resources/config/roda-wui.properties @@ -75,8 +75,10 @@ ui.sharedProperties.whitelist.configuration.prefix = ui.icons ui.sharedProperties.whitelist.configuration.prefix = ui.lists ui.sharedProperties.whitelist.configuration.prefix = ui.search.fields ui.sharedProperties.whitelist.configuration.property = ui.lists +ui.sharedProperties.whitelist.configuration.prefix = ui.catalogue ui.sharedProperties.whitelist.configuration.prefix = ui.css ui.sharedProperties.whitelist.messages.prefix = ui.facets +ui.sharedProperties.whitelist.messages.prefix = ui.catalogue.virtual ui.sharedProperties.whitelist.messages.prefix = lists.label ui.sharedProperties.whitelist.messages.prefix = ui.search.fields ui.sharedProperties.whitelist.messages.prefix = level @@ -1117,6 +1119,91 @@ ui.lists.Search_AIPs.columns[].default_IndexedAIP_hasrepresentations.width = 3.0 ui.lists.Search_AIPs.defaultSortList.columnName = default_IndexedAIP_dates ui.lists.Search_AIPs.defaultSortList.ascending = false +########################################################################## +# Virtual catalogue configuration +# +# ui.catalogue.virtual +# Array of virtual catalogue list IDs to append to the search/catalogue tabs. +# Each entry corresponds to a ui.lists.{listId} block that must define +# either catalogue.filter (type 1: filtered AIP view) or +# catalogue.childOf.filter (type 2: nested child documents). +# +# ui.lists.{listId}.catalogue.label.i18n +# i18n key whose resolved translation is used as the dropdown label. +# +# ui.lists.{listId}.catalogue.icon +# FontAwesome CSS class for the dropdown icon. +# +# ui.lists.{listId}.catalogue.filter = field:value +# Type 1: filters parent AIP documents by a Solr field:value. +# Standard AIP browse (bindOpener) is used on row click. +# +# ui.lists.{listId}.catalogue.childOf.filter = field:value +# Type 2: returns nested child documents whose parent matches field:value. +# Row click navigates to the parent AIP. +########################################################################## +ui.catalogue.virtual = Search_emailarchive +ui.catalogue.virtual = Search_emails + +## Search_emailarchive — Virtual catalogue: Email Archive AIPs (type 1, filtered) +ui.lists.Search_emailarchive.catalogue.label.i18n = ui.catalogue.virtual.Search_emailarchive.label +ui.lists.Search_emailarchive.catalogue.filter = content_type:emailarchive + +ui.lists.Search_emailarchive.columns[] = title +ui.lists.Search_emailarchive.columns[].title.field = title +ui.lists.Search_emailarchive.columns[].title.header = ui.search.fields.IndexedAIP.title +ui.lists.Search_emailarchive.columns[].title.sortBy = title_sort + +ui.lists.Search_emailarchive.columns[] = default_IndexedAIP_dates +ui.lists.Search_emailarchive.columns[].default_IndexedAIP_dates.header = ui.search.fields.IndexedAIP.dates +ui.lists.Search_emailarchive.columns[].default_IndexedAIP_dates.sortBy = dateInitial +ui.lists.Search_emailarchive.columns[].default_IndexedAIP_dates.sortBy = dateFinal +ui.lists.Search_emailarchive.columns[].default_IndexedAIP_dates.width = 14.0 + +ui.lists.Search_emailarchive.facets.parameters = level +ui.lists.Search_emailarchive.facets.parameters = hasRepresentations +ui.lists.Search_emailarchive.facets.parameters.level.type = SimpleFacetParameter +ui.lists.Search_emailarchive.facets.parameters.hasRepresentations.type = SimpleFacetParameter + +## Search_emails — Virtual catalogue: Individual Emails (type 2, nested child documents) +ui.lists.Search_emails.catalogue.label.i18n = ui.catalogue.virtual.Search_emails.label +ui.lists.Search_emails.catalogue.childOf.filter = content_type:emailarchive + +ui.lists.Search_emails.columns[] = sender_s +ui.lists.Search_emails.columns[].sender_s.field = sender_s +ui.lists.Search_emails.columns[].sender_s.header = ui.search.fields.IndexedAIP.emailSender +ui.lists.Search_emails.columns[].sender_s.sortBy = sender_s + +ui.lists.Search_emails.columns[] = subject_txt +ui.lists.Search_emails.columns[].subject_txt.field = subject_txt +ui.lists.Search_emails.columns[].subject_txt.header = ui.search.fields.IndexedAIP.emailSubject +ui.lists.Search_emails.columns[].subject_txt.sortBy = subject_txt + +ui.lists.Search_emails.columns[] = sentDate_dt +ui.lists.Search_emails.columns[].sentDate_dt.field = sentDate_dt +ui.lists.Search_emails.columns[].sentDate_dt.header = ui.search.fields.IndexedAIP.emailSentDate +ui.lists.Search_emails.columns[].sentDate_dt.sortBy = sentDate_dt +ui.lists.Search_emails.columns[].sentDate_dt.width = 14.0 + +ui.lists.Search_emails.facets.parameters = sender_s +ui.lists.Search_emails.facets.parameters.sender_s.type = SimpleFacetParameter + +ui.search.fields.Search_emails = emailSender +ui.search.fields.Search_emails = emailSubject +ui.search.fields.Search_emails = emailSentDate + +ui.search.fields.Search_emails.emailSender.fields = sender_s +ui.search.fields.Search_emails.emailSender.i18n = ui.search.fields.IndexedAIP.emailSender +ui.search.fields.Search_emails.emailSender.type = text + +ui.search.fields.Search_emails.emailSubject.fields = subject_txt +ui.search.fields.Search_emails.emailSubject.i18n = ui.search.fields.IndexedAIP.emailSubject +ui.search.fields.Search_emails.emailSubject.type = text + +ui.search.fields.Search_emails.emailSentDate.fields = sentDate_dt +ui.search.fields.Search_emails.emailSentDate.i18n = ui.search.fields.IndexedAIP.emailSentDate +ui.search.fields.Search_emails.emailSentDate.type = date_interval + ## BrowseTop_aip ui.lists.BrowseTop_aip.columns[] = default_IndexedAIP_level ui.lists.BrowseTop_aip.columns[].default_IndexedAIP_level.sortBy = level From 4d8a4a748a01accd3931e919478943bc4810a342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Faria?= Date: Thu, 7 May 2026 09:01:08 +0100 Subject: [PATCH 36/37] fix(catalogue): fix email column rendering and enable advanced search in virtual catalogues - Fix LIST rendering hint bug: recursive call passed LIST hint causing ClassCastException when inner values are strings; now passes null - Apply LIST rendering hint to subject_txt so multivalued subjects display cleanly instead of as Java array toString ([value]) - Apply DATETIME_FORMAT_SIMPLE rendering hint to sentDate_dt for human-readable date formatting - Enable advanced search for Search_emailarchive and Search_emails virtual catalogue tabs - Add explicit search fields for Search_emailarchive (title, description, dates) to avoid inheriting nestedType email fields from the IndexedAIP scope fallback Co-Authored-By: Claude Sonnet 4.6 --- .../utils/ConfigurableAsyncTableCell.java | 2 +- .../main/resources/config/roda-wui.properties | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/ConfigurableAsyncTableCell.java b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/ConfigurableAsyncTableCell.java index b44f3bdd90..8afba84fea 100644 --- a/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/ConfigurableAsyncTableCell.java +++ b/roda-ui/roda-wui/src/main/java/org/roda/wui/client/common/lists/utils/ConfigurableAsyncTableCell.java @@ -375,7 +375,7 @@ private String renderValue(Object value, RenderingHint hint) { case DATE_FORMAT_SIMPLE: return Humanize.formatDate((String) value, false); case LIST: - List renderedList = ((List) value).stream().map(v -> renderValue(v, hint)).collect(Collectors.toList()); + List renderedList = ((List) value).stream().map(v -> renderValue(v, null)).collect(Collectors.toList()); return StringUtils.prettyPrint(renderedList); default: return value.toString(); diff --git a/roda-ui/roda-wui/src/main/resources/config/roda-wui.properties b/roda-ui/roda-wui/src/main/resources/config/roda-wui.properties index 170c63996b..a600fece42 100644 --- a/roda-ui/roda-wui/src/main/resources/config/roda-wui.properties +++ b/roda-ui/roda-wui/src/main/resources/config/roda-wui.properties @@ -1148,6 +1148,7 @@ ui.catalogue.virtual = Search_emails ## Search_emailarchive — Virtual catalogue: Email Archive AIPs (type 1, filtered) ui.lists.Search_emailarchive.catalogue.label.i18n = ui.catalogue.virtual.Search_emailarchive.label ui.lists.Search_emailarchive.catalogue.filter = content_type:emailarchive +ui.lists.Search_emailarchive.search.advanced.enabled = true ui.lists.Search_emailarchive.columns[] = title ui.lists.Search_emailarchive.columns[].title.field = title @@ -1165,9 +1166,29 @@ ui.lists.Search_emailarchive.facets.parameters = hasRepresentations ui.lists.Search_emailarchive.facets.parameters.level.type = SimpleFacetParameter ui.lists.Search_emailarchive.facets.parameters.hasRepresentations.type = SimpleFacetParameter +ui.search.fields.Search_emailarchive = title +ui.search.fields.Search_emailarchive = description +ui.search.fields.Search_emailarchive = dates + +ui.search.fields.Search_emailarchive.title.fields = title +ui.search.fields.Search_emailarchive.title.i18n = ui.search.fields.IndexedAIP.title +ui.search.fields.Search_emailarchive.title.type = text +ui.search.fields.Search_emailarchive.title.fixed = true + +ui.search.fields.Search_emailarchive.description.fields = description +ui.search.fields.Search_emailarchive.description.i18n = ui.search.fields.IndexedAIP.description +ui.search.fields.Search_emailarchive.description.type = text +ui.search.fields.Search_emailarchive.description.fixed = false + +ui.search.fields.Search_emailarchive.dates.fields = dateInitial,dateFinal +ui.search.fields.Search_emailarchive.dates.i18n = ui.search.fields.IndexedAIP.dates +ui.search.fields.Search_emailarchive.dates.type = date_interval +ui.search.fields.Search_emailarchive.dates.fixed = false + ## Search_emails — Virtual catalogue: Individual Emails (type 2, nested child documents) ui.lists.Search_emails.catalogue.label.i18n = ui.catalogue.virtual.Search_emails.label ui.lists.Search_emails.catalogue.childOf.filter = content_type:emailarchive +ui.lists.Search_emails.search.advanced.enabled = true ui.lists.Search_emails.columns[] = sender_s ui.lists.Search_emails.columns[].sender_s.field = sender_s @@ -1178,12 +1199,14 @@ ui.lists.Search_emails.columns[] = subject_txt ui.lists.Search_emails.columns[].subject_txt.field = subject_txt ui.lists.Search_emails.columns[].subject_txt.header = ui.search.fields.IndexedAIP.emailSubject ui.lists.Search_emails.columns[].subject_txt.sortBy = subject_txt +ui.lists.Search_emails.columns[].subject_txt.renderingHint = LIST ui.lists.Search_emails.columns[] = sentDate_dt ui.lists.Search_emails.columns[].sentDate_dt.field = sentDate_dt ui.lists.Search_emails.columns[].sentDate_dt.header = ui.search.fields.IndexedAIP.emailSentDate ui.lists.Search_emails.columns[].sentDate_dt.sortBy = sentDate_dt ui.lists.Search_emails.columns[].sentDate_dt.width = 14.0 +ui.lists.Search_emails.columns[].sentDate_dt.renderingHint = DATETIME_FORMAT_SIMPLE ui.lists.Search_emails.facets.parameters = sender_s ui.lists.Search_emails.facets.parameters.sender_s.type = SimpleFacetParameter From e808afd2c8b33277b938db0f6ea9db77b11f5734 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Faria?= Date: Thu, 11 Jun 2026 09:47:27 +0100 Subject: [PATCH 37/37] fix(crosswalk): emit dateStart_dt and dateEnd_dt alongside dateInitial and dateFinal in emailarchive crosswalk Co-Authored-By: Claude Sonnet 4.6 --- .../main/resources/config/crosswalks/ingest/emailarchive.xslt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/roda-core/roda-core/src/main/resources/config/crosswalks/ingest/emailarchive.xslt b/roda-core/roda-core/src/main/resources/config/crosswalks/ingest/emailarchive.xslt index f720ec15e8..a7303a3983 100644 --- a/roda-core/roda-core/src/main/resources/config/crosswalks/ingest/emailarchive.xslt +++ b/roda-core/roda-core/src/main/resources/config/crosswalks/ingest/emailarchive.xslt @@ -40,9 +40,11 @@ T00:00:00Z + T00:00:00Z T00:00:00Z + T00:00:00Z