diff --git a/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementApp.java b/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementApp.java index 55700d2d86..4f55f13e5f 100644 --- a/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementApp.java +++ b/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementApp.java @@ -60,7 +60,8 @@ public String getDisplayName() { @Override public AppInstance create() { List authenticationProviders = - ServiceLoader.load(ServiceAuthenticationProvider.class).stream().map(Provider::get).collect(Collectors.toList()); + ServiceLoader.load(ServiceAuthenticationProvider.class).stream().map(Provider::get) + .collect(Collectors.toList()); try { SecureStore secureStore = new SecureStore(); new CredentialsManagementStage(authenticationProviders, secureStore).show(); diff --git a/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementController.java b/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementController.java index cd1af5cada..1aa73f5786 100644 --- a/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementController.java +++ b/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementController.java @@ -31,6 +31,8 @@ import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.TextField; +import javafx.scene.input.KeyCode; +import javafx.stage.Stage; import javafx.util.Callback; import org.phoebus.framework.jobs.JobManager; import org.phoebus.security.authorization.ServiceAuthenticationProvider; @@ -75,6 +77,8 @@ public class CredentialsManagementController { private static final Logger LOGGER = Logger.getLogger(CredentialsManagementController.class.getName()); private final List authenticationProviders; + private Stage stage; + public CredentialsManagementController(List authenticationProviders, SecureStore secureStore) { this.authenticationProviders = authenticationProviders; this.secureStore = secureStore; @@ -139,6 +143,11 @@ public void logOutFromAll() { } } + /** + * Attempts to sign in user based on provided credentials. If sign-in succeeds, this method will close the + * associated UI. + * @param serviceItem The {@link ServiceItem} defining the scope, and implicitly the authentication service. + */ private void login(ServiceItem serviceItem){ try { serviceItem.getServiceAuthenticationProvider().authenticate(serviceItem.getUsername(), serviceItem.getPassword()); @@ -146,11 +155,10 @@ private void login(ServiceItem serviceItem){ secureStore.setScopedAuthentication(new ScopedAuthenticationToken(serviceItem.getAuthenticationScope(), serviceItem.getUsername(), serviceItem.getPassword())); + stage.close(); } catch (Exception exception) { LOGGER.log(Level.WARNING, "Failed to store credentials", exception); } - updateTable(); - } catch (Exception exception) { LOGGER.log(Level.WARNING, "Failed to login to service", exception); ExceptionDetailsErrorDialog.openError(parent, "Login Failure", "Failed to login to service", exception); @@ -252,7 +260,7 @@ public boolean isLoginAction(){ return loginAction; } } - private static class UsernameTableCell extends TableCell{ + private class UsernameTableCell extends TableCell{ private final TextField textField = new TextField(); public UsernameTableCell(){ @@ -279,7 +287,7 @@ protected void updateItem(String item, final boolean empty) } } - private static class PasswordTableCell extends TableCell{ + private class PasswordTableCell extends TableCell{ private final PasswordField passwordField = new PasswordField(); public PasswordTableCell(){ @@ -301,8 +309,17 @@ protected void updateItem(String item, final boolean empty) // Disable field if user is logged in. passwordField.disableProperty().set(!getTableRow().getItem().loginAction); } + passwordField.setOnKeyPressed(keyEvent -> { + if (keyEvent.getCode() == KeyCode.ENTER) { + CredentialsManagementController.this.login(getTableRow().getItem()); + } + }); setGraphic(passwordField); } } } + + public void setStage(Stage stage){ + this.stage = stage; + } } diff --git a/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementStage.java b/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementStage.java index 6749e1175e..ef51b00f04 100644 --- a/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementStage.java +++ b/app/credentials-management/src/main/java/org/phoebus/applications/credentialsmanagement/CredentialsManagementStage.java @@ -50,6 +50,7 @@ public CredentialsManagementStage(List authentica CredentialsManagementController controller = (CredentialsManagementController) clazz.getConstructor(List.class, SecureStore.class) .newInstance(authenticationProviders, secureStore); + controller.setStage(this); return controller; } catch (Exception e) { diff --git a/app/save-and-restore/app/doc/index.rst b/app/save-and-restore/app/doc/index.rst index 1d85ada7c3..eb90d00997 100644 --- a/app/save-and-restore/app/doc/index.rst +++ b/app/save-and-restore/app/doc/index.rst @@ -11,6 +11,9 @@ The application uses the save-and-restore service deployed on the network such t HTTP(s). The URL of the service is specified in the save-and-restore.properties file, or in the settings file pointed to on the command line. +Actions that create, modify or delete data are protected by the service. User must sign in through the +Crendentials Manager application. See also below. + Nodes and node types -------------------- @@ -392,7 +395,25 @@ The items of this context menu offers actions associated with a PV, which is sim other applications. However, user should be aware that the "Data Browser" item will launch the Data Browser app for the selected PV *around the point in time defined by the PV timestamp*. - - +Authentication and Authorization +-------------------------------- + +Authorization uses a role-based approach like so: + +* Unauthenticated users may read data, i.e. browse the tree and view configurations, snapshots, search and view filters. +* Role "user": + * Create and save configurations + * Create and save snapshots + * Create and save composite snapshots + * Create and save filters + * Update and delete objects if user name matches object's user id and: + * Object is a snapshot and not referenced in a composite snapshot node + * Object is a composite snapshot node + * Object is configuration or folder node with no child nodes + * Object is a filter +* Role "superuser": +perform restore operation +* Role "admin": no restrictions + +Roles are defined and managed on the service. Role (group) membership is managed in Active Directory or LDAP. diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Messages.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Messages.java index 00bd9b1c52..c15a64f8cd 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Messages.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/Messages.java @@ -23,6 +23,7 @@ public class Messages { public static String alertContinue; public static String alertAddingPVsToConfiguration; + public static String authenticationFailed; public static String baseSetpoint; public static String buttonSearch; public static String cannotCompareHeader; @@ -69,9 +70,11 @@ public class Messages { public static String duplicatePVNamesFoundInSelection; public static String editFilter; public static String errorActionFailed; + public static String errorAddTagFailed; public static String errorCreateFolderFailed; public static String errorCreateConfigurationFailed; public static String errorDeleteNodeFailed; + public static String errorDeleteTagFailed; public static String errorGeneric; public static String errorUnableToRetrieveData; public static String exportConfigurationLabel; diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/SaveAndRestoreApplication.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/SaveAndRestoreApplication.java index f87feaf619..eaa346dd26 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/SaveAndRestoreApplication.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/SaveAndRestoreApplication.java @@ -37,6 +37,8 @@ public class SaveAndRestoreApplication implements AppResourceDescriptor { public static final String NAME = "saveandrestore"; public static final String DISPLAY_NAME = "Save And Restore"; + private AppInstance instance; + /** * Custom MIME type definition for the purpose of drag-n-drop in the */ @@ -71,12 +73,18 @@ public AppInstance create(URI uri) { if(uri != null){ ((SaveAndRestoreInstance)tab.getApplication()).openResource(uri); } - return tab.getApplication(); + instance = tab.getApplication(); + return instance; } } } } - return new SaveAndRestoreInstance(this, uri); + instance = new SaveAndRestoreInstance(this, uri); + return instance; + } + + public AppInstance getInstance(){ + return instance; } } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/SaveAndRestoreInstance.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/SaveAndRestoreInstance.java index 4f7a3611f6..ea7c5a5abc 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/SaveAndRestoreInstance.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/SaveAndRestoreInstance.java @@ -24,10 +24,12 @@ import org.phoebus.framework.persistence.Memento; import org.phoebus.framework.spi.AppDescriptor; import org.phoebus.framework.spi.AppInstance; +import org.phoebus.security.tokens.ScopedAuthenticationToken; import org.phoebus.ui.docking.DockItem; import org.phoebus.ui.docking.DockPane; import java.net.URI; +import java.util.List; import java.util.ResourceBundle; import java.util.logging.Level; import java.util.logging.Logger; @@ -35,7 +37,7 @@ public class SaveAndRestoreInstance implements AppInstance { private final AppDescriptor appDescriptor; - private SaveAndRestoreController controller; + private final SaveAndRestoreController saveAndRestoreController; public SaveAndRestoreInstance(AppDescriptor appDescriptor, URI uri) { this.appDescriptor = appDescriptor; @@ -62,9 +64,9 @@ public SaveAndRestoreInstance(AppDescriptor appDescriptor, URI uri) { Logger.getLogger(SaveAndRestoreApplication.class.getName()).log(Level.SEVERE, "Failed loading fxml", e); } - controller = loader.getController(); + saveAndRestoreController = loader.getController(); - tab.setOnCloseRequest(event -> controller.handleTabClosed()); + tab.setOnCloseRequest(event -> saveAndRestoreController.handleTabClosed()); DockPane.getActiveDockPane().addTab(tab); } @@ -76,10 +78,14 @@ public AppDescriptor getAppDescriptor() { @Override public void save(Memento memento) { - controller.saveLocalState(); + saveAndRestoreController.saveLocalState(); } public void openResource(URI uri) { - controller.openResource(uri); + saveAndRestoreController.openResource(uri); + } + + public void secureStoreChanged(List validTokens){ + saveAndRestoreController.secureStoreChanged(validTokens); } } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SaveAndRestoreAuthenticationProvider.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SaveAndRestoreAuthenticationProvider.java new file mode 100644 index 0000000000..1ce2a3e074 --- /dev/null +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SaveAndRestoreAuthenticationProvider.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2020 European Spallation Source ERIC. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +package org.phoebus.applications.saveandrestore.authentication; + +import org.phoebus.applications.saveandrestore.Preferences; +import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreService; +import org.phoebus.security.authorization.ServiceAuthenticationProvider; +import org.phoebus.security.tokens.AuthenticationScope; + +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Authentication provider for the save-and-restore service. + */ +public class SaveAndRestoreAuthenticationProvider implements ServiceAuthenticationProvider { + + @Override + public void authenticate(String username, String password){ + SaveAndRestoreService saveAndRestoreService = SaveAndRestoreService.getInstance(); + try { + saveAndRestoreService.authenticate(username, password); + } catch (Exception e) { + Logger.getLogger(SaveAndRestoreAuthenticationProvider.class.getName()) + .log(Level.WARNING, "Failed to authenticate user " + username + " against save&restore service", e); + throw new RuntimeException(e); + } + } + + @Override + public void logout(String token) { + // Not implemented for save&restore + } + + @Override + public AuthenticationScope getAuthenticationScope(){ + return AuthenticationScope.SAVE_AND_RESTORE; + } + +} diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SecureStoreChangeHandlerImpl.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SecureStoreChangeHandlerImpl.java new file mode 100644 index 0000000000..f2177aed32 --- /dev/null +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/authentication/SecureStoreChangeHandlerImpl.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2020 European Spallation Source ERIC. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +package org.phoebus.applications.saveandrestore.authentication; + +import org.phoebus.applications.saveandrestore.SaveAndRestoreApplication; +import org.phoebus.applications.saveandrestore.SaveAndRestoreInstance; +import org.phoebus.framework.spi.AppDescriptor; +import org.phoebus.framework.workbench.ApplicationService; +import org.phoebus.security.store.SecureStoreChangeHandler; +import org.phoebus.security.tokens.ScopedAuthenticationToken; + +import java.util.List; + +public class SecureStoreChangeHandlerImpl implements SecureStoreChangeHandler { + + /** + * Callback method implementation. + * + * @param validTokens A list of valid {@link ScopedAuthenticationToken}s, i.e. a list of tokens associated + * with scopes where is authenticated. + */ + @Override + public void secureStoreChanged(List validTokens) { + AppDescriptor appDescriptor = ApplicationService.findApplication(SaveAndRestoreApplication.NAME); + if (appDescriptor != null && appDescriptor instanceof SaveAndRestoreApplication) { + SaveAndRestoreApplication saveAndRestoreApplication = (SaveAndRestoreApplication) appDescriptor; + SaveAndRestoreInstance saveAndRestoreInstance = (SaveAndRestoreInstance) saveAndRestoreApplication.getInstance(); + saveAndRestoreInstance.secureStoreChanged(validTokens); + } + } +} diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java index 68abf8616b..a95994721d 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreClient.java @@ -95,8 +95,6 @@ public interface SaveAndRestoreClient { */ Node updateNode(Node nodeToUpdate, boolean customTimeForMigration); - void deleteNode(String uniqueNodeId); - /** * Deletes a list of {@link Node}s * @@ -138,6 +136,14 @@ public interface SaveAndRestoreClient { ConfigurationData getConfigurationData(String nodeId); + /** + * Creates a new {@link Node} of type {@link NodeType#CONFIGURATION} in the remote + * service. + * @param parentNodeId Non-null and non-empty unique id of an existing parent {@link Node}, + * which must be of type {@link NodeType#FOLDER}. + * @param configuration {@link ConfigurationData} object + * @return A representation of the persisted {@link Configuration}. + */ Configuration createConfiguration(String parentNodeId, Configuration configuration); Configuration updateConfiguration(Configuration configuration); @@ -145,7 +151,9 @@ public interface SaveAndRestoreClient { SnapshotData getSnapshotData(String uniqueId); - Snapshot saveSnapshot(String parentNodeId, Snapshot snapshot); + Snapshot createSnapshot(String parentNodeId, Snapshot snapshot); + + Snapshot updateSnapshot(Snapshot snapshot); /** * Creates a new {@link CompositeSnapshot}. @@ -214,4 +222,13 @@ public interface SaveAndRestoreClient { * passed in the tagData parameter. */ List deleteTag(TagData tagData); + + /** + * For the purpose of login and authentication in the service. + * @param userName User's account name + * @param password User's password + * @return A {@link UserData} object if login is successful, otherwise implementation should throw + * an exception. + */ + UserData authenticate(String userName, String password); } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java index 1038eb3804..ae9cf53c92 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/client/SaveAndRestoreJerseyClient.java @@ -25,12 +25,16 @@ import com.sun.jersey.api.client.*; import com.sun.jersey.api.client.config.ClientConfig; import com.sun.jersey.api.client.config.DefaultClientConfig; +import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter; import org.phoebus.applications.saveandrestore.Messages; import org.phoebus.applications.saveandrestore.SaveAndRestoreClientException; import org.phoebus.applications.saveandrestore.model.*; import org.phoebus.applications.saveandrestore.model.search.Filter; import org.phoebus.applications.saveandrestore.model.search.SearchResult; import org.phoebus.framework.preferences.PreferencesReader; +import org.phoebus.security.store.SecureStore; +import org.phoebus.security.tokens.AuthenticationScope; +import org.phoebus.security.tokens.ScopedAuthenticationToken; import javax.ws.rs.core.MultivaluedMap; import java.io.IOException; @@ -40,7 +44,6 @@ public class SaveAndRestoreJerseyClient implements org.phoebus.applications.saveandrestore.client.SaveAndRestoreClient { - private final Client client; private static final String CONTENT_TYPE_JSON = "application/json; charset=UTF-8"; private final Logger logger = Logger.getLogger(SaveAndRestoreJerseyClient.class.getName()); @@ -49,39 +52,64 @@ public class SaveAndRestoreJerseyClient implements org.phoebus.applications.save private static final int DEFAULT_READ_TIMEOUT = 5000; // ms private static final int DEFAULT_CONNECT_TIMEOUT = 3000; // ms + private int httpClientReadTimeout = DEFAULT_READ_TIMEOUT; + private int httpClientConnectTimeout = DEFAULT_CONNECT_TIMEOUT; + + ObjectMapper mapper = new ObjectMapper(); + + private HTTPBasicAuthFilter httpBasicAuthFilter; + public SaveAndRestoreJerseyClient() { + mapper.registerModule(new JavaTimeModule()); + mapper.setSerializationInclusion(Include.NON_NULL); + PreferencesReader preferencesReader = new PreferencesReader(SaveAndRestoreClient.class, "/save_and_restore_preferences.properties"); this.jmasarServiceUrl = preferencesReader.get("jmasar.service.url"); - int httpClientReadTimeout = DEFAULT_READ_TIMEOUT; String readTimeoutString = preferencesReader.get("httpClient.readTimeout"); try { httpClientReadTimeout = Integer.parseInt(readTimeoutString); - logger.log(Level.INFO, "JMasar client using read timeout " + httpClientReadTimeout + " ms"); + logger.log(Level.INFO, "Save&restore client using read timeout " + httpClientReadTimeout + " ms"); } catch (NumberFormatException e) { logger.log(Level.INFO, "Property httpClient.readTimeout \"" + readTimeoutString + "\" is not a number, using default value " + DEFAULT_READ_TIMEOUT + " ms"); } - int httpClientConnectTimeout = DEFAULT_CONNECT_TIMEOUT; String connectTimeoutString = preferencesReader.get("httpClient.connectTimeout"); try { httpClientConnectTimeout = Integer.parseInt(connectTimeoutString); - logger.log(Level.INFO, "JMasar client using connect timeout " + httpClientConnectTimeout + " ms"); + logger.log(Level.INFO, "Save&restore client using connect timeout " + httpClientConnectTimeout + " ms"); } catch (NumberFormatException e) { logger.log(Level.INFO, "Property httpClient.connectTimeout \"" + connectTimeoutString + "\" is not a number, using default value " + DEFAULT_CONNECT_TIMEOUT + " ms"); } + } - + private Client getClient() { DefaultClientConfig defaultClientConfig = new DefaultClientConfig(); defaultClientConfig.getProperties().put(ClientConfig.PROPERTY_READ_TIMEOUT, httpClientReadTimeout); defaultClientConfig.getProperties().put(ClientConfig.PROPERTY_CONNECT_TIMEOUT, httpClientConnectTimeout); - ObjectMapper mapper = new ObjectMapper(); - mapper.registerModule(new JavaTimeModule()); - mapper.setSerializationInclusion(Include.NON_NULL); + JacksonJsonProvider jacksonJsonProvider = new JacksonJsonProvider(mapper); defaultClientConfig.getSingletons().add(jacksonJsonProvider); - client = Client.create(defaultClientConfig); + + Client client = Client.create(defaultClientConfig); + + try { + SecureStore store = new SecureStore(); + ScopedAuthenticationToken scopedAuthenticationToken = store.getScopedAuthenticationToken(AuthenticationScope.SAVE_AND_RESTORE); + if (scopedAuthenticationToken != null) { + String username = scopedAuthenticationToken.getUsername(); + String password = scopedAuthenticationToken.getPassword(); + httpBasicAuthFilter = new HTTPBasicAuthFilter(username, password); + client.addFilter(httpBasicAuthFilter); + } else if (httpBasicAuthFilter != null) { + client.removeFilter(httpBasicAuthFilter); + } + } catch (Exception e) { + logger.log(Level.WARNING, "Unable to retrieve credentials from secure store", e); + } + + return client; } @Override @@ -100,11 +128,11 @@ public Node getNode(String uniqueNodeId) { } @Override - public List getCompositeSnapshotReferencedNodes(String uniqueNodeId){ - WebResource webResource = client.resource(jmasarServiceUrl + "/composite-snapshot/" + uniqueNodeId + "/nodes"); + public List getCompositeSnapshotReferencedNodes(String uniqueNodeId) { + WebResource webResource = getClient().resource(jmasarServiceUrl + "/composite-snapshot/" + uniqueNodeId + "/nodes"); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON).get(ClientResponse.class); - if (response.getStatus() != 200) { + if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { String message; try { message = new String(response.getEntityInputStream().readAllBytes()); @@ -114,16 +142,16 @@ public List getCompositeSnapshotReferencedNodes(String uniqueNodeId){ throw new SaveAndRestoreClientException("Failed : HTTP error code : " + response.getStatus() + ", error message: " + message); } - return response.getEntity(new GenericType>() { + return response.getEntity(new GenericType<>() { }); } @Override - public List getCompositeSnapshotItems(String uniqueNodeId){ - WebResource webResource = client.resource(jmasarServiceUrl + "/composite-snapshot/" + uniqueNodeId + "/items"); + public List getCompositeSnapshotItems(String uniqueNodeId) { + WebResource webResource = getClient().resource(jmasarServiceUrl + "/composite-snapshot/" + uniqueNodeId + "/items"); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON).get(ClientResponse.class); - if (response.getStatus() != 200) { + if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { String message; try { message = new String(response.getEntityInputStream().readAllBytes()); @@ -133,7 +161,7 @@ public List getCompositeSnapshotItems(String uniqueNodeId){ throw new SaveAndRestoreClientException("Failed : HTTP error code : " + response.getStatus() + ", error message: " + message); } - return response.getEntity(new GenericType>() { + return response.getEntity(new GenericType<>() { }); } @@ -145,29 +173,27 @@ public Node getParentNode(String unqiueNodeId) { @Override public List getChildNodes(String uniqueNodeId) throws SaveAndRestoreClientException { ClientResponse response = getCall("/node/" + uniqueNodeId + "/children"); - return response.getEntity(new GenericType>() { + return response.getEntity(new GenericType<>() { }); } @Override public Node createNewNode(String parentNodeId, Node node) { - node.setUserName(getCurrentUsersName()); - WebResource webResource = client.resource(jmasarServiceUrl + "/node").queryParam("parentNodeId", parentNodeId); + WebResource webResource = getClient().resource(jmasarServiceUrl + "/node") + .queryParam("parentNodeId", parentNodeId); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .entity(node, CONTENT_TYPE_JSON) .put(ClientResponse.class); - if (response.getStatus() != 200) { + if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { String message = Messages.createNodeFailed; try { message = new String(response.getEntityInputStream().readAllBytes()); } catch (IOException e) { - // Ignore + logger.log(Level.WARNING, "Unable to parse response", e); } throw new SaveAndRestoreClientException(message); } - return response.getEntity(Node.class); - } @Override @@ -177,22 +203,19 @@ public Node updateNode(Node nodeToUpdate) { @Override public Node updateNode(Node nodeToUpdate, boolean customTimeForMigration) { - if (nodeToUpdate.getUserName() == null || nodeToUpdate.getUserName().isEmpty()) { - nodeToUpdate.setUserName(getCurrentUsersName()); - } - WebResource webResource = client.resource(jmasarServiceUrl + "/node") + WebResource webResource = getClient().resource(jmasarServiceUrl + "/node") .queryParam("customTimeForMigration", customTimeForMigration ? "true" : "false"); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .entity(nodeToUpdate, CONTENT_TYPE_JSON) .post(ClientResponse.class); - if (response.getStatus() != 200) { + if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { String message = Messages.updateNodeFailed; try { message = new String(response.getEntityInputStream().readAllBytes()); } catch (IOException e) { - // Ignore + logger.log(Level.WARNING, "Unable to parse response", e); } throw new SaveAndRestoreClientException(message); } @@ -201,16 +224,15 @@ public Node updateNode(Node nodeToUpdate, boolean customTimeForMigration) { } private T getCall(String relativeUrl, Class clazz) { - ClientResponse response = getCall(relativeUrl); return response.getEntity(clazz); } private ClientResponse getCall(String relativeUrl) { - WebResource webResource = client.resource(jmasarServiceUrl + relativeUrl); + WebResource webResource = getClient().resource(jmasarServiceUrl + relativeUrl); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON).get(ClientResponse.class); - if (response.getStatus() != 200) { + if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { String message; try { message = new String(response.getEntityInputStream().readAllBytes()); @@ -224,55 +246,47 @@ private ClientResponse getCall(String relativeUrl) { } @Override - public void deleteNode(String uniqueNodeId) { - WebResource webResource = client.resource(jmasarServiceUrl + "/node/" + uniqueNodeId); - ClientResponse response = webResource.accept(CONTENT_TYPE_JSON).delete(ClientResponse.class); - if (response.getStatus() != 200) { + public void deleteNodes(List nodeIds) { + WebResource webResource = getClient().resource(jmasarServiceUrl + "/node"); + ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) + .entity(nodeIds, CONTENT_TYPE_JSON) + .delete(ClientResponse.class); + if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { String message = response.getEntity(String.class); throw new SaveAndRestoreClientException("Failed : HTTP error code : " + response.getStatus() + ", error message: " + message); } } - @Override - public void deleteNodes(List nodeIds) { - nodeIds.forEach(this::deleteNode); - } - - private String getCurrentUsersName() { - return System.getProperty("user.name"); - } - @Override public List getAllTags() { ClientResponse response = getCall("/tags"); - return response.getEntity(new GenericType>() { + return response.getEntity(new GenericType<>() { }); } @Override public List getAllSnapshots() { ClientResponse response = getCall("/snapshots"); - return response.getEntity(new GenericType>() { + return response.getEntity(new GenericType<>() { }); } @Override public Node moveNodes(List sourceNodeIds, String targetNodeId) { WebResource webResource = - client.resource(jmasarServiceUrl + "/move") - .queryParam("to", targetNodeId) - .queryParam("username", getCurrentUsersName()); + getClient().resource(jmasarServiceUrl + "/move") + .queryParam("to", targetNodeId); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .entity(sourceNodeIds, CONTENT_TYPE_JSON) .post(ClientResponse.class); - if (response.getStatus() != 200) { + if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { String message = Messages.copyOrMoveNotAllowedBody; try { message = new String(response.getEntityInputStream().readAllBytes()); } catch (IOException e) { - // Ignore + logger.log(Level.WARNING, "Unable to parse response", e); } throw new SaveAndRestoreClientException(message); } @@ -282,20 +296,19 @@ public Node moveNodes(List sourceNodeIds, String targetNodeId) { @Override public Node copyNodes(List sourceNodeIds, String targetNodeId) { WebResource webResource = - client.resource(jmasarServiceUrl + "/copy") - .queryParam("to", targetNodeId) - .queryParam("username", getCurrentUsersName()); + getClient().resource(jmasarServiceUrl + "/copy") + .queryParam("to", targetNodeId); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .entity(sourceNodeIds, CONTENT_TYPE_JSON) .post(ClientResponse.class); - if (response.getStatus() != 200) { + if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { String message = Messages.copyOrMoveNotAllowedBody; try { message = new String(response.getEntityInputStream().readAllBytes()); } catch (IOException e) { - // Ignore + logger.log(Level.WARNING, "Unable to parse response", e); } throw new SaveAndRestoreClientException(message); } @@ -305,10 +318,10 @@ public Node copyNodes(List sourceNodeIds, String targetNodeId) { @Override public String getFullPath(String uniqueNodeId) { WebResource webResource = - client.resource(jmasarServiceUrl + "/path/" + uniqueNodeId); + getClient().resource(jmasarServiceUrl + "/path/" + uniqueNodeId); ClientResponse response = webResource.get(ClientResponse.class); - if (response.getStatus() != 200) { + if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { return null; } return response.getEntity(String.class); @@ -327,19 +340,18 @@ public ConfigurationData getConfigurationData(String nodeId) { @Override public Configuration createConfiguration(String parentNodeId, Configuration configuration) { - configuration.getConfigurationNode().setUserName(getCurrentUsersName()); WebResource webResource = - client.resource(jmasarServiceUrl + "/config") + getClient().resource(jmasarServiceUrl + "/config") .queryParam("parentNodeId", parentNodeId); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .entity(configuration, CONTENT_TYPE_JSON) .put(ClientResponse.class); - if (response.getStatus() != 200) { + if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { String message = Messages.createConfigurationFailed; try { message = new String(response.getEntityInputStream().readAllBytes()); } catch (IOException e) { - // Ignore + logger.log(Level.WARNING, "Unable to parse response", e); } throw new SaveAndRestoreClientException(message); } @@ -348,17 +360,17 @@ public Configuration createConfiguration(String parentNodeId, Configuration conf @Override public Configuration updateConfiguration(Configuration configuration) { - WebResource webResource = client.resource(jmasarServiceUrl + "/config"); + WebResource webResource = getClient().resource(jmasarServiceUrl + "/config"); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .entity(configuration, CONTENT_TYPE_JSON) .post(ClientResponse.class); - if (response.getStatus() != 200) { + if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { String message = Messages.updateConfigurationFailed; try { message = new String(response.getEntityInputStream().readAllBytes()); } catch (IOException e) { - // Ignore + logger.log(Level.WARNING, "Unable to parse response", e); } throw new RuntimeException(message); } @@ -372,10 +384,9 @@ public SnapshotData getSnapshotData(String nodeId) { } @Override - public Snapshot saveSnapshot(String parentNodeId, Snapshot snapshot) { - snapshot.getSnapshotNode().setUserName(getCurrentUsersName()); + public Snapshot createSnapshot(String parentNodeId, Snapshot snapshot) { WebResource webResource = - client.resource(jmasarServiceUrl + "/snapshot") + getClient().resource(jmasarServiceUrl + "/snapshot") .queryParam("parentNodeId", parentNodeId); ClientResponse response; try { @@ -384,36 +395,58 @@ public Snapshot saveSnapshot(String parentNodeId, Snapshot snapshot) { .put(ClientResponse.class); } catch (UniformInterfaceException e) { throw new RuntimeException(e); - } catch (ClientHandlerException e) { + } + if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { + String message = Messages.searchFailed; + try { + message = new String(response.getEntityInputStream().readAllBytes()); + } catch (IOException e) { + logger.log(Level.WARNING, "Unable to parse response", e); + } + throw new SaveAndRestoreClientException(message); + } + return response.getEntity(Snapshot.class); + } + + @Override + public Snapshot updateSnapshot(Snapshot snapshot) { + WebResource webResource = + getClient().resource(jmasarServiceUrl + "/snapshot"); + ClientResponse response; + try { + response = webResource.accept(CONTENT_TYPE_JSON) + .entity(snapshot, CONTENT_TYPE_JSON) + .post(ClientResponse.class); + } catch (UniformInterfaceException e) { throw new RuntimeException(e); } - if (response.getStatus() != 200) { + if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { String message = Messages.searchFailed; try { message = new String(response.getEntityInputStream().readAllBytes()); } catch (IOException e) { - // Ignore + logger.log(Level.WARNING, "Unable to parse response", e); } throw new SaveAndRestoreClientException(message); } return response.getEntity(Snapshot.class); } + @Override - public CompositeSnapshot createCompositeSnapshot(String parentNodeId, CompositeSnapshot compositeSnapshot){ - compositeSnapshot.getCompositeSnapshotNode().setUserName(getCurrentUsersName()); + public CompositeSnapshot createCompositeSnapshot(String parentNodeId, CompositeSnapshot compositeSnapshot) { WebResource webResource = - client.resource(jmasarServiceUrl + "/composite-snapshot") + getClient().resource(jmasarServiceUrl + "/composite-snapshot") .queryParam("parentNodeId", parentNodeId); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .entity(compositeSnapshot, CONTENT_TYPE_JSON) .put(ClientResponse.class); - if (response.getStatus() != 200) { + if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { String message = Messages.createConfigurationFailed; try { message = new String(response.getEntityInputStream().readAllBytes()); } catch (IOException e) { - // Ignore + logger.log(Level.WARNING, "Unable to parse response", e); } throw new SaveAndRestoreClientException(message); } @@ -421,38 +454,38 @@ public CompositeSnapshot createCompositeSnapshot(String parentNodeId, CompositeS } @Override - public List checkCompositeSnapshotConsistency(List snapshotNodeIds){ + public List checkCompositeSnapshotConsistency(List snapshotNodeIds) { WebResource webResource = - client.resource(jmasarServiceUrl + "/composite-snapshot-consistency-check"); + getClient().resource(jmasarServiceUrl + "/composite-snapshot-consistency-check"); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .entity(snapshotNodeIds, CONTENT_TYPE_JSON) .post(ClientResponse.class); - if (response.getStatus() != 200) { + if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { String message = Messages.compositeSnapshotConsistencyCheckFailed; try { message = new String(response.getEntityInputStream().readAllBytes()); } catch (IOException e) { - // Ignore + logger.log(Level.WARNING, "Unable to parse response", e); } throw new SaveAndRestoreClientException(message); } - return response.getEntity(new GenericType>() { + return response.getEntity(new GenericType<>() { }); } @Override - public CompositeSnapshot updateCompositeSnapshot(CompositeSnapshot compositeSnapshot){ - WebResource webResource = client.resource(jmasarServiceUrl + "/composite-snapshot"); + public CompositeSnapshot updateCompositeSnapshot(CompositeSnapshot compositeSnapshot) { + WebResource webResource = getClient().resource(jmasarServiceUrl + "/composite-snapshot"); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .entity(compositeSnapshot, CONTENT_TYPE_JSON) .post(ClientResponse.class); - if (response.getStatus() != 200) { + if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { String message = Messages.updateConfigurationFailed; try { message = new String(response.getEntityInputStream().readAllBytes()); } catch (IOException e) { - // Ignore + logger.log(Level.WARNING, "Unable to parse response", e); } throw new RuntimeException(message); } @@ -460,17 +493,17 @@ public CompositeSnapshot updateCompositeSnapshot(CompositeSnapshot compositeSnap } @Override - public SearchResult search(MultivaluedMap searchParams){ - WebResource webResource = client.resource(jmasarServiceUrl + "/search") + public SearchResult search(MultivaluedMap searchParams) { + WebResource webResource = getClient().resource(jmasarServiceUrl + "/search") .queryParams(searchParams); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .get(ClientResponse.class); - if (response.getStatus() != 200) { + if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { String message = Messages.searchFailed; try { message = new String(response.getEntityInputStream().readAllBytes()); } catch (IOException e) { - // Ignore + logger.log(Level.WARNING, "Unable to parse response", e); } throw new RuntimeException(message); } @@ -478,19 +511,17 @@ public SearchResult search(MultivaluedMap searchParams){ } @Override - public Filter saveFilter(Filter filter){ - filter.setUser(getCurrentUsersName()); - WebResource webResource = client.resource(jmasarServiceUrl + "/filter"); - + public Filter saveFilter(Filter filter) { + WebResource webResource = getClient().resource(jmasarServiceUrl + "/filter"); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .entity(filter, CONTENT_TYPE_JSON) .put(ClientResponse.class); - if (response.getStatus() != 200) { + if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { String message = Messages.saveFilterFailed; try { message = new String(response.getEntityInputStream().readAllBytes()); } catch (IOException e) { - // Ignore + logger.log(Level.WARNING, "Unable to parse response", e); } throw new RuntimeException(message); } @@ -498,49 +529,52 @@ public Filter saveFilter(Filter filter){ } @Override - public List getAllFilters(){ - WebResource webResource = client.resource(jmasarServiceUrl + "/filters"); + public List getAllFilters() { + WebResource webResource = getClient().resource(jmasarServiceUrl + "/filters"); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .get(ClientResponse.class); - if (response.getStatus() != 200) { + if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { String message = Messages.searchFailed; try { message = new String(response.getEntityInputStream().readAllBytes()); } catch (IOException e) { - // Ignore + logger.log(Level.WARNING, "Unable to parse response", e); } throw new RuntimeException(message); } - return response.getEntity(new GenericType>(){}); + return response.getEntity(new GenericType<>() { + }); } @Override - public void deleteFilter(String name){ + public void deleteFilter(String name) { // Filter name may contain space chars, need to URL encode these. String filterName = name.replace(" ", "%20"); - WebResource webResource = client.resource(jmasarServiceUrl + "/filter/" + filterName); + WebResource webResource = getClient().resource(jmasarServiceUrl + "/filter/" + filterName); ClientResponse response = webResource.accept(CONTENT_TYPE_JSON) .delete(ClientResponse.class); - if (response.getStatus() != 200) { + if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { String message = Messages.deleteFilterFailed; try { message = new String(response.getEntityInputStream().readAllBytes()); } catch (IOException e) { - // Ignore + logger.log(Level.WARNING, "Unable to parse response", e); } throw new RuntimeException(message); } } /** - * Adds a tag to a list of unique node ids, see {@link TagData} + * Adds a tag to a list of unique node ids, see {@link TagData}. + * * @param tagData see {@link TagData} * @return A list of updated {@link Node}s. This may contain fewer elements than the list of unique node ids * passed in the tagData parameter. */ - public List addTag(TagData tagData){ + public List addTag(TagData tagData) { + WebResource webResource = - client.resource(jmasarServiceUrl + "/tags"); + getClient().resource(jmasarServiceUrl + "/tags"); ClientResponse response; try { response = webResource.accept(CONTENT_TYPE_JSON) @@ -548,31 +582,30 @@ public List addTag(TagData tagData){ .post(ClientResponse.class); } catch (UniformInterfaceException e) { throw new RuntimeException(e); - } catch (ClientHandlerException e) { - throw new RuntimeException(e); } - if (response.getStatus() != 200) { + if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { String message = Messages.tagAddFailed; try { message = new String(response.getEntityInputStream().readAllBytes()); } catch (IOException e) { - // Ignore + logger.log(Level.WARNING, "Unable to parse response", e); } throw new SaveAndRestoreClientException(message); } - return response.getEntity(new GenericType>() { + return response.getEntity(new GenericType<>() { }); } /** * Deletes a tag from a list of unique node ids, see {@link TagData} + * * @param tagData see {@link TagData} * @return A list of updated {@link Node}s. This may contain fewer elements than the list of unique node ids * passed in the tagData parameter. */ - public List deleteTag(TagData tagData){ + public List deleteTag(TagData tagData) { WebResource webResource = - client.resource(jmasarServiceUrl + "/tags"); + getClient().resource(jmasarServiceUrl + "/tags"); ClientResponse response; try { response = webResource.accept(CONTENT_TYPE_JSON) @@ -580,19 +613,43 @@ public List deleteTag(TagData tagData){ .delete(ClientResponse.class); } catch (UniformInterfaceException e) { throw new RuntimeException(e); - } catch (ClientHandlerException e) { - throw new RuntimeException(e); } - if (response.getStatus() != 200) { + if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { String message = Messages.tagAddFailed; try { message = new String(response.getEntityInputStream().readAllBytes()); } catch (IOException e) { - // Ignore + logger.log(Level.WARNING, "Unable to parse response", e); + } + throw new SaveAndRestoreClientException(message); + } + return response.getEntity(new GenericType<>() { + }); + } + + @Override + public UserData authenticate(String userName, String password) { + WebResource webResource = + getClient().resource(jmasarServiceUrl + "/login") + .queryParam("username", userName) + .queryParam("password", password); + ClientResponse response; + try { + response = webResource.accept(CONTENT_TYPE_JSON) + .post(ClientResponse.class); + } catch (UniformInterfaceException e) { + throw new RuntimeException(e); + } + if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) { + String message = Messages.authenticationFailed; + try { + message = new String(response.getEntityInputStream().readAllBytes()); + } catch (IOException e) { + logger.log(Level.WARNING, "Unable to parse response", e); } throw new SaveAndRestoreClientException(message); } - return response.getEntity(new GenericType>() { + return response.getEntity(new GenericType<>() { }); } } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/BrowserTreeCell.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/BrowserTreeCell.java index 799b695d82..a90b98c556 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/BrowserTreeCell.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/BrowserTreeCell.java @@ -18,21 +18,13 @@ package org.phoebus.applications.saveandrestore.ui; -import javafx.scene.control.ContextMenu; -import javafx.scene.control.Label; -import javafx.scene.control.Tooltip; -import javafx.scene.control.TreeCell; -import javafx.scene.control.TreeItem; +import javafx.application.Platform; +import javafx.scene.control.*; import javafx.scene.image.ImageView; -import javafx.scene.input.ClipboardContent; -import javafx.scene.input.Dragboard; -import javafx.scene.input.TransferMode; -import javafx.scene.layout.Border; -import javafx.scene.layout.BorderStroke; -import javafx.scene.layout.BorderStrokeStyle; -import javafx.scene.layout.CornerRadii; -import javafx.scene.layout.HBox; +import javafx.scene.input.*; +import javafx.scene.layout.*; import javafx.scene.paint.Color; +import org.phoebus.applications.saveandrestore.Messages; import org.phoebus.applications.saveandrestore.SaveAndRestoreApplication; import org.phoebus.applications.saveandrestore.model.Node; import org.phoebus.applications.saveandrestore.model.NodeType; @@ -49,29 +41,16 @@ */ public class BrowserTreeCell extends TreeCell { - private final ContextMenu folderContextMenu; - private final ContextMenu configurationContextMenu; - private final ContextMenu snapshotContextMenu; - private final ContextMenu rootFolderContextMenu; - private final ContextMenu compositeSnapshotContextMenu; private final SaveAndRestoreController saveAndRestoreController; private static final Border BORDER = new Border(new BorderStroke(Color.GREEN, BorderStrokeStyle.SOLID, new CornerRadii(5.0), BorderStroke.THIN)); public BrowserTreeCell() { - this(null, null, null, null, null, null); + this(null); } - public BrowserTreeCell(ContextMenu folderContextMenu, ContextMenu configurationContextMenu, - ContextMenu snapshotContextMenu, ContextMenu rootFolderContextMenu, - ContextMenu compositeSnapshotContextMenu, - SaveAndRestoreController saveAndRestoreController) { - this.folderContextMenu = folderContextMenu; - this.configurationContextMenu = configurationContextMenu; - this.snapshotContextMenu = snapshotContextMenu; - this.rootFolderContextMenu = rootFolderContextMenu; - this.compositeSnapshotContextMenu = compositeSnapshotContextMenu; + public BrowserTreeCell(SaveAndRestoreController saveAndRestoreController) { this.saveAndRestoreController = saveAndRestoreController; // This is need in order to suppress the context menu when right-clicking in a portion of the @@ -86,7 +65,7 @@ public BrowserTreeCell(ContextMenu folderContextMenu, ContextMenu configurationC }); setOnDragDetected(event -> { - if (!saveAndRestoreController.checkMultipleSelection()) { + if (saveAndRestoreController.getUserIdentity().isNull().get() || !saveAndRestoreController.selectedNodesOfSameType()) { return; } final ClipboardContent content = new ClipboardContent(); @@ -110,9 +89,9 @@ public BrowserTreeCell(ContextMenu folderContextMenu, ContextMenu configurationC setOnDragOver(event -> { final Node node = getItem(); - if(node != null){ + if (node != null) { List sourceNodes = (List) event.getDragboard().getContent(SaveAndRestoreApplication.NODE_SELECTION_FORMAT); - if(DragNDropUtil.mayDrop(event.getTransferMode(), node, sourceNodes)){ + if (DragNDropUtil.mayDrop(event.getTransferMode(), node, sourceNodes)) { event.acceptTransferModes(event.getTransferMode()); setBorder(BORDER); } @@ -145,12 +124,19 @@ public BrowserTreeCell(ContextMenu folderContextMenu, ContextMenu configurationC event.setDropCompleted(true); event.consume(); }); + + // This is to suppress expansion of the TreeItem on double-click. + addEventFilter(MouseEvent.MOUSE_PRESSED, (MouseEvent e) -> { + if (e.getClickCount() % 2 == 0 && e.getButton().equals(MouseButton.PRIMARY)) + e.consume(); + }); } @Override public void updateItem(Node node, boolean empty) { super.updateItem(node, empty); if (empty) { + setText(null); setGraphic(null); setTooltip(null); getStyleClass().remove("filter-match"); @@ -158,7 +144,7 @@ public void updateItem(Node node, boolean empty) { } // Use custom layout as this makes it easier to set opacity HBox hBox = new HBox(); - // saveAndRestoreController is null if configuration management is accessed from context menu + // saveAndRestoreController is null if configuration management is from OPI or channel table if (saveAndRestoreController != null && !saveAndRestoreController.matchesFilter(node)) { hBox.setOpacity(0.4); } @@ -168,7 +154,8 @@ public void updateItem(Node node, boolean empty) { stringBuilder.append(comment).append(System.lineSeparator()); } if (node.getCreated() != null) { // Happens if configuration management is accessed from context menu - stringBuilder.append(TimestampFormats.SECONDS_FORMAT.format(node.getCreated().toInstant())).append(" (").append(node.getUserName()).append(")"); + stringBuilder.append(TimestampFormats.SECONDS_FORMAT + .format(node.getLastModified() != null ? node.getLastModified().toInstant() : node.getCreated().toInstant())).append(" (").append(node.getUserName()).append(")"); } // Tooltip with at least date and user id is set on all tree items setTooltip(new Tooltip(stringBuilder.toString())); @@ -186,7 +173,9 @@ public void updateItem(Node node, boolean empty) { tagImage.setPreserveRatio(true); hBox.getChildren().add(tagImage); } - setContextMenu(snapshotContextMenu); + if(saveAndRestoreController != null){ + setContextMenu(new ContextMenuSnapshot(saveAndRestoreController)); + } break; case COMPOSITE_SNAPSHOT: hBox.getChildren().add(new ImageView(ImageRepository.COMPOSITE_SNAPSHOT)); @@ -195,25 +184,40 @@ public void updateItem(Node node, boolean empty) { ImageView tagImage = new ImageView(ImageCache.getImage(BrowserTreeCell.class, "/icons/save-and-restore/snapshot-tags.png")); tagImage.setFitHeight(13); tagImage.setPreserveRatio(true); - hBox.getChildren().add(tagImage); + getChildren().add(tagImage); + } + if(saveAndRestoreController != null){ + setContextMenu(new ContextMenuCompositeSnapshot(saveAndRestoreController)); } - setContextMenu(compositeSnapshotContextMenu); break; case CONFIGURATION: hBox.getChildren().add(new ImageView(ImageRepository.CONFIGURATION)); hBox.getChildren().add(new Label(node.getName())); - setContextMenu(configurationContextMenu); + if(saveAndRestoreController != null){ + setContextMenu(new ContextMenuConfiguration(saveAndRestoreController)); + } break; case FOLDER: - String name = node.getName(); + hBox.getChildren().add(new ImageView(ImageRepository.FOLDER)); + hBox.getChildren().add(new Label(node.getName())); if (node.getUniqueId().equals(Node.ROOT_FOLDER_UNIQUE_ID)) { - setContextMenu(rootFolderContextMenu); - name += " (" + SaveAndRestoreService.getInstance().getServiceIdentifier() + ")"; - } else { - setContextMenu(folderContextMenu); + setTooltip(new Tooltip(SaveAndRestoreService.getInstance().getServiceIdentifier())); + ContextMenu rootFolderContextMenu = new ContextMenu(); + MenuItem newRootFolderMenuItem = new MenuItem(Messages.contextMenuNewFolder, new ImageView(ImageRepository.FOLDER)); + newRootFolderMenuItem.setOnAction(ae -> saveAndRestoreController.createNewFolder()); + rootFolderContextMenu.getItems().add(newRootFolderMenuItem); + rootFolderContextMenu.setOnShowing(event -> { + if (saveAndRestoreController.getUserIdentity().isNull().get()) { + Platform.runLater(() -> rootFolderContextMenu.hide()); + } + }); + if(saveAndRestoreController != null){ + setContextMenu(rootFolderContextMenu); + } + + } else if (saveAndRestoreController != null){ + setContextMenu(new ContextMenuFolder(saveAndRestoreController)); } - hBox.getChildren().add(new ImageView(ImageRepository.FOLDER)); - hBox.getChildren().add(new Label(name)); break; } setGraphic(hBox); diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java index 045d8009f8..e904215ee2 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuBase.java @@ -18,22 +18,19 @@ package org.phoebus.applications.saveandrestore.ui; -import javafx.application.Platform; +import javafx.beans.binding.Bindings; import javafx.beans.property.SimpleBooleanProperty; -import javafx.collections.ListChangeListener; -import javafx.collections.ObservableList; -import javafx.event.EventHandler; import javafx.scene.control.ContextMenu; import javafx.scene.control.MenuItem; -import javafx.scene.control.TreeItem; -import javafx.scene.control.TreeView; import javafx.scene.image.Image; import javafx.scene.image.ImageView; -import javafx.scene.input.ContextMenuEvent; import org.phoebus.applications.saveandrestore.Messages; import org.phoebus.applications.saveandrestore.model.Node; import org.phoebus.ui.javafx.ImageCache; +/** + * Abstract base class for context menus. + */ public abstract class ContextMenuBase extends ContextMenu { protected Image csvImportIcon = ImageCache.getImage(SaveAndRestoreController.class, "/icons/csv_import.png"); @@ -41,71 +38,73 @@ public abstract class ContextMenuBase extends ContextMenu { protected MenuItem deleteNodesMenuItem; protected MenuItem copyUniqueIdToClipboardMenuItem; - protected TreeView treeView; - - protected SimpleBooleanProperty multipleSelection = new SimpleBooleanProperty(); + protected SimpleBooleanProperty multipleNodesSelectedProperty = new SimpleBooleanProperty(); protected SaveAndRestoreController saveAndRestoreController; - public ContextMenuBase(SaveAndRestoreController saveAndRestoreController, TreeView treeView) { - this.treeView = treeView; + /** + * Property showing if user has signed in or not. Context menus should in onShowing + * check the sign-in status and set the property accordingly to determine which + * menu items to disable (e.g. create or delete data). + */ + protected SimpleBooleanProperty userIsAuthenticatedProperty = + new SimpleBooleanProperty(); + + /** + * Property showing if selected {@link Node}s have the same parent {@link Node}. Context menus should + * in onShowing check the selection and determine which menu items to disable. + */ + protected SimpleBooleanProperty hasSameParentProperty = + new SimpleBooleanProperty(); + + /** + * Property updated based on check of multiple {@link Node} selection, + * e.g. selection of different type of {@link Node}s. Context menus should + * in onShowing check the selection and determine which menu items to disable. + */ + protected SimpleBooleanProperty nodesOfSameTypeProperty = + new SimpleBooleanProperty(); + + protected SimpleBooleanProperty mayPasteProperty = + new SimpleBooleanProperty(); + + protected SimpleBooleanProperty mayCopyProperty = + new SimpleBooleanProperty(); + + public ContextMenuBase(SaveAndRestoreController saveAndRestoreController) { this.saveAndRestoreController = saveAndRestoreController; deleteNodesMenuItem = new MenuItem(Messages.contextMenuDelete, new ImageView(ImageRepository.DELETE)); deleteNodesMenuItem.setOnAction(ae -> saveAndRestoreController.deleteNodes()); + deleteNodesMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() -> + userIsAuthenticatedProperty.not().get() || + hasSameParentProperty.not().get(), + userIsAuthenticatedProperty, hasSameParentProperty)); copyUniqueIdToClipboardMenuItem = new MenuItem(Messages.copyUniqueIdToClipboard, ImageCache.getImageView(ImageCache.class, "/icons/copy.png")); copyUniqueIdToClipboardMenuItem.setOnAction(ae -> saveAndRestoreController.copyUniqueNodeIdToClipboard()); - copyUniqueIdToClipboardMenuItem.disableProperty().bind(multipleSelection); - - treeView.getSelectionModel().getSelectedItems() - .addListener((ListChangeListener>) c -> - multipleSelection.set(treeView.getSelectionModel().getSelectedItems().size() > 1)); - - setOnShowing(event -> { - if(!saveAndRestoreController.checkMultipleSelection()){ - Platform.runLater(() -> hide()); - } - else{ - runChecks(); - } - }); + copyUniqueIdToClipboardMenuItem.disableProperty().bind(multipleNodesSelectedProperty); + + // Controller may be null, e.g. when adding PVs from channel table + if(saveAndRestoreController != null){ + setOnShowing(event -> runChecks()); + } } /** - * Applies logic to determine which context menu items to disable as some actions (e.g. rename) do not - * make sense if multiple items are selected. Special case is if nodes in different parent nodes - * are selected, in this case none of the menu items make sense, to the context menu is suppressed. + * Applies logic to determine if the user is authenticated and if multiple {@link Node}s have been selected. + * Subclasses use this to disable menu items if needed, e.g. disable delete if user has not signed in. */ protected void runChecks() { - ObservableList> selected = - treeView.getSelectionModel().getSelectedItems(); - if (multipleSelection.get() && !hasSameParent(selected)) { - deleteNodesMenuItem.disableProperty().set(true); + userIsAuthenticatedProperty.set(saveAndRestoreController.getUserIdentity().isNotNull().get()); + boolean multipleNodesSelected = saveAndRestoreController.multipleNodesSelected(); + multipleNodesSelectedProperty.set(multipleNodesSelected); + if (multipleNodesSelected) { // No need to check this if only one node was selected + hasSameParentProperty.set(saveAndRestoreController.hasSameParent()); + nodesOfSameTypeProperty.set(saveAndRestoreController.selectedNodesOfSameType()); } else { - deleteNodesMenuItem.disableProperty().set(false); - } - } - - /** - * Used to determine if nodes selected in the tree view have the same parent node. Most menu items - * do not make sense unless the selected nodes have same the parent node. - * - * @param selectedItems The selected tree nodes. - * @return true if all selected nodes have the same parent node, false otherwise. - */ - protected boolean hasSameParent(ObservableList> selectedItems) { - if (selectedItems.size() == 1) { - return true; - } - Node parentNodeOfFirst = selectedItems.get(0).getParent().getValue(); - for (int i = 1; i < selectedItems.size(); i++) { - TreeItem treeItem = selectedItems.get(i); - if (!treeItem.getParent().getValue().getUniqueId().equals(parentNodeOfFirst.getUniqueId())) { - System.out.println(parentNodeOfFirst.getUniqueId() + " " + treeItem.getParent().getValue().getUniqueId()); - return false; - } + hasSameParentProperty.set(true); + nodesOfSameTypeProperty.set(true); } - return true; } } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuCompositeSnapshot.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuCompositeSnapshot.java index 0403c1b58c..eadec07c4e 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuCompositeSnapshot.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuCompositeSnapshot.java @@ -18,29 +18,35 @@ package org.phoebus.applications.saveandrestore.ui; +import javafx.beans.binding.Bindings; import javafx.scene.control.CustomMenuItem; import javafx.scene.control.Menu; import javafx.scene.control.MenuItem; import javafx.scene.control.SeparatorMenuItem; -import javafx.scene.control.TreeView; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import org.phoebus.applications.saveandrestore.Messages; -import org.phoebus.applications.saveandrestore.model.Node; import org.phoebus.applications.saveandrestore.ui.snapshot.tag.TagWidget; import org.phoebus.ui.javafx.ImageCache; +/** + * Context menu for {@link org.phoebus.applications.saveandrestore.model.Node}s of type + * {@link org.phoebus.applications.saveandrestore.model.NodeType#COMPOSITE_SNAPSHOT}. + */ public class ContextMenuCompositeSnapshot extends ContextMenuBase { - public ContextMenuCompositeSnapshot(SaveAndRestoreController saveAndRestoreController, TreeView treeView) { - super(saveAndRestoreController, treeView); + public ContextMenuCompositeSnapshot(SaveAndRestoreController saveAndRestoreController) { + super(saveAndRestoreController); Image snapshotTagsWithCommentIcon = ImageCache.getImage(SaveAndRestoreController.class, "/icons/save-and-restore/snapshot-tags.png"); MenuItem editCompositeSnapshotMenuItem = new MenuItem(Messages.Edit, new ImageView(ImageRepository.EDIT_CONFIGURATION)); - editCompositeSnapshotMenuItem.disableProperty().bind(multipleSelection); + editCompositeSnapshotMenuItem.disableProperty().bind(multipleNodesSelectedProperty); editCompositeSnapshotMenuItem.setOnAction(ae -> saveAndRestoreController.editCompositeSnapshot()); + editCompositeSnapshotMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() -> + userIsAuthenticatedProperty.not().get() || multipleNodesSelectedProperty.get(), + userIsAuthenticatedProperty, multipleNodesSelectedProperty)); ImageView snapshotTagsWithCommentIconImage = new ImageView(snapshotTagsWithCommentIcon); snapshotTagsWithCommentIconImage.setFitHeight(22); @@ -48,6 +54,9 @@ public ContextMenuCompositeSnapshot(SaveAndRestoreController saveAndRestoreContr Menu tagWithComment = new Menu(Messages.contextMenuTagsWithComment, snapshotTagsWithCommentIconImage); tagWithComment.setOnShowing(event -> saveAndRestoreController.tagWithComment(tagWithComment)); + tagWithComment.disableProperty().bind(Bindings.createBooleanBinding(() -> + multipleNodesSelectedProperty.get() || userIsAuthenticatedProperty.not().get(), + multipleNodesSelectedProperty, userIsAuthenticatedProperty)); CustomMenuItem addTagWithCommentMenuItem = TagWidget.AddTagWithCommentMenuItem(); addTagWithCommentMenuItem.setOnAction(action -> saveAndRestoreController.addTagToSnapshots()); @@ -57,10 +66,9 @@ public ContextMenuCompositeSnapshot(SaveAndRestoreController saveAndRestoreContr Image copyIcon = ImageCache.getImage(SaveAndRestoreController.class, "/icons/copy.png"); MenuItem copyMenuItem = new MenuItem(Messages.copy, new ImageView(copyIcon)); copyMenuItem.setOnAction(action -> saveAndRestoreController.copySelectionToClipboard()); - - setOnShowing(event -> { - copyMenuItem.setDisable(!saveAndRestoreController.mayCopy()); - }); + copyMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() -> + userIsAuthenticatedProperty.not().get() || mayCopyProperty.not().get(), + userIsAuthenticatedProperty, mayCopyProperty)); getItems().addAll(editCompositeSnapshotMenuItem, copyMenuItem, @@ -68,4 +76,16 @@ public ContextMenuCompositeSnapshot(SaveAndRestoreController saveAndRestoreContr copyUniqueIdToClipboardMenuItem, tagWithComment); } + + /** + * Execute common checks (see {@link ContextMenuBase#runChecks()}) and: + *
    + *
  • If copy operation is possible on selected {@link org.phoebus.applications.saveandrestore.model.Node}s
  • + *
+ */ + @Override + public void runChecks() { + super.runChecks(); + mayCopyProperty.set(saveAndRestoreController.mayCopy()); + } } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuConfiguration.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuConfiguration.java index 81c7454468..8347e13785 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuConfiguration.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuConfiguration.java @@ -18,36 +18,36 @@ package org.phoebus.applications.saveandrestore.ui; +import javafx.beans.binding.Bindings; import javafx.scene.control.MenuItem; -import javafx.scene.control.TreeView; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import org.phoebus.applications.saveandrestore.Messages; -import org.phoebus.applications.saveandrestore.model.Node; import org.phoebus.ui.javafx.ImageCache; -public class ContextMenuConfiguration extends ContextMenuBase{ +/** + * Context menu for {@link org.phoebus.applications.saveandrestore.model.Node}s of type + * {@link org.phoebus.applications.saveandrestore.model.NodeType#CONFIGURATION}. + */ +public class ContextMenuConfiguration extends ContextMenuBase { - public ContextMenuConfiguration(SaveAndRestoreController saveAndRestoreController, - TreeView treeView) { - super(saveAndRestoreController, treeView); + public ContextMenuConfiguration(SaveAndRestoreController saveAndRestoreController) { + super(saveAndRestoreController); Image csvExportIcon = ImageCache.getImage(SaveAndRestoreController.class, "/icons/csv_export.png"); MenuItem openConfigurationMenuItem = new MenuItem(Messages.contextMenuCreateSnapshot, new ImageView(ImageRepository.CONFIGURATION)); - openConfigurationMenuItem.disableProperty().bind(multipleSelection); + openConfigurationMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() -> + multipleNodesSelectedProperty.get() || userIsAuthenticatedProperty.not().get(), + multipleNodesSelectedProperty, userIsAuthenticatedProperty)); openConfigurationMenuItem.setOnAction(ae -> saveAndRestoreController.openConfigurationForSnapshot()); - MenuItem editConfigurationMenuItem = new MenuItem(Messages.Edit, new ImageView(ImageRepository.EDIT_CONFIGURATION)); - editConfigurationMenuItem.disableProperty().bind(multipleSelection); - editConfigurationMenuItem.setOnAction(ae -> saveAndRestoreController.nodeDoubleClicked()); - ImageView exportConfigurationIconImageView = new ImageView(csvExportIcon); exportConfigurationIconImageView.setFitWidth(18); exportConfigurationIconImageView.setFitHeight(18); MenuItem exportConfigurationMenuItem = new MenuItem(Messages.exportConfigurationLabel, exportConfigurationIconImageView); - exportConfigurationMenuItem.disableProperty().bind(multipleSelection); + exportConfigurationMenuItem.disableProperty().bind(multipleNodesSelectedProperty); exportConfigurationMenuItem.setOnAction(ae -> saveAndRestoreController.exportConfiguration()); ImageView importSnapshotIconImageView = new ImageView(csvImportIcon); @@ -55,24 +55,22 @@ public ContextMenuConfiguration(SaveAndRestoreController saveAndRestoreControlle importSnapshotIconImageView.setFitHeight(18); MenuItem importSnapshotMenuItem = new MenuItem(Messages.importSnapshotLabel, importSnapshotIconImageView); - importSnapshotMenuItem.disableProperty().bind(multipleSelection); + importSnapshotMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() -> + multipleNodesSelectedProperty.get() || userIsAuthenticatedProperty.not().get(), + multipleNodesSelectedProperty, userIsAuthenticatedProperty)); importSnapshotMenuItem.setOnAction(ae -> saveAndRestoreController.importSnapshot()); Image copyIcon = ImageCache.getImage(SaveAndRestoreController.class, "/icons/copy.png"); MenuItem copyMenuItem = new MenuItem(Messages.copy, new ImageView(copyIcon)); copyMenuItem.setOnAction(action -> saveAndRestoreController.copySelectionToClipboard()); + copyMenuItem.disableProperty().bind(mayCopyProperty.not()); Image pasteIcon = ImageCache.getImage(SaveAndRestoreController.class, "/icons/paste.png"); MenuItem pasteMenuItem = new MenuItem(Messages.paste, new ImageView(pasteIcon)); pasteMenuItem.setOnAction(ae -> saveAndRestoreController.pasteFromClipboard()); - - setOnShowing(event -> { - pasteMenuItem.setDisable(!saveAndRestoreController.mayPaste()); - copyMenuItem.setDisable(!saveAndRestoreController.mayCopy()); - }); + pasteMenuItem.disableProperty().bind(mayPasteProperty.not()); getItems().addAll(openConfigurationMenuItem, - editConfigurationMenuItem, copyMenuItem, pasteMenuItem, deleteNodesMenuItem, @@ -80,4 +78,18 @@ public ContextMenuConfiguration(SaveAndRestoreController saveAndRestoreControlle exportConfigurationMenuItem, importSnapshotMenuItem); } + + /** + * Execute common checks (see {@link ContextMenuBase#runChecks()}) and: + *
    + *
  • If copy operation is possible on selected {@link org.phoebus.applications.saveandrestore.model.Node}s
  • + *
  • If paste operation is possible on selected {@link org.phoebus.applications.saveandrestore.model.Node}s
  • + *
+ */ + @Override + protected void runChecks() { + super.runChecks(); + mayPasteProperty.set(saveAndRestoreController.mayPaste()); + mayCopyProperty.set(saveAndRestoreController.mayCopy()); + } } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuCreateSaveset.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuCreateSaveset.java index ab1676a165..dd8a1003ad 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuCreateSaveset.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuCreateSaveset.java @@ -35,6 +35,9 @@ import org.phoebus.core.types.ProcessVariable; import org.phoebus.framework.nls.NLS; import org.phoebus.framework.selection.Selection; +import org.phoebus.security.store.SecureStore; +import org.phoebus.security.tokens.AuthenticationScope; +import org.phoebus.security.tokens.ScopedAuthenticationToken; import org.phoebus.ui.javafx.ImageCache; import org.phoebus.ui.spi.ContextMenuEntry; @@ -81,9 +84,29 @@ public Class getSupportedType() return supportedTypes; } + /** + * @return true only if user has been authenticated by the save-n-restore service. + */ + @Override + public boolean isEnabled(){ + try { + SecureStore secureStore = new SecureStore(); + ScopedAuthenticationToken token = + secureStore.getScopedAuthenticationToken(AuthenticationScope.SAVE_AND_RESTORE); + return token != null; + } catch (Exception e) { + Logger.getLogger(ContextMenuCreateSaveset.class.getName()).log(Level.WARNING, "Unable to retrieve authentication token for " + + AuthenticationScope.SAVE_AND_RESTORE.getName() + " scope", e); + return false; + } + } + @Override public void call(final Selection selection) { + + + saveAndRestoreService = SaveAndRestoreService.getInstance(); checkRootNode(); diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuFolder.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuFolder.java index 3e15ac3afc..4b42dff695 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuFolder.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuFolder.java @@ -18,35 +18,48 @@ package org.phoebus.applications.saveandrestore.ui; +import javafx.beans.binding.Bindings; import javafx.scene.control.MenuItem; -import javafx.scene.control.TreeView; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import org.phoebus.applications.saveandrestore.Messages; import org.phoebus.applications.saveandrestore.model.Node; import org.phoebus.ui.javafx.ImageCache; +/** + * Context menu for {@link Node}s of type {@link org.phoebus.applications.saveandrestore.model.NodeType#FOLDER}. + * All item actions require user to be authenticated, and if that is not the case, + * the context menu is hidden rather than showing a list of disabled context menu items. + */ public class ContextMenuFolder extends ContextMenuBase { - public ContextMenuFolder(SaveAndRestoreController saveAndRestoreController, TreeView treeView) { - super(saveAndRestoreController, treeView); + public ContextMenuFolder(SaveAndRestoreController saveAndRestoreController) { + super(saveAndRestoreController); Image renameIcon = ImageCache.getImage(SaveAndRestoreController.class, "/icons/rename_col.png"); MenuItem renameNodeMenuItem = new MenuItem(Messages.contextMenuRename, new ImageView(renameIcon)); renameNodeMenuItem.setOnAction(ae -> saveAndRestoreController.renameNode()); - renameNodeMenuItem.disableProperty().bind(multipleSelection); + renameNodeMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() -> + multipleNodesSelectedProperty.get() || userIsAuthenticatedProperty.not().get(), + multipleNodesSelectedProperty, userIsAuthenticatedProperty)); MenuItem newFolderMenuItem = new MenuItem(Messages.contextMenuNewFolder, new ImageView(ImageRepository.FOLDER)); - newFolderMenuItem.disableProperty().bind(multipleSelection); + newFolderMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() -> + multipleNodesSelectedProperty.get() || userIsAuthenticatedProperty.not().get(), + multipleNodesSelectedProperty, userIsAuthenticatedProperty)); newFolderMenuItem.setOnAction(ae -> saveAndRestoreController.createNewFolder()); MenuItem newConfigurationMenuItem = new MenuItem(Messages.contextMenuNewConfiguration, new ImageView(ImageRepository.CONFIGURATION)); - newConfigurationMenuItem.disableProperty().bind(multipleSelection); + newConfigurationMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() -> + multipleNodesSelectedProperty.get() || userIsAuthenticatedProperty.not().get(), + multipleNodesSelectedProperty, userIsAuthenticatedProperty)); newConfigurationMenuItem.setOnAction(ae -> saveAndRestoreController.createNewConfiguration()); MenuItem newCompositeSnapshotMenuItem = new MenuItem(Messages.contextMenuNewCompositeSnapshot, new ImageView(ImageRepository.COMPOSITE_SNAPSHOT)); - newCompositeSnapshotMenuItem.disableProperty().bind(multipleSelection); + newCompositeSnapshotMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() -> + multipleNodesSelectedProperty.get() || userIsAuthenticatedProperty.not().get(), + multipleNodesSelectedProperty, userIsAuthenticatedProperty)); newCompositeSnapshotMenuItem.setOnAction(ae -> saveAndRestoreController.createNewCompositeSnapshot()); ImageView importConfigurationIconImageView = new ImageView(csvImportIcon); @@ -54,16 +67,17 @@ public ContextMenuFolder(SaveAndRestoreController saveAndRestoreController, Tree importConfigurationIconImageView.setFitHeight(18); MenuItem importConfigurationMenuItem = new MenuItem(Messages.importConfigurationLabel, importConfigurationIconImageView); - importConfigurationMenuItem.disableProperty().bind(multipleSelection); + importConfigurationMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() -> + multipleNodesSelectedProperty.get() || userIsAuthenticatedProperty.not().get(), + multipleNodesSelectedProperty, userIsAuthenticatedProperty)); importConfigurationMenuItem.setOnAction(ae -> saveAndRestoreController.importConfiguration()); Image pasteIcon = ImageCache.getImage(SaveAndRestoreController.class, "/icons/paste.png"); MenuItem pasteMenuItem = new MenuItem(Messages.paste, new ImageView(pasteIcon)); pasteMenuItem.setOnAction(ae -> saveAndRestoreController.pasteFromClipboard()); - - setOnShowing(event -> { - pasteMenuItem.setDisable(!saveAndRestoreController.mayPaste()); - }); + pasteMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() -> + mayPasteProperty.not().get() || userIsAuthenticatedProperty.not().get(), + mayPasteProperty, userIsAuthenticatedProperty)); getItems().addAll(newFolderMenuItem, renameNodeMenuItem, @@ -73,5 +87,16 @@ public ContextMenuFolder(SaveAndRestoreController saveAndRestoreController, Tree newCompositeSnapshotMenuItem, copyUniqueIdToClipboardMenuItem, importConfigurationMenuItem); + + } + + /** + * Execute common checks (see {@link ContextMenuBase#runChecks()}) and hides the menu + * if user is not authenticated. + */ + @Override + protected void runChecks() { + super.runChecks(); + mayPasteProperty.set(saveAndRestoreController.mayPaste()); } } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuSnapshot.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuSnapshot.java index b27475aa65..4a4086b687 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuSnapshot.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ContextMenuSnapshot.java @@ -18,49 +18,56 @@ package org.phoebus.applications.saveandrestore.ui; -import javafx.application.Platform; -import javafx.beans.property.SimpleObjectProperty; -import javafx.beans.property.SimpleStringProperty; -import javafx.collections.ObservableList; -import javafx.event.EventHandler; -import javafx.scene.control.CustomMenuItem; +import javafx.beans.binding.Bindings; +import javafx.beans.property.SimpleBooleanProperty; import javafx.scene.control.Menu; import javafx.scene.control.MenuItem; -import javafx.scene.control.SeparatorMenuItem; -import javafx.scene.control.TreeItem; -import javafx.scene.control.TreeView; import javafx.scene.image.Image; import javafx.scene.image.ImageView; -import javafx.stage.WindowEvent; import org.phoebus.applications.saveandrestore.Messages; -import org.phoebus.applications.saveandrestore.model.Node; -import org.phoebus.applications.saveandrestore.model.NodeType; import org.phoebus.applications.saveandrestore.ui.snapshot.tag.TagWidget; import org.phoebus.ui.javafx.ImageCache; +/** + * Context menu for {@link org.phoebus.applications.saveandrestore.model.Node}s of type + * {@link org.phoebus.applications.saveandrestore.model.NodeType#SNAPSHOT}. + */ public class ContextMenuSnapshot extends ContextMenuBase { protected Image compareSnapshotIcon = ImageCache.getImage(SaveAndRestoreController.class, "/icons/save-and-restore/compare.png"); protected Image csvExportIcon = ImageCache.getImage(SaveAndRestoreController.class, "/icons/csv_export.png"); - private MenuItem compareSnapshotsMenuItem; - private Menu tagWithComment; + private final MenuItem tagGoldenMenuItem; + + private final Menu tagWithComment; + + private final SimpleBooleanProperty mayTagProperty = new SimpleBooleanProperty(); + + private final SimpleBooleanProperty mayCompareSnapshotsProperty = new SimpleBooleanProperty(); + + private final SimpleBooleanProperty mayTagOrUntagGoldenProperty = new SimpleBooleanProperty(); - public ContextMenuSnapshot(SaveAndRestoreController saveAndRestoreController, - TreeView treeView) { - super(saveAndRestoreController, treeView); + public ContextMenuSnapshot(SaveAndRestoreController saveAndRestoreController) { + super(saveAndRestoreController); - compareSnapshotsMenuItem = new MenuItem(Messages.contextMenuCompareSnapshots, new ImageView(compareSnapshotIcon)); + MenuItem compareSnapshotsMenuItem = new MenuItem(Messages.contextMenuCompareSnapshots, new ImageView(compareSnapshotIcon)); compareSnapshotsMenuItem.setOnAction(ae -> saveAndRestoreController.compareSnapshot()); + compareSnapshotsMenuItem.disableProperty().bind(mayCompareSnapshotsProperty.not()); ImageView snapshotTagsWithCommentIconImage = new ImageView(ImageRepository.SNAPSHOT_ADD_TAG_WITH_COMMENT); tagWithComment = new Menu(Messages.contextMenuTagsWithComment, snapshotTagsWithCommentIconImage); tagWithComment.setOnShowing(event -> saveAndRestoreController.tagWithComment(tagWithComment)); + tagWithComment.disableProperty().bind(Bindings.createBooleanBinding(() -> + multipleNodesSelectedProperty.get() || userIsAuthenticatedProperty.not().get(), + multipleNodesSelectedProperty, userIsAuthenticatedProperty)); MenuItem addTagWithCommentMenuItem = TagWidget.AddTagWithCommentMenuItem(); addTagWithCommentMenuItem.setOnAction(action -> saveAndRestoreController.addTagToSnapshots()); + addTagWithCommentMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() -> + multipleNodesSelectedProperty.get() || mayTagProperty.not().get(), + multipleNodesSelectedProperty, mayTagProperty)); tagWithComment.getItems().addAll(addTagWithCommentMenuItem); @@ -72,21 +79,18 @@ public ContextMenuSnapshot(SaveAndRestoreController saveAndRestoreController, exportSnapshotIconImageView.setFitHeight(18); MenuItem exportSnapshotMenuItem = new MenuItem(Messages.exportSnapshotLabel, exportSnapshotIconImageView); - exportSnapshotMenuItem.disableProperty().bind(multipleSelection); + exportSnapshotMenuItem.disableProperty().bind(multipleNodesSelectedProperty); exportSnapshotMenuItem.setOnAction(ae -> saveAndRestoreController.exportSnapshot()); - MenuItem tagGoldenMenuItem = new MenuItem(Messages.contextMenuTagAsGolden, new ImageView(ImageRepository.SNAPSHOT)); + tagGoldenMenuItem = new MenuItem(Messages.contextMenuTagAsGolden, new ImageView(ImageRepository.SNAPSHOT)); + tagGoldenMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() -> + multipleNodesSelectedProperty.get() || userIsAuthenticatedProperty.not().get() || mayTagOrUntagGoldenProperty.not().get(), + multipleNodesSelectedProperty, userIsAuthenticatedProperty, mayTagOrUntagGoldenProperty)); Image copyIcon = ImageCache.getImage(SaveAndRestoreController.class, "/icons/copy.png"); MenuItem copyMenuItem = new MenuItem(Messages.copy, new ImageView(copyIcon)); copyMenuItem.setOnAction(action -> saveAndRestoreController.copySelectionToClipboard()); - - setOnShowing(event -> { - saveAndRestoreController.configureGoldenItem(tagGoldenMenuItem); - copyMenuItem.setDisable(!saveAndRestoreController.mayCopy()); - compareSnapshotsMenuItem.disableProperty().set(!compareSnapshotsPossible()); - runChecks(); - }); + copyMenuItem.disableProperty().bind(mayCopyProperty.not()); getItems().addAll(deleteNodesMenuItem, compareSnapshotsMenuItem, @@ -98,49 +102,19 @@ public ContextMenuSnapshot(SaveAndRestoreController saveAndRestoreController, } - @Override - protected void runChecks() { - super.runChecks(); - ObservableList> selected = - treeView.getSelectionModel().getSelectedItems(); - if (multipleSelection.get() && checkNotTaggable(selected)) { - tagWithComment.disableProperty().set(true); - } - else{ - tagWithComment.disableProperty().set(false); - } - } - /** - * Determines if comparing snapshots is possible, which is the case if all of the following holds true: + * Execute common checks (see {@link ContextMenuBase#runChecks()}) and: *
    - *
  • The active tab must be a {@link org.phoebus.applications.saveandrestore.ui.snapshot.SnapshotTab}
  • - *
  • The active {@link org.phoebus.applications.saveandrestore.ui.snapshot.SnapshotTab} must not show an unsaved snapshot.
  • - *
  • The snapshot selected from the tree view must have same parent as the one shown in the active {@link org.phoebus.applications.saveandrestore.ui.snapshot.SnapshotTab}
  • - *
  • The snapshot selected from the tree view must not be the same as as the one shown in the active {@link org.phoebus.applications.saveandrestore.ui.snapshot.SnapshotTab}
  • + *
  • If tagging is possible on selected {@link org.phoebus.applications.saveandrestore.model.Node}s
  • + *
  • If comparing snapshots is possible
  • + *
  • If setting/unsetting golden tag is possible
  • *
- * @return true if selection can be added to snapshot view for comparison. */ - private boolean compareSnapshotsPossible() { - Node[] configAndSnapshotNode = saveAndRestoreController.getConfigAndSnapshotForActiveSnapshotTab(); - if(configAndSnapshotNode == null){ - return false; - } - TreeItem selectedItem = treeView.getSelectionModel().getSelectedItem(); - TreeItem parentItem = selectedItem.getParent(); - return configAndSnapshotNode[1].getUniqueId() != null && - parentItem.getValue().getUniqueId().equals(configAndSnapshotNode[0].getUniqueId()) && - !selectedItem.getValue().getUniqueId().equals(configAndSnapshotNode[1].getUniqueId()); - } - - /** - * Checks if selection is not allowed, i.e. not all selected nodes are snapshot nodes. - * @param selectedItems List of selected nodes - * @return true if any of the selected nodes is of type {@link NodeType#FOLDER} or - * {@link NodeType#CONFIGURATION}. - */ - private boolean checkNotTaggable(ObservableList> selectedItems){ - return selectedItems.stream().filter(i -> i.getValue().getNodeType().equals(NodeType.FOLDER) || - i.getValue().getNodeType().equals(NodeType.CONFIGURATION)).findFirst().isPresent(); + @Override + protected void runChecks() { + super.runChecks(); + mayTagProperty.set(saveAndRestoreController.checkTaggable()); + mayCompareSnapshotsProperty.set(saveAndRestoreController.compareSnapshotsPossible()); + mayTagOrUntagGoldenProperty.set(saveAndRestoreController.configureGoldenItem(tagGoldenMenuItem)); } } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ImageRepository.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ImageRepository.java index d2b3192889..ddaaeb2b25 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ImageRepository.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/ImageRepository.java @@ -46,8 +46,5 @@ public class ImageRepository { public static final Image SNAPSHOT_ADD_TAG_WITH_COMMENT = ImageCache.getImage(ImageRepository.class, "/icons/save-and-restore/snapshot-add_tag.png"); - public static final Image SNAPSHOT_REMOVE_TAG_WITH_COMMENT = - ImageCache.getImage(ImageRepository.class, "/icons/save-and-restore/snapshot-remove_tag.png"); - public static final Image DELETE = ImageCache.getImage(SaveAndRestoreController.class, "/icons/delete.png"); } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreBaseController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreBaseController.java new file mode 100644 index 0000000000..6e5fbf8e2f --- /dev/null +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreBaseController.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2020 European Spallation Source ERIC. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +package org.phoebus.applications.saveandrestore.ui; + +import javafx.beans.property.SimpleStringProperty; +import org.phoebus.applications.saveandrestore.ui.snapshot.SnapshotControlsViewController; +import org.phoebus.security.store.SecureStore; +import org.phoebus.security.tokens.AuthenticationScope; +import org.phoebus.security.tokens.ScopedAuthenticationToken; + +import java.util.List; +import java.util.Optional; +import java.util.logging.Level; +import java.util.logging.Logger; + +public abstract class SaveAndRestoreBaseController { + + protected final SimpleStringProperty userIdentity = new SimpleStringProperty(); + + public SaveAndRestoreBaseController(){ + try { + SecureStore secureStore = new SecureStore(); + ScopedAuthenticationToken token = + secureStore.getScopedAuthenticationToken(AuthenticationScope.SAVE_AND_RESTORE); + if (token != null) { + userIdentity.set(token.getUsername()); + } + else{ + userIdentity.set(null); + } + } catch (Exception e) { + Logger.getLogger(SnapshotControlsViewController.class.getName()).log(Level.WARNING, "Unable to retrieve authentication token for " + + AuthenticationScope.SAVE_AND_RESTORE.getName() + " scope", e); + } + } + + public void secureStoreChanged(List validTokens) { + Optional token = + validTokens.stream() + .filter(t -> t.getAuthenticationScope().equals(AuthenticationScope.SAVE_AND_RESTORE)).findFirst(); + if (token.isPresent()) { + userIdentity.set(token.get().getUsername()); + } else { + userIdentity.set(null); + } + } + + public SimpleStringProperty getUserIdentity(){ + return userIdentity; + } +} diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java index eedbb968b4..dfae860eb7 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreController.java @@ -30,33 +30,10 @@ import javafx.concurrent.Task; import javafx.fxml.FXML; import javafx.fxml.Initializable; -import javafx.scene.control.Alert; +import javafx.scene.control.*; import javafx.scene.control.Alert.AlertType; -import javafx.scene.control.Button; -import javafx.scene.control.ButtonType; -import javafx.scene.control.CheckBox; -import javafx.scene.control.ComboBox; -import javafx.scene.control.ContextMenu; -import javafx.scene.control.ListCell; -import javafx.scene.control.ListView; -import javafx.scene.control.Menu; -import javafx.scene.control.MenuItem; -import javafx.scene.control.MultipleSelectionModel; -import javafx.scene.control.ProgressIndicator; -import javafx.scene.control.SelectionMode; -import javafx.scene.control.SplitPane; -import javafx.scene.control.Tab; -import javafx.scene.control.TabPane; -import javafx.scene.control.TextInputDialog; -import javafx.scene.control.Tooltip; -import javafx.scene.control.TreeItem; -import javafx.scene.control.TreeView; import javafx.scene.image.ImageView; -import javafx.scene.input.Clipboard; -import javafx.scene.input.ClipboardContent; -import javafx.scene.input.KeyCode; -import javafx.scene.input.KeyEvent; -import javafx.scene.input.TransferMode; +import javafx.scene.input.*; import javafx.scene.layout.VBox; import javafx.stage.FileChooser; import javafx.util.Callback; @@ -80,6 +57,7 @@ import org.phoebus.applications.saveandrestore.ui.snapshot.tag.TagUtil; import org.phoebus.framework.jobs.JobManager; import org.phoebus.framework.preferences.PhoebusPreferenceService; +import org.phoebus.security.tokens.ScopedAuthenticationToken; import org.phoebus.ui.dialog.DialogHelper; import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog; import org.phoebus.ui.javafx.ImageCache; @@ -91,15 +69,7 @@ import java.net.URI; import java.net.URL; import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.ResourceBundle; -import java.util.Stack; +import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -107,7 +77,8 @@ /** * Main controller for the save and restore UI. */ -public class SaveAndRestoreController implements Initializable, NodeChangedListener, NodeAddedListener, FilterChangeListener { +public class SaveAndRestoreController extends SaveAndRestoreBaseController + implements Initializable, NodeChangedListener, NodeAddedListener, FilterChangeListener { @FXML protected TreeView treeView; @@ -134,12 +105,6 @@ public class SaveAndRestoreController implements Initializable, NodeChangedListe private final ObjectMapper objectMapper = new ObjectMapper(); - protected ContextMenu folderContextMenu; - protected ContextMenu configurationContextMenu; - protected ContextMenu snapshotContextMenu; - protected ContextMenu rootFolderContextMenu; - protected ContextMenu compositeSnapshotContextMenu; - protected MultipleSelectionModel> browserSelectionModel; private static final String TREE_STATE = "tree_state"; @@ -174,6 +139,7 @@ public SaveAndRestoreController(URI uri) { this.uri = uri; } + @Override public void initialize(URL url, ResourceBundle resourceBundle) { @@ -194,18 +160,6 @@ public void initialize(URL url, ResourceBundle resourceBundle) { filtersComboBox.disableProperty().bind(filterEnabledProperty.not()); filterEnabledProperty.addListener((observable, oldValue, newValue) -> filterEnabledChanged(newValue)); - folderContextMenu = new ContextMenuFolder(this, treeView); - configurationContextMenu = new ContextMenuConfiguration(this, treeView); - - rootFolderContextMenu = new ContextMenu(); - MenuItem newRootFolderMenuItem = new MenuItem(Messages.contextMenuNewFolder, new ImageView(ImageRepository.FOLDER)); - newRootFolderMenuItem.setOnAction(ae -> createNewFolder()); - rootFolderContextMenu.getItems().add(newRootFolderMenuItem); - - snapshotContextMenu = new ContextMenuSnapshot(this, treeView); - - compositeSnapshotContextMenu = new ContextMenuCompositeSnapshot(this, treeView); - treeView.setEditable(true); treeView.setOnMouseClicked(me -> { @@ -224,9 +178,7 @@ public void initialize(URL url, ResourceBundle resourceBundle) { saveAndRestoreService.addNodeAddedListener(this); saveAndRestoreService.addFilterChangeListener(this); - treeView.setCellFactory(p -> new BrowserTreeCell(folderContextMenu, - configurationContextMenu, snapshotContextMenu, rootFolderContextMenu, compositeSnapshotContextMenu, - this)); + treeView.setCellFactory(p -> new BrowserTreeCell(this)); progressIndicator.visibleProperty().bind(disabledUi); disabledUi.addListener((observable, oldValue, newValue) -> treeView.setDisable(newValue)); @@ -281,12 +233,18 @@ public Filter fromString(String s) { // considered in paste actions. Clipboard.getSystemClipboard().clear(); + userIdentity.addListener((a, b, c) -> { + String name = c == null ? "Root folder" : + "Root folder (" + userIdentity.get() + ")"; + treeView.getRoot().setValue(Node.builder().uniqueId(Node.ROOT_FOLDER_UNIQUE_ID).name(name).build()); + }); + loadTreeData(); } /** * Loads the data for the tree root as provided (persisted) by the current - * {@link org.phoebus.applications.saveandrestore.SaveAndRestoreClient}. + * {@link org.phoebus.applications.saveandrestore.client.SaveAndRestoreClient}. */ public void loadTreeData() { @@ -303,7 +261,7 @@ protected TreeItem call() { HashMap>> childNodesMap = new HashMap<>(); savedTreeViewStructure.forEach(s -> { List childNodes = saveAndRestoreService.getChildNodes(Node.builder().uniqueId(s).build()); - if (childNodes != null) { // This may be the case if the tree structure was modified outside of the UI + if (childNodes != null) { // This may be the case if the tree structure was modified externally List> childItems = childNodes.stream().map(n -> createTreeItem(n)).sorted(treeNodeComparator).collect(Collectors.toList()); childNodesMap.put(s, childItems); } @@ -571,9 +529,11 @@ protected Void call() throws Exception { @Override public void failed() { - expandTreeNode(parentTreeItem.getParent()); + if (parentTreeItem.getParent() != null) { // Parent is null for root folder + expandTreeNode(parentTreeItem.getParent()); + } ExceptionDetailsErrorDialog.openError(Messages.errorGeneric, - Messages.errorCreateFolderFailed, null); + Messages.errorCreateFolderFailed, new Exception(getException())); } }; @@ -581,10 +541,6 @@ public void failed() { } } - public void nodeDoubleClicked() { - nodeDoubleClicked(treeView.getSelectionModel().getSelectedItem().getValue()); - } - /** * Handles double click on the specified tree node. Actual action depends on the {@link Node} type. * @@ -606,7 +562,9 @@ public void nodeDoubleClicked(Node node) { ((SnapshotTab) tab).loadSnapshot(node); break; case COMPOSITE_SNAPSHOT: - openCompositeSnapshotForRestore(); + TreeItem treeItem = browserSelectionModel.getSelectedItems().get(0); + tab = new SnapshotTab(treeItem.getValue(), saveAndRestoreService); + ((SnapshotTab) tab).loadSnapshot(treeItem.getValue()); return; case FOLDER: default: @@ -799,12 +757,12 @@ public void nodeChanged(Node node) { } nodeSubjectToUpdate.setValue(node); // Folder and configuration node changes may include structure changes, so expand to force update. - if (nodeSubjectToUpdate.getValue().getNodeType().equals(NodeType.FOLDER) || - nodeSubjectToUpdate.getValue().getNodeType().equals(NodeType.CONFIGURATION)) { - if (nodeSubjectToUpdate.getParent() != null) { // null means root folder as it has no parent - nodeSubjectToUpdate.getParent().getChildren().sort(treeNodeComparator); - } - expandTreeNode(nodeSubjectToUpdate); + if(nodeSubjectToUpdate.isExpanded() && (nodeSubjectToUpdate.getValue().getNodeType().equals(NodeType.FOLDER) || + nodeSubjectToUpdate.getValue().getNodeType().equals(NodeType.CONFIGURATION))){ + if (nodeSubjectToUpdate.getParent() != null) { // null means root folder as it has no parent + nodeSubjectToUpdate.getParent().getChildren().sort(treeNodeComparator); + } + expandTreeNode(nodeSubjectToUpdate); } } @@ -1062,32 +1020,28 @@ public void tagWithComment(final Menu tagWithCommentMenu) { * * * @param menuItem The {@link MenuItem} subject to configuration. + * @return false if the menu item should be disabled. */ - public void configureGoldenItem(MenuItem menuItem) { + public boolean configureGoldenItem(MenuItem menuItem) { List selectedNodes = browserSelectionModel.getSelectedItems().stream().map(TreeItem::getValue).collect(Collectors.toList()); - TagUtil.configureGoldenItem(selectedNodes, menuItem); + return TagUtil.configureGoldenItem(selectedNodes, menuItem); } /** - * Performs check of multiple selection to determine if it fulfills the criteria: - *
    - *
  • All selected nodes must be of same type.
  • - *
  • All selected nodes must have same parent node.
  • - *
+ * Performs check of selection to determine if all selected nodes are of same type. * * @return true if criteria are met, otherwise false */ - protected boolean checkMultipleSelection() { + public boolean selectedNodesOfSameType() { ObservableList> selectedItems = browserSelectionModel.getSelectedItems(); if (selectedItems.size() < 2) { return true; } - TreeItem parent = selectedItems.get(0).getParent(); NodeType nodeType = selectedItems.get(0).getValue().getNodeType(); - for(int i = 1; i < selectedItems.size(); i++){ - if(!selectedItems.get(i).getParent().equals(parent) || !selectedItems.get(i).getValue().getNodeType().equals(nodeType)){ + for (int i = 1; i < selectedItems.size(); i++) { + if (!selectedItems.get(i).getValue().getNodeType().equals(nodeType)) { return false; } } @@ -1279,15 +1233,6 @@ public Node[] getConfigAndSnapshotForActiveSnapshotTab() { return null; } - protected void openCompositeSnapshotForRestore() { - TreeItem treeItem = browserSelectionModel.getSelectedItems().get(0); - SnapshotTab tab = new SnapshotTab(treeItem.getValue(), saveAndRestoreService); - tab.loadSnapshot(treeItem.getValue()); - - tabPane.getTabs().add(tab); - tabPane.getSelectionModel().select(tab); - } - @Override public void filterAddedOrUpdated(Filter filter) { if (!filtersList.contains(filter)) { @@ -1317,7 +1262,7 @@ public void filterRemoved(Filter filter) { } } - public void copySelectionToClipboard(){ + public void copySelectionToClipboard() { List> selectedItems = browserSelectionModel.getSelectedItems(); List selectedNodes = selectedItems.stream().map(TreeItem::getValue).collect(Collectors.toList()); ClipboardContent clipboardContent = new ClipboardContent(); @@ -1332,15 +1277,19 @@ public void copySelectionToClipboard(){ *
  • All selected nodes must be of same type.
  • *
  • All selected nodes must have same parent.
  • * + * * @return true if selection may be copied to clipboard, otherwise false. */ - public boolean mayCopy(){ + public boolean mayCopy() { + if (userIdentity.isNull().get()) { + return false; + } List selectedNodes = browserSelectionModel.getSelectedItems().stream().map(TreeItem::getValue).collect(Collectors.toList()); - if(selectedNodes.size() == 1){ + if (selectedNodes.size() == 1) { return true; } NodeType nodeTypeOfFirst = selectedNodes.get(0).getNodeType(); - if(selectedNodes.stream().filter(n -> !n.getNodeType().equals(nodeTypeOfFirst)).findFirst().isPresent()){ + if (selectedNodes.stream().filter(n -> !n.getNodeType().equals(nodeTypeOfFirst)).findFirst().isPresent()) { return false; } TreeItem parentOfFirst = browserSelectionModel.getSelectedItems().get(0).getParent(); @@ -1350,44 +1299,43 @@ public boolean mayCopy(){ /** * Checks if the clipboard content may be pasted onto a target node: *
      - *
    • Clipboard c ontent must be of {@link SaveAndRestoreApplication#NODE_SELECTION_FORMAT}.
    • + *
    • Clipboard content must be of {@link SaveAndRestoreApplication#NODE_SELECTION_FORMAT}.
    • *
    • Selected node for paste (target) must be single node.
    • *
    • Configurations and composite snapshots may be pasted only onto folder.
    • *
    • Snapshot may be pasted only onto configuration.
    • *
    - * @return true if selection may be pasted, otherwise false. + * + * @return true if selection may be pasted, otherwise false. */ - public boolean mayPaste(){ + public boolean mayPaste() { + if (userIdentity.isNull().get()) { + return false; + } Object clipBoardContent = Clipboard.getSystemClipboard().getContent(SaveAndRestoreApplication.NODE_SELECTION_FORMAT); - if(clipBoardContent == null || browserSelectionModel.getSelectedItems().size() != 1){ + if (clipBoardContent == null || browserSelectionModel.getSelectedItems().size() != 1) { return false; } // Check is made if target node is of supported type for the clipboard content. - List selectedNodes = (List)clipBoardContent; + List selectedNodes = (List) clipBoardContent; NodeType nodeTypeOfFirst = selectedNodes.get(0).getNodeType(); NodeType nodeTypeOfTarget = browserSelectionModel.getSelectedItem().getValue().getNodeType(); - if((nodeTypeOfFirst.equals(NodeType.COMPOSITE_SNAPSHOT) || - nodeTypeOfFirst.equals(NodeType.CONFIGURATION)) && !nodeTypeOfTarget.equals(NodeType.FOLDER)){ + if ((nodeTypeOfFirst.equals(NodeType.COMPOSITE_SNAPSHOT) || + nodeTypeOfFirst.equals(NodeType.CONFIGURATION)) && !nodeTypeOfTarget.equals(NodeType.FOLDER)) { return false; - } - else if(nodeTypeOfFirst.equals(NodeType.SNAPSHOT) && !nodeTypeOfTarget.equals(NodeType.CONFIGURATION)){ + } else if (nodeTypeOfFirst.equals(NodeType.SNAPSHOT) && !nodeTypeOfTarget.equals(NodeType.CONFIGURATION)) { return false; } return true; } - public List> getSelectedItems(){ - return treeView.getSelectionModel().getSelectedItems(); - } - - public void pasteFromClipboard(){ + public void pasteFromClipboard() { disabledUi.set(true); Object selectedNodes = Clipboard.getSystemClipboard().getContent(SaveAndRestoreApplication.NODE_SELECTION_FORMAT); - if(selectedNodes == null || browserSelectionModel.getSelectedItems().size() != 1){ + if (selectedNodes == null || browserSelectionModel.getSelectedItems().size() != 1) { return; } List selectedNodeIds = - ((List)selectedNodes).stream().map(Node::getUniqueId).collect(Collectors.toList()); + ((List) selectedNodes).stream().map(Node::getUniqueId).collect(Collectors.toList()); JobManager.schedule("copy nodes", monitor -> { try { saveAndRestoreService.copyNodes(selectedNodeIds, browserSelectionModel.getSelectedItem().getValue().getUniqueId()); @@ -1404,4 +1352,74 @@ public void pasteFromClipboard(){ }); }); } + + /** + * Used to determine if nodes selected in the tree view have the same parent node. Most menu items + * do not make sense unless the selected nodes have same the parent node. + * + * @return true if all selected nodes have the same parent node, false otherwise. + */ + public boolean hasSameParent() { + ObservableList> selectedItems = browserSelectionModel.getSelectedItems(); + if (selectedItems.size() == 1) { + return true; + } + Node parentNodeOfFirst = selectedItems.get(0).getParent().getValue(); + for (int i = 1; i < selectedItems.size(); i++) { + TreeItem treeItem = selectedItems.get(i); + if (!treeItem.getParent().getValue().getUniqueId().equals(parentNodeOfFirst.getUniqueId())) { + return false; + } + } + return true; + } + + /** + * @return true selection contains multiple {@link Node}s, otherwise false. + */ + public boolean multipleNodesSelected() { + return browserSelectionModel.getSelectedItems().size() > 1; + } + + /** + * Checks if selection is not allowed, i.e. not all selected nodes are snapshot nodes. + * + * @return false if any of the selected nodes is of type {@link NodeType#FOLDER} or + * {@link NodeType#CONFIGURATION}. Since these {@link NodeType}s cannot be tagged. + */ + public boolean checkTaggable() { + return browserSelectionModel.getSelectedItems().stream().filter(i -> i.getValue().getNodeType().equals(NodeType.FOLDER) || + i.getValue().getNodeType().equals(NodeType.CONFIGURATION)).findFirst().isEmpty(); + } + + /** + * Determines if comparing snapshots is possible, which is the case if all of the following holds true: + *
      + *
    • The active tab must be a {@link org.phoebus.applications.saveandrestore.ui.snapshot.SnapshotTab}
    • + *
    • The active {@link org.phoebus.applications.saveandrestore.ui.snapshot.SnapshotTab} must not show an unsaved snapshot.
    • + *
    • The snapshot selected from the tree view must have same parent as the one shown in the active {@link org.phoebus.applications.saveandrestore.ui.snapshot.SnapshotTab}
    • + *
    • The snapshot selected from the tree view must not be the same as as the one shown in the active {@link org.phoebus.applications.saveandrestore.ui.snapshot.SnapshotTab}
    • + *
    + * + * @return true if selection can be added to snapshot view for comparison. + */ + public boolean compareSnapshotsPossible() { + Node[] configAndSnapshotNode = getConfigAndSnapshotForActiveSnapshotTab(); + if (configAndSnapshotNode == null) { + return false; + } + TreeItem selectedItem = treeView.getSelectionModel().getSelectedItem(); + TreeItem parentItem = selectedItem.getParent(); + return configAndSnapshotNode[1].getUniqueId() != null && + parentItem.getValue().getUniqueId().equals(configAndSnapshotNode[0].getUniqueId()) && + !selectedItem.getValue().getUniqueId().equals(configAndSnapshotNode[1].getUniqueId()); + } + + @Override + public void secureStoreChanged(List validTokens) { + super.secureStoreChanged(validTokens); + tabPane.getTabs().forEach(t -> { + ((SaveAndRestoreTab) t).secureStoreChanged(validTokens); + }); + } } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java index f08d792574..4b7ea4c361 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreService.java @@ -18,7 +18,7 @@ package org.phoebus.applications.saveandrestore.ui; - +import org.phoebus.applications.saveandrestore.model.*; import org.phoebus.applications.saveandrestore.model.CompositeSnapshot; import org.phoebus.applications.saveandrestore.model.Configuration; import org.phoebus.applications.saveandrestore.model.ConfigurationData; @@ -145,7 +145,7 @@ public Configuration updateConfiguration(Configuration configuration) throws Exc Future future = executor.submit(() -> saveAndRestoreClient.updateConfiguration(configuration)); Configuration updatedConfiguration = future.get(); // Associated configuration Node may have a new name - notifyNodeChangeListeners(configuration.getConfigurationNode()); + notifyNodeChangeListeners(updatedConfiguration.getConfigurationNode()); return updatedConfiguration; } @@ -235,7 +235,14 @@ public Snapshot saveSnapshot(Node configurationNode, Snapshot snapshot) throws E return snapshotItem; }).collect(Collectors.toList()); snapshot.getSnapshotData().setSnapshotItems(beautifiedItems); - Future future = executor.submit(() -> saveAndRestoreClient.saveSnapshot(configurationNode.getUniqueId(), snapshot)); + Future future = executor.submit(() -> { + if(snapshot.getSnapshotNode().getUniqueId() == null){ + return saveAndRestoreClient.createSnapshot(configurationNode.getUniqueId(), snapshot); + } + else{ + return saveAndRestoreClient.updateSnapshot(snapshot); + } + }); Snapshot updatedSnapshot = future.get(); // Notify listeners as the configuration node has a new child node. notifyNodeChangeListeners(configurationNode); @@ -313,7 +320,7 @@ public Filter saveFilter(Filter filter) throws Exception { */ public List getAllFilters() throws Exception { Future> future = - executor.submit(() -> saveAndRestoreClient.getAllFilters()); + executor.submit(saveAndRestoreClient::getAllFilters); return future.get(); } @@ -338,7 +345,7 @@ public List addTag(TagData tagData) throws Exception { Future> future = executor.submit(() -> saveAndRestoreClient.addTag(tagData)); List updatedNodes = future.get(); - updatedNodes.forEach(n -> notifyNodeChangeListeners(n)); + updatedNodes.forEach(this::notifyNodeChangeListeners); return updatedNodes; } @@ -353,7 +360,7 @@ public List deleteTag(TagData tagData) throws Exception { Future> future = executor.submit(() -> saveAndRestoreClient.deleteTag(tagData)); List updatedNodes = future.get(); - updatedNodes.forEach(n -> notifyNodeChangeListeners(n)); + updatedNodes.forEach(this::notifyNodeChangeListeners); return updatedNodes; } @@ -372,4 +379,17 @@ private void notifyFilterAddedOrUpdated(Filter filter) { private void notifyFilterDeleted(Filter filter) { filterChangeListeners.forEach(l -> l.filterRemoved(filter)); } + + /** + * Authenticate user, needed for all non-GET endpoints if service requires it + * @param userName User's account name + * @param password User's password + * @return A {@link UserData} object + * @throws Exception if authentication fails + */ + public UserData authenticate(String userName, String password) throws Exception{ + Future future = + executor.submit(() -> saveAndRestoreClient.authenticate(userName, password)); + return future.get(); + } } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreTab.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreTab.java index 86aaba41d5..3beb5a5da8 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreTab.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/SaveAndRestoreTab.java @@ -25,6 +25,7 @@ import javafx.scene.control.Tab; import javafx.scene.image.ImageView; import org.phoebus.applications.saveandrestore.ui.snapshot.SnapshotTab; +import org.phoebus.security.tokens.ScopedAuthenticationToken; import org.phoebus.ui.javafx.ImageCache; import java.util.ArrayList; @@ -35,6 +36,8 @@ */ public abstract class SaveAndRestoreTab extends Tab implements NodeChangedListener { + protected SaveAndRestoreBaseController controller; + public SaveAndRestoreTab() { ContextMenu contextMenu = new ContextMenu(); @@ -58,4 +61,8 @@ public SaveAndRestoreTab() { contextMenu.getItems().addAll(closeAll, closeOthers); setContextMenu(contextMenu); } + + public void secureStoreChanged(List validTokens){ + controller.secureStoreChanged(validTokens); + } } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationController.java index 7bc0b23481..11587af979 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationController.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationController.java @@ -28,33 +28,17 @@ import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.fxml.FXML; -import javafx.scene.control.Alert; -import javafx.scene.control.Button; -import javafx.scene.control.ButtonType; -import javafx.scene.control.CheckBox; -import javafx.scene.control.ContextMenu; -import javafx.scene.control.Label; -import javafx.scene.control.MenuItem; -import javafx.scene.control.SelectionMode; -import javafx.scene.control.SeparatorMenuItem; -import javafx.scene.control.TableCell; -import javafx.scene.control.TableColumn; -import javafx.scene.control.TableView; -import javafx.scene.control.TextArea; -import javafx.scene.control.TextField; +import javafx.scene.control.*; import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.image.ImageView; import javafx.scene.layout.BorderPane; +import javafx.scene.layout.Pane; import javafx.util.Callback; import org.phoebus.applications.saveandrestore.Messages; import org.phoebus.applications.saveandrestore.SaveAndRestoreApplication; -import org.phoebus.applications.saveandrestore.model.ConfigPv; -import org.phoebus.applications.saveandrestore.model.Configuration; -import org.phoebus.applications.saveandrestore.model.ConfigurationData; -import org.phoebus.applications.saveandrestore.model.Node; -import org.phoebus.applications.saveandrestore.model.NodeType; +import org.phoebus.applications.saveandrestore.model.*; import org.phoebus.applications.saveandrestore.ui.NodeChangedListener; -import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreController; +import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreBaseController; import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreService; import org.phoebus.core.types.ProcessVariable; import org.phoebus.framework.selection.SelectionService; @@ -73,7 +57,7 @@ import java.util.logging.Logger; import java.util.stream.Collectors; -public class ConfigurationController implements NodeChangedListener { +public class ConfigurationController extends SaveAndRestoreBaseController implements NodeChangedListener { @FXML private BorderPane root; @@ -120,6 +104,9 @@ public class ConfigurationController implements NodeChangedListener { @FXML private Label createdByField; + @FXML + private Pane addPVsPane; + private SaveAndRestoreService saveAndRestoreService; private static final Executor UI_EXECUTOR = Platform::runLater; @@ -156,15 +143,16 @@ public void initialize() { ContextMenu pvNameContextMenu = new ContextMenu(); MenuItem deleteMenuItem = new MenuItem(Messages.menuItemDeleteSelectedPVs, - new ImageView(ImageCache.getImage(SaveAndRestoreController.class, "/icons/delete.png"))); + new ImageView(ImageCache.getImage(ConfigurationController.class, "/icons/delete.png"))); deleteMenuItem.setOnAction(ae -> { configurationEntries.removeAll(pvTable.getSelectionModel().getSelectedItems()); configurationTab.annotateDirty(true); pvTable.refresh(); }); - deleteMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() -> pvTable.getSelectionModel().getSelectedItems().isEmpty(), - pvTable.getSelectionModel().getSelectedItems())); + deleteMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() -> pvTable.getSelectionModel().getSelectedItems().isEmpty() + || userIdentity.isNull().get(), + pvTable.getSelectionModel().getSelectedItems(), userIdentity)); pvNameColumn.setEditable(true); pvNameColumn.setCellValueFactory(new PropertyValueFactory<>("pvName")); @@ -213,7 +201,9 @@ public void updateItem(String item, boolean empty) { pvNameField.textProperty().bindBidirectional(pvNameProperty); readbackPvNameField.textProperty().bindBidirectional(readbackPvNameProperty); configurationNameField.textProperty().bindBidirectional(configurationNameProperty); + configurationNameField.disableProperty().bind(userIdentity.isNull()); descriptionTextArea.textProperty().bindBidirectional(configurationDescriptionProperty); + descriptionTextArea.disableProperty().bind(userIdentity.isNull()); configurationEntries.addListener((ListChangeListener) change -> { while (change.next()) { @@ -225,13 +215,13 @@ public void updateItem(String item, boolean empty) { }); configurationNameProperty.addListener((observableValue, oldValue, newValue) -> dirty.set(!newValue.equals(configurationNode.getName()))); - configurationDescriptionProperty.addListener((observable, oldValue, newValue) -> dirty.set(!newValue.equals(configurationNode.get().getDescription()))); saveButton.disableProperty().bind(Bindings.createBooleanBinding(() -> dirty.not().get() || configurationDescriptionProperty.isEmpty().get() || - configurationNameProperty.isEmpty().get(), - dirty, configurationDescriptionProperty, configurationNameProperty)); + configurationNameProperty.isEmpty().get() || + userIdentity.isNull().get(), + dirty, configurationDescriptionProperty, configurationNameProperty, userIdentity)); addPvButton.disableProperty().bind(pvNameField.textProperty().isEmpty()); @@ -253,6 +243,8 @@ public void updateItem(String item, boolean empty) { } }); + addPVsPane.disableProperty().bind(userIdentity.isNull()); + SaveAndRestoreService.getInstance().addNodeChangeListener(this); } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationTab.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationTab.java index 96579ad17a..a905df15fc 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationTab.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/configuration/ConfigurationTab.java @@ -27,15 +27,15 @@ import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreService; import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreTab; import org.phoebus.framework.nls.NLS; +import org.phoebus.security.tokens.ScopedAuthenticationToken; +import java.util.List; import java.util.ResourceBundle; import java.util.logging.Level; import java.util.logging.Logger; public class ConfigurationTab extends SaveAndRestoreTab { - private ConfigurationController configurationController; - public ConfigurationTab() { configure(); } @@ -59,7 +59,7 @@ private void configure() { return null; }); setContent(loader.load()); - configurationController = loader.getController(); + controller = loader.getController(); setGraphic(new ImageView(ImageRepository.CONFIGURATION)); } catch (Exception e) { Logger.getLogger(ConfigurationTab.class.getName()) @@ -68,7 +68,7 @@ private void configure() { } setOnCloseRequest(event -> { - if (!configurationController.handleConfigurationTabClosed()) { + if (!((ConfigurationController)controller).handleConfigurationTabClosed()) { event.consume(); } else { SaveAndRestoreService.getInstance().removeNodeChangeListener(this); @@ -86,11 +86,12 @@ private void configure() { public void editConfiguration(Node configurationNode) { setId(configurationNode.getUniqueId()); textProperty().set(configurationNode.getName()); - configurationController.loadConfiguration(configurationNode); + ((ConfigurationController)controller).loadConfiguration(configurationNode); } public void configureForNewConfiguration(Node parentNode) { - configurationController.newConfiguration(parentNode); + textProperty().set(Messages.contextMenuNewConfiguration); + ((ConfigurationController)controller).newConfiguration(parentNode); } @Override @@ -111,9 +112,9 @@ public void updateTabTitle(String tabTitle) { public void annotateDirty(boolean dirty) { String tabTitle = textProperty().get(); - if (dirty) { + if (dirty && !tabTitle.contains("*")) { updateTabTitle("* " + tabTitle); - } else { + } else if(!dirty){ updateTabTitle(tabTitle.substring(2)); } } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/search/SearchAndFilterTab.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/search/SearchAndFilterTab.java index fa6f88f201..cd5dbbf92e 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/search/SearchAndFilterTab.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/search/SearchAndFilterTab.java @@ -21,13 +21,13 @@ import javafx.fxml.FXMLLoader; import javafx.scene.Node; -import javafx.scene.control.Tab; import javafx.scene.image.ImageView; import org.phoebus.applications.saveandrestore.Messages; import org.phoebus.applications.saveandrestore.SaveAndRestoreApplication; import org.phoebus.applications.saveandrestore.ui.NodeChangedListener; import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreController; import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreService; +import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreTab; import org.phoebus.framework.nls.NLS; import org.phoebus.ui.javafx.ImageCache; @@ -36,7 +36,7 @@ import java.util.logging.Level; import java.util.logging.Logger; -public class SearchAndFilterTab extends Tab implements NodeChangedListener { +public class SearchAndFilterTab extends SaveAndRestoreTab implements NodeChangedListener { public static final String SEARCH_AND_FILTER_TAB_ID = "SearchAndFilterTab"; private SearchAndFilterViewController searchAndFilterViewController; diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/search/SearchAndFilterViewController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/search/SearchAndFilterViewController.java index 6460fd0982..bec5534a6c 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/search/SearchAndFilterViewController.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/search/SearchAndFilterViewController.java @@ -66,11 +66,7 @@ import org.phoebus.applications.saveandrestore.model.search.SearchQueryUtil; import org.phoebus.applications.saveandrestore.model.search.SearchQueryUtil.Keys; import org.phoebus.applications.saveandrestore.model.search.SearchResult; -import org.phoebus.applications.saveandrestore.ui.FilterChangeListener; -import org.phoebus.applications.saveandrestore.ui.HelpViewer; -import org.phoebus.applications.saveandrestore.ui.ImageRepository; -import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreController; -import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreService; +import org.phoebus.applications.saveandrestore.ui.*; import org.phoebus.applications.saveandrestore.ui.snapshot.tag.TagUtil; import org.phoebus.applications.saveandrestore.ui.snapshot.tag.TagWidget; import org.phoebus.framework.jobs.JobManager; @@ -98,7 +94,7 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -public class SearchAndFilterViewController implements Initializable, FilterChangeListener { +public class SearchAndFilterViewController extends SaveAndRestoreBaseController implements Initializable, FilterChangeListener { private final SaveAndRestoreController saveAndRestoreController; @@ -379,8 +375,12 @@ public void initialize(URL url, ResourceBundle resourceBundle) { queryLabel.textProperty().bind(query); filterNameTextField.textProperty().bindBidirectional(filterNameProperty); + filterNameTextField.disableProperty().bind(saveAndRestoreController.getUserIdentity().isNull()); saveFilterButton.disableProperty().bind(Bindings.createBooleanBinding(() -> - filterNameProperty.get() == null || filterNameProperty.get().isEmpty(), filterNameProperty)); + filterNameProperty.get() == null || + filterNameProperty.get().isEmpty() || + saveAndRestoreController.getUserIdentity().isNull().get(), + filterNameProperty, saveAndRestoreController.getUserIdentity())); resultTableView.setRowFactory(tableView -> new TableRow<>() { @Override @@ -722,6 +722,8 @@ private class DeleteTableCell extends TableCell { @Override protected void updateItem(final Filter filter, final boolean empty) { super.updateItem(filter, empty); + // If user clicks on the delete column cell, consume the mouse event to prevent the filter from being loaded. + setOnMouseClicked(event -> event.consume()); if (empty) { setGraphic(null); } else { @@ -731,13 +733,13 @@ protected void updateItem(final Filter filter, final boolean empty) { button.setOnAction(event -> { try { saveAndRestoreService.deleteFilter(filter); - //loadFilters(); clearFilter(filter); } catch (Exception e) { LOGGER.log(Level.SEVERE, "Failed to delete filter", e); ExceptionDetailsErrorDialog.openError(Messages.errorGeneric, Messages.faildDeleteFilter, e); } }); + button.disableProperty().bind(saveAndRestoreController.getUserIdentity().isNull()); setGraphic(button); } } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/BaseSnapshotTableViewController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/BaseSnapshotTableViewController.java index d362a367ad..75a7f788eb 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/BaseSnapshotTableViewController.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/BaseSnapshotTableViewController.java @@ -273,7 +273,12 @@ protected String getPVKey(String pvName, boolean isReadonly) { } protected void showSnapshotInTable(Snapshot snapshot){ - snapshots.add(0, snapshot); + if(snapshots.isEmpty()){ + snapshots.add(snapshot); + } + else{ + snapshots.set(0, snapshot); + } AtomicInteger counter = new AtomicInteger(0); snapshot.getSnapshotData().getSnapshotItems().forEach(entry -> { TableEntry tableEntry = new TableEntry(); diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/CompositeSnapshotController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/CompositeSnapshotController.java index b4db6e120c..52419e7b7e 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/CompositeSnapshotController.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/CompositeSnapshotController.java @@ -29,21 +29,8 @@ import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.fxml.FXML; -import javafx.scene.control.Alert; +import javafx.scene.control.*; import javafx.scene.control.Alert.AlertType; -import javafx.scene.control.Button; -import javafx.scene.control.ButtonType; -import javafx.scene.control.ContextMenu; -import javafx.scene.control.Label; -import javafx.scene.control.MenuItem; -import javafx.scene.control.SelectionMode; -import javafx.scene.control.TableCell; -import javafx.scene.control.TableColumn; -import javafx.scene.control.TableRow; -import javafx.scene.control.TableView; -import javafx.scene.control.TextArea; -import javafx.scene.control.TextField; -import javafx.scene.control.Tooltip; import javafx.scene.image.ImageView; import javafx.scene.input.TransferMode; import javafx.scene.layout.BorderPane; @@ -53,12 +40,9 @@ import org.phoebus.applications.saveandrestore.DirectoryUtilities; import org.phoebus.applications.saveandrestore.Messages; import org.phoebus.applications.saveandrestore.SaveAndRestoreApplication; -import org.phoebus.applications.saveandrestore.model.CompositeSnapshot; -import org.phoebus.applications.saveandrestore.model.CompositeSnapshotData; -import org.phoebus.applications.saveandrestore.model.Node; -import org.phoebus.applications.saveandrestore.model.NodeType; -import org.phoebus.applications.saveandrestore.model.Tag; +import org.phoebus.applications.saveandrestore.model.*; import org.phoebus.applications.saveandrestore.ui.ImageRepository; +import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreBaseController; import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreController; import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreService; import org.phoebus.framework.jobs.JobManager; @@ -69,15 +53,11 @@ import java.text.MessageFormat; import java.time.Instant; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Optional; -import java.util.Stack; +import java.util.*; import java.util.function.Consumer; import java.util.stream.Collectors; -public class CompositeSnapshotController { +public class CompositeSnapshotController extends SaveAndRestoreBaseController { @FXML private StackPane root; @@ -145,6 +125,7 @@ public class CompositeSnapshotController { private ChangeListener nodeNameChangeListener; private ChangeListener descriptionChangeListener; + public CompositeSnapshotController(CompositeSnapshotTab compositeSnapshotTab, SaveAndRestoreController saveAndRestoreController) { this.compositeSnapshotTab = compositeSnapshotTab; this.saveAndRestoreController = saveAndRestoreController; @@ -167,8 +148,9 @@ public void initialize() { snapshotTable.refresh(); }); - deleteMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() -> snapshotTable.getSelectionModel().getSelectedItems().isEmpty(), - snapshotTable.getSelectionModel().getSelectedItems())); + deleteMenuItem.disableProperty().bind(Bindings.createBooleanBinding(() -> + snapshotTable.getSelectionModel().getSelectedItems().isEmpty() || userIdentity.isNull().get(), + snapshotTable.getSelectionModel().getSelectedItems(), userIdentity)); snapshotDateColumn.setCellFactory(new Callback<>() { @Override @@ -274,7 +256,9 @@ public void updateItem(Node item, boolean empty) { }); compositeSnapshotNameField.textProperty().bindBidirectional(compositeSnapshotNameProperty); + compositeSnapshotNameField.disableProperty().bind(userIdentity.isNull()); descriptionTextArea.textProperty().bindBidirectional(compositeSnapshotDescriptionProperty); + descriptionTextArea.disableProperty().bind(userIdentity.isNull()); compositeSnapshotLastModifiedDateField.textProperty().bindBidirectional(lastUpdatedProperty); compositeSnapshotCreatedDateField.textProperty().bindBidirectional(createdDateProperty); createdByField.textProperty().bindBidirectional(createdByProperty); @@ -286,8 +270,9 @@ public void updateItem(Node item, boolean empty) { saveButton.disableProperty().bind(Bindings.createBooleanBinding(() -> dirty.not().get() || compositeSnapshotDescriptionProperty.isEmpty().get() || - compositeSnapshotNameProperty.isEmpty().get(), - dirty, compositeSnapshotDescriptionProperty, compositeSnapshotNameProperty)); + compositeSnapshotNameProperty.isEmpty().get() || + userIdentity.isNull().get(), + dirty, compositeSnapshotDescriptionProperty, compositeSnapshotNameProperty, userIdentity)); snapshotTable.setOnDragOver(event -> { if (event.getDragboard().hasContent(SaveAndRestoreApplication.NODE_SELECTION_FORMAT)) { @@ -297,6 +282,10 @@ public void updateItem(Node item, boolean empty) { }); snapshotTable.setOnDragDropped(event -> { + if (userIdentity.isNull().get()) { + event.consume(); + return; + } List sourceNodes = (List) event.getDragboard().getContent(SaveAndRestoreApplication.NODE_SELECTION_FORMAT); if (!mayDrop(sourceNodes)) { return; @@ -313,14 +302,12 @@ public void updateItem(Node item, boolean empty) { compositeSnapshotTab.annotateDirty(n); } }); - } @FXML public void save() { - doSave(compositeSnapshot -> { - loadCompositeSnapshot(compositeSnapshot.getCompositeSnapshotNode(), Collections.emptyList()); - }); + doSave(compositeSnapshot -> + loadCompositeSnapshot(compositeSnapshot.getCompositeSnapshotNode(), Collections.emptyList())); } private void doSave(Consumer completion) { @@ -411,18 +398,17 @@ public boolean handleCompositeSnapshotTabClosed() { /** * Configures the controller to create a new composite snapshot. * - * @param parentNode The parent {@link Node} for the new composite, i.e. must be a - * {@link Node} of type {@link NodeType#FOLDER}. + * @param parentNode The parent {@link Node} for the new composite, i.e. must be a + * {@link Node} of type {@link NodeType#FOLDER}. * @param snapshotNodes Potentially empty list of {@link Node}s of type {@link NodeType#SNAPSHOT} * or {@link NodeType#COMPOSITE_SNAPSHOT}, or both. */ public void newCompositeSnapshot(Node parentNode, List snapshotNodes) { parentFolder = parentNode; compositeSnapshotNode = Node.builder().nodeType(NodeType.COMPOSITE_SNAPSHOT).build(); - if(snapshotNodes.isEmpty()){ + if (snapshotNodes.isEmpty()) { dirty.set(false); - } - else{ + } else { dirty.set(true); //snapshotEntries.addAll(snapshotNodes); addToCompositeSnapshot(snapshotNodes); @@ -466,15 +452,16 @@ public void addToCompositeSnapshot(List sourceNodes) { e); } disabledUi.set(false); - if (duplicates.isEmpty()) { + if (duplicates != null && duplicates.isEmpty()) { snapshotEntries.addAll(sourceNodes); } else { int maxItems = 10; StringBuilder stringBuilder = new StringBuilder(); - for (int i = 0; i < Math.min(duplicates.size(), maxItems); i++) { + int duplicatesSize = duplicates != null ? duplicates.size() : 0; + for (int i = 0; i < Math.min(duplicatesSize, maxItems); i++) { stringBuilder.append(duplicates.get(i)).append(System.lineSeparator()); } - if (duplicates.size() > maxItems) { + if (duplicatesSize > maxItems) { stringBuilder.append(".").append(System.lineSeparator()) .append(".").append(System.lineSeparator()) .append(".").append(System.lineSeparator()); @@ -517,5 +504,4 @@ private void removeListeners() { compositeSnapshotNameProperty.removeListener(nodeNameChangeListener); compositeSnapshotDescriptionProperty.removeListener(descriptionChangeListener); } - } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/CompositeSnapshotTab.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/CompositeSnapshotTab.java index 7a14307748..c2eef07a9e 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/CompositeSnapshotTab.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/CompositeSnapshotTab.java @@ -22,12 +22,12 @@ import javafx.application.Platform; import javafx.beans.property.SimpleStringProperty; import javafx.fxml.FXMLLoader; -import javafx.scene.control.Tab; import javafx.scene.image.ImageView; import org.phoebus.applications.saveandrestore.Messages; import org.phoebus.applications.saveandrestore.model.Node; import org.phoebus.applications.saveandrestore.ui.ImageRepository; import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreController; +import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreTab; import org.phoebus.framework.nls.NLS; import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog; @@ -41,9 +41,7 @@ * Tab for creating or editing composite snapshots, * i.e. for node type {@link org.phoebus.applications.saveandrestore.model.NodeType#COMPOSITE_SNAPSHOT}. */ -public class CompositeSnapshotTab extends Tab { - - private CompositeSnapshotController compositeSnapshotController; +public class CompositeSnapshotTab extends SaveAndRestoreTab { private final SimpleStringProperty tabTitleProperty = new SimpleStringProperty(Messages.contextMenuNewCompositeSnapshot); @@ -82,14 +80,14 @@ private void configure() { return; } - compositeSnapshotController = loader.getController(); + controller = loader.getController(); setContent(rootNode); setGraphic(new ImageView(ImageRepository.COMPOSITE_SNAPSHOT)); textProperty().bind(tabTitleProperty); setOnCloseRequest(event -> { - if (!compositeSnapshotController.handleCompositeSnapshotTabClosed()) { + if (!((CompositeSnapshotController) controller).handleCompositeSnapshotTabClosed()) { event.consume(); } }); @@ -108,7 +106,7 @@ public void annotateDirty(boolean dirty) { public void configureForNewCompositeSnapshot(Node parentNode, List snapshotNodes) { tabTitleProperty.set(Messages.contextMenuNewCompositeSnapshot); - compositeSnapshotController.newCompositeSnapshot(parentNode, snapshotNodes); + ((CompositeSnapshotController) controller).newCompositeSnapshot(parentNode, snapshotNodes); } /** @@ -121,7 +119,7 @@ public void configureForNewCompositeSnapshot(Node parentNode, List snapsho public void editCompositeSnapshot(Node compositeSnapshotNode, List snapshotNodes) { setId("edit_" + compositeSnapshotNode.getUniqueId()); setNodeName(compositeSnapshotNode.getName()); - compositeSnapshotController.loadCompositeSnapshot(compositeSnapshotNode, snapshotNodes); + ((CompositeSnapshotController) controller).loadCompositeSnapshot(compositeSnapshotNode, snapshotNodes); } /** @@ -131,6 +129,11 @@ public void editCompositeSnapshot(Node compositeSnapshotNode, List snapsho * the composite snapshot. */ public void addToCompositeSnapshot(List snapshotNodes) { - compositeSnapshotController.addToCompositeSnapshot(snapshotNodes); + ((CompositeSnapshotController) controller).addToCompositeSnapshot(snapshotNodes); + } + + @Override + public void nodeChanged(Node node) { + } } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotController.java index 65c1d4d4cd..20d024e70b 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotController.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotController.java @@ -30,9 +30,11 @@ import org.phoebus.applications.saveandrestore.Messages; import org.phoebus.applications.saveandrestore.model.*; import org.phoebus.applications.saveandrestore.model.event.SaveAndRestoreEventReceiver; +import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreBaseController; import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreService; import org.phoebus.applications.saveandrestore.ui.VNoData; import org.phoebus.framework.jobs.JobManager; +import org.phoebus.security.tokens.ScopedAuthenticationToken; import org.phoebus.ui.dialog.DialogHelper; import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog; @@ -46,7 +48,7 @@ * Once the snapshot has been saved, this controller calls the {@link SnapshotTab} API to load * the view associated with restore actions. */ -public class SnapshotController { +public class SnapshotController extends SaveAndRestoreBaseController { @FXML @@ -85,6 +87,7 @@ public SnapshotController(SnapshotTab snapshotTab) { @FXML public void initialize() { + // Locate registered SaveAndRestoreEventReceivers eventReceivers = ServiceLoader.load(SaveAndRestoreEventReceiver.class); progressIndicator.visibleProperty().bind(disabledUi); @@ -125,7 +128,6 @@ public void newSnapshot(Node configurationNode) { snapshotData.setSnapshotItems(configurationToSnapshotItems(configPvs)); snapshot.setSnapshotData(snapshotData); snapshotProperty.set(snapshot); - //List tableEntries = snapshotTableViewController.createTableEntries(snapshot); Platform.runLater(() -> snapshotTableViewController.showSnapshotInTable(snapshot)); }); } @@ -134,6 +136,7 @@ public void newSnapshot(Node configurationNode) { @SuppressWarnings("unused") public void takeSnapshot() { disabledUi.set(true); + snapshotTab.setText(Messages.unnamedSnapshot); snapshotTableViewController.takeSnapshot(snapshot -> { disabledUi.set(false); snapshotProperty.set(snapshot); @@ -148,13 +151,22 @@ public void saveSnapshot(ActionEvent actionEvent) { List snapshotItems = snapshotProperty.get().getSnapshotData().getSnapshotItems(); SnapshotData snapshotData = new SnapshotData(); snapshotData.setSnapshotItems(snapshotItems); - Snapshot snapshot = new Snapshot(); + Snapshot snapshot = snapshotProperty.get(); + // Creating new or updating existing (e.g. name change)? + if (snapshot == null) { + snapshot = new Snapshot(); + snapshot.setSnapshotNode(Node.builder().nodeType(NodeType.SNAPSHOT) + .name(snapshotControlsViewController.getSnapshotNameProperty().get()) + .description(snapshotControlsViewController.getSnapshotCommentProperty().get()).build()); + } else { + snapshot.getSnapshotNode().setName(snapshotControlsViewController.getSnapshotNameProperty().get()); + snapshot.getSnapshotNode().setDescription(snapshotControlsViewController.getSnapshotCommentProperty().get()); + } snapshot.setSnapshotData(snapshotData); - snapshot.setSnapshotNode(Node.builder().nodeType(NodeType.SNAPSHOT) - .name(snapshotControlsViewController.getSnapshotNameProperty().get()) - .description(snapshotControlsViewController.getSnapshotCommentProperty().get()).build()); + try { snapshot = SaveAndRestoreService.getInstance().saveSnapshot(configurationNode, snapshot); + snapshotProperty.set(snapshot); Node _snapshotNode = snapshot.getSnapshotNode(); javafx.scene.Node jfxNode = (javafx.scene.Node) actionEvent.getSource(); String userData = (String) jfxNode.getUserData(); @@ -358,7 +370,7 @@ public void addSnapshot(Node snapshotNode) { Snapshot snapshot = getSnapshotFromService(snapshotNode); snapshotTableViewController.addSnapshot(snapshot); } catch (Exception e) { - e.printStackTrace(); + Logger.getLogger(SnapshotController.class.getName()).log(Level.WARNING, "Failed to add snapshot", e); } finally { disabledUi.set(false); } @@ -385,4 +397,9 @@ private Snapshot getSnapshotFromService(Node snapshotNode) throws Exception { snapshot.setSnapshotData(snapshotData); return snapshot; } + + @Override + public void secureStoreChanged(List validTokens) { + snapshotControlsViewController.secureStoreChanged(validTokens); + } } \ No newline at end of file diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotControlsViewController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotControlsViewController.java index a1b421b47c..b0da33a7d9 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotControlsViewController.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotControlsViewController.java @@ -39,6 +39,7 @@ import org.phoebus.applications.saveandrestore.model.Node; import org.phoebus.applications.saveandrestore.model.NodeType; import org.phoebus.applications.saveandrestore.model.event.SaveAndRestoreEventReceiver; +import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreBaseController; import org.phoebus.ui.docking.DockPane; import org.phoebus.util.time.TimestampFormats; @@ -49,7 +50,7 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -public class SnapshotControlsViewController { +public class SnapshotControlsViewController extends SaveAndRestoreBaseController { private SnapshotController snapshotController; @@ -138,13 +139,16 @@ public void setSnapshotController(SnapshotController snapshotController) { public void initialize() { snapshotName.textProperty().bindBidirectional(snapshotNameProperty); + snapshotName.disableProperty().bind(userIdentity.isNull()); snapshotComment.textProperty().bindBidirectional(snapshotCommentProperty); + snapshotComment.disableProperty().bind(userIdentity.isNull()); createdBy.textProperty().bind(createdByTextProperty); createdDate.textProperty().bind(createdDateTextProperty); snapshotLastModifiedLabel.textProperty().bind(lastModifiedDateTextProperty); - takeSnapshotButton.disableProperty().bind(Bindings.createBooleanBinding(() -> snapshotNodeProperty.isNotNull().get() && - !snapshotNodeProperty.get().getNodeType().equals(NodeType.SNAPSHOT), snapshotNodeProperty)); + takeSnapshotButton.disableProperty().bind(Bindings.createBooleanBinding(() -> + (snapshotNodeProperty.isNotNull().get() && !snapshotNodeProperty.get().getNodeType().equals(NodeType.SNAPSHOT)) || + userIdentity.isNull().get(), snapshotNodeProperty, userIdentity)); snapshotNameProperty.addListener(((observableValue, oldValue, newValue) -> snapshotDataDirty.set(newValue != null && (snapshotNodeProperty.isNull().get() || snapshotNodeProperty.isNotNull().get() && !newValue.equals(snapshotNodeProperty.get().getName()))))); @@ -154,20 +158,25 @@ public void initialize() { saveSnapshotButton.disableProperty().bind(Bindings.createBooleanBinding(() -> snapshotDataDirty.not().get() || snapshotNameProperty.isEmpty().get() || - snapshotCommentProperty.isEmpty().get(), - snapshotDataDirty, snapshotNameProperty, snapshotCommentProperty)); + snapshotCommentProperty.isEmpty().get() || + userIdentity.isNull().get(), + snapshotDataDirty, snapshotNameProperty, snapshotCommentProperty, userIdentity)); saveSnapshotAndCreateLogEntryButton.disableProperty().bind(Bindings.createBooleanBinding(() -> ( snapshotDataDirty.not().get()) || snapshotNameProperty.isEmpty().get() || - snapshotCommentProperty.isEmpty().get(), - snapshotDataDirty, snapshotNameProperty, snapshotCommentProperty)); + snapshotCommentProperty.isEmpty().get() || + userIdentity.isNull().get(), + snapshotDataDirty, snapshotNameProperty, snapshotCommentProperty, userIdentity)); // Do not show the create log entry button if no event receivers have been registered saveSnapshotAndCreateLogEntryButton.visibleProperty().set(ServiceLoader.load(SaveAndRestoreEventReceiver.class).iterator().hasNext()); - restoreButton.disableProperty().bind(snapshotRestorableProperty.not()); - restoreAndLogButton.disableProperty().bind(snapshotRestorableProperty.not()); + restoreButton.disableProperty().bind(Bindings.createBooleanBinding(() -> + snapshotRestorableProperty.not().get() || + userIdentity.isNull().get(), snapshotRestorableProperty, userIdentity)); + restoreAndLogButton.disableProperty().bind(Bindings.createBooleanBinding(() -> + snapshotRestorableProperty.not().get() || userIdentity.isNull().get(), snapshotRestorableProperty, userIdentity)); SpinnerValueFactory thresholdSpinnerValueFactory = new SpinnerValueFactory.DoubleSpinnerValueFactory(0.0, 999.0, 0.0, 0.01); thresholdSpinnerValueFactory.setConverter(new DoubleStringConverter()); @@ -264,6 +273,8 @@ public void initialize() { }); } }); + + } public SimpleStringProperty getSnapshotNameProperty() { @@ -326,7 +337,7 @@ public void setFilterToolbarDisabled(boolean disabled) { filterToolbar.disableProperty().set(disabled); } - public void setSnapshotRestorableProperty(boolean restorable){ + public void setSnapshotRestorableProperty(boolean restorable) { snapshotRestorableProperty.set(restorable); } } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotTab.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotTab.java index 6818ccc8eb..34fdf88271 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotTab.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotTab.java @@ -47,8 +47,6 @@ public class SnapshotTab extends SaveAndRestoreTab { public SaveAndRestoreService saveAndRestoreService; - private SnapshotController snapshotController; - private final SimpleObjectProperty tabGraphicImageProperty = new SimpleObjectProperty<>(); @@ -84,7 +82,7 @@ public SnapshotTab(org.phoebus.applications.saveandrestore.model.Node node, Save try { setContent(loader.load()); - snapshotController = loader.getController(); + controller = loader.getController(); } catch (IOException e) { Logger.getLogger(SnapshotTab.class.getName()) .log(Level.SEVERE, "Failed to load fxml", e); @@ -100,7 +98,7 @@ public SnapshotTab(org.phoebus.applications.saveandrestore.model.Node node, Save setTabImage(node); setOnCloseRequest(event -> { - if (snapshotController != null && !snapshotController.handleSnapshotTabClosed()) { + if (controller != null && !((SnapshotController) controller).handleSnapshotTabClosed()) { event.consume(); } else { SaveAndRestoreService.getInstance().removeNodeChangeListener(this); @@ -116,15 +114,11 @@ public void updateTabTitle(String name) { } private void setTabImage(Node node) { - if (node.getNodeType().equals(NodeType.COMPOSITE_SNAPSHOT)) { - tabGraphicImageProperty.set(ImageRepository.COMPOSITE_SNAPSHOT); + boolean golden = node.getTags() != null && node.getTags().stream().anyMatch(t -> t.getName().equals(Tag.GOLDEN)); + if (golden) { + tabGraphicImageProperty.set(ImageRepository.GOLDEN_SNAPSHOT); } else { - boolean golden = node.getTags() != null && node.getTags().stream().anyMatch(t -> t.getName().equals(Tag.GOLDEN)); - if (golden) { - tabGraphicImageProperty.set(ImageRepository.GOLDEN_SNAPSHOT); - } else { - tabGraphicImageProperty.set(ImageRepository.SNAPSHOT); - } + tabGraphicImageProperty.set(ImageRepository.SNAPSHOT); } } @@ -136,7 +130,7 @@ private void setTabImage(Node node) { */ public void newSnapshot(org.phoebus.applications.saveandrestore.model.Node configurationNode) { setId(null); - snapshotController.newSnapshot(configurationNode); + ((SnapshotController) controller).newSnapshot(configurationNode); } /** @@ -147,28 +141,29 @@ public void newSnapshot(org.phoebus.applications.saveandrestore.model.Node confi public void loadSnapshot(Node snapshotNode) { updateTabTitle(snapshotNode.getName()); setId(snapshotNode.getUniqueId()); - snapshotController.loadSnapshot(snapshotNode); + ((SnapshotController) controller).loadSnapshot(snapshotNode); } public void addSnapshot(org.phoebus.applications.saveandrestore.model.Node node) { - snapshotController.addSnapshot(node); + ((SnapshotController) controller).addSnapshot(node); } @Override public void nodeChanged(Node node) { if (node.getUniqueId().equals(getId())) { Platform.runLater(() -> { - snapshotController.setSnapshotNameProperty(node.getName()); + ((SnapshotController) controller).setSnapshotNameProperty(node.getName()); setTabImage(node); }); } } public Node getSnapshotNode() { - return snapshotController.getSnapshot().getSnapshotNode(); + return ((SnapshotController) controller).getSnapshot().getSnapshotNode(); } public Node getConfigNode() { - return snapshotController.getConfigurationNode(); + return ((SnapshotController) controller).getConfigurationNode(); } + } diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotTableViewController.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotTableViewController.java index e84dcfb842..4c762d7cb5 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotTableViewController.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/SnapshotTableViewController.java @@ -160,6 +160,8 @@ public void updateItem(final Boolean item, final boolean empty) { } public void takeSnapshot(Consumer consumer) { + // Clear snapshots array + snapshots.clear(); List entries = new ArrayList<>(); readAll(list -> Platform.runLater(() -> { diff --git a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/tag/TagUtil.java b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/tag/TagUtil.java index cce94ebf67..75666e05cb 100644 --- a/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/tag/TagUtil.java +++ b/app/save-and-restore/app/src/main/java/org/phoebus/applications/saveandrestore/ui/snapshot/tag/TagUtil.java @@ -44,7 +44,9 @@ import org.phoebus.applications.saveandrestore.ui.snapshot.SnapshotNewTagDialog; import org.phoebus.framework.autocomplete.ProposalService; import org.phoebus.ui.autocomplete.AutocompleteMenu; +import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -96,7 +98,7 @@ public static void tagWithComment(Menu parentMenu, Consumer> callback) { AtomicInteger nonSnapshotCount = new AtomicInteger(0); selectedNodes.forEach(n -> { - if (!n.getNodeType().equals(NodeType.SNAPSHOT)) { + if (!n.getNodeType().equals(NodeType.SNAPSHOT) && !n.getNodeType().equals(NodeType.COMPOSITE_SNAPSHOT)) { nonSnapshotCount.incrementAndGet(); } }); @@ -132,6 +134,9 @@ public static void tagWithComment(Menu parentMenu, List updatedNodes = SaveAndRestoreService.getInstance().deleteTag(tagData); callback.accept(updatedNodes); } catch (Exception e) { + ExceptionDetailsErrorDialog.openError(Messages.errorGeneric, + Messages.errorDeleteTagFailed, + e); Logger.getLogger(TagUtil.class.getName()).log(Level.WARNING, "Failed to remove tag from node", e); } } @@ -168,6 +173,9 @@ public static List addTag(Li tagData.setUniqueNodeIds(selectedNodeIds); updatedNodes.addAll(SaveAndRestoreService.getInstance().addTag(tagData)); } catch (Exception e) { + ExceptionDetailsErrorDialog.openError(Messages.errorGeneric, + Messages.errorAddTagFailed, + e); Logger.getLogger(TagUtil.class.getName()).log(Level.WARNING, "Failed to add tag to node"); } }); @@ -188,23 +196,22 @@ public static List addTag(Li * * @param selectedNodes List of {@link org.phoebus.applications.saveandrestore.model.Node}s selected by user. * @param menuItem The {@link javafx.scene.control.MenuItem} subject to configuration. + * @return false if the menu item should be disabled. */ - public static void configureGoldenItem(List selectedNodes, MenuItem menuItem) { + public static boolean configureGoldenItem(List selectedNodes, MenuItem menuItem) { AtomicInteger goldenTagCount = new AtomicInteger(0); AtomicInteger nonSnapshotCount = new AtomicInteger(0); selectedNodes.forEach(node -> { if (node.hasTag(Tag.GOLDEN)) { goldenTagCount.incrementAndGet(); - } else if (!node.getNodeType().equals(NodeType.SNAPSHOT) && !node.getNodeType().equals(NodeType.COMPOSITE_SNAPSHOT)) { + } else if (!node.getNodeType().equals(NodeType.SNAPSHOT)) { nonSnapshotCount.incrementAndGet(); } }); if (nonSnapshotCount.get() > 0) { - menuItem.disableProperty().set(true); - return; + return false; } if (goldenTagCount.get() == selectedNodes.size()) { - menuItem.disableProperty().set(false); menuItem.setText(Messages.contextMenuRemoveGoldenTag); menuItem.setGraphic(new ImageView(ImageRepository.SNAPSHOT)); menuItem.setOnAction(event -> { @@ -214,11 +221,14 @@ public static void configureGoldenItem(List { @@ -228,11 +238,15 @@ public static void configureGoldenItem(List - +
    - +
    - +