From 4753d433b59396935b2f13629c2c501f2033c263 Mon Sep 17 00:00:00 2001 From: Rohit Awate Date: Sat, 28 Apr 2018 21:54:36 +0530 Subject: [PATCH 01/15] Added RequestManagersPool to re-use managers --- .../controllers/DashboardController.java | 37 ++------ .../requestmanager/DELETERequestManager.java | 9 +- .../DataDispatchRequestManager.java | 9 +- .../requestmanager/GETRequestManager.java | 9 +- .../requestmanager/RequestManager.java | 16 +++- .../requestmanager/RequestManagersPool.java | 94 +++++++++++++++++++ .../com/rohitawate/everest/util/Services.java | 3 + src/main/resources/templates/LogsFile.html | 2 +- 8 files changed, 128 insertions(+), 51 deletions(-) create mode 100644 src/main/java/com/rohitawate/everest/requestmanager/RequestManagersPool.java diff --git a/src/main/java/com/rohitawate/everest/controllers/DashboardController.java b/src/main/java/com/rohitawate/everest/controllers/DashboardController.java index 8e2b1c4..fe04cfc 100644 --- a/src/main/java/com/rohitawate/everest/controllers/DashboardController.java +++ b/src/main/java/com/rohitawate/everest/controllers/DashboardController.java @@ -25,9 +25,7 @@ import com.rohitawate.everest.models.requests.DELETERequest; import com.rohitawate.everest.models.requests.DataDispatchRequest; import com.rohitawate.everest.models.requests.GETRequest; import com.rohitawate.everest.models.responses.EverestResponse; -import com.rohitawate.everest.requestmanager.DELETERequestManager; import com.rohitawate.everest.requestmanager.DataDispatchRequestManager; -import com.rohitawate.everest.requestmanager.GETRequestManager; import com.rohitawate.everest.requestmanager.RequestManager; import com.rohitawate.everest.util.EverestUtilities; import com.rohitawate.everest.util.Services; @@ -169,18 +167,8 @@ public class DashboardController implements Initializable { GETRequest getRequest = new GETRequest(addressField.getText()); getRequest.setHeaders(headerTabController.getHeaders()); - /* - Creates a new instance if its the first request of that session or - the HTTP method type was changed. Also checks if a request is already being processed. - */ - if (requestManager == null || requestManager.getClass() != GETRequestManager.class) - requestManager = new GETRequestManager(getRequest); - else if (requestManager.isRunning()) { - snackbar.show("Please wait while the current request is processed.", 3000); - return; - } else { - requestManager.setRequest(getRequest); - } + requestManager = Services.pool.get(); + requestManager.setRequest(getRequest); cancelButton.setOnAction(e -> requestManager.cancel()); configureRequestManager(); @@ -195,14 +183,8 @@ public class DashboardController implements Initializable { dataDispatchRequest.setTarget(addressField.getText()); dataDispatchRequest.setHeaders(headerTabController.getHeaders()); - if (requestManager == null || requestManager.getClass() != DataDispatchRequestManager.class) - requestManager = new DataDispatchRequestManager(dataDispatchRequest); - else if (requestManager.isRunning()) { - snackbar.show("Please wait while the current request is processed.", 3000); - return; - } else { - requestManager.setRequest(dataDispatchRequest); - } + requestManager = Services.pool.data(); + requestManager.setRequest(dataDispatchRequest); cancelButton.setOnAction(e -> requestManager.cancel()); configureRequestManager(); @@ -212,16 +194,9 @@ public class DashboardController implements Initializable { DELETERequest deleteRequest = new DELETERequest(addressField.getText()); deleteRequest.setHeaders(headerTabController.getHeaders()); - if (requestManager == null || requestManager.getClass() != DELETERequestManager.class) - requestManager = new DELETERequestManager(deleteRequest); - else if (requestManager.isRunning()) { - snackbar.show("Please wait while the current request is processed.", 3000); - return; - } else { - requestManager.setRequest(deleteRequest); - } - + requestManager = Services.pool.delete(); requestManager.setRequest(deleteRequest); + cancelButton.setOnAction(e -> requestManager.cancel()); configureRequestManager(); requestManager.start(); diff --git a/src/main/java/com/rohitawate/everest/requestmanager/DELETERequestManager.java b/src/main/java/com/rohitawate/everest/requestmanager/DELETERequestManager.java index 693167a..61ae6b6 100644 --- a/src/main/java/com/rohitawate/everest/requestmanager/DELETERequestManager.java +++ b/src/main/java/com/rohitawate/everest/requestmanager/DELETERequestManager.java @@ -16,7 +16,6 @@ package com.rohitawate.everest.requestmanager; -import com.rohitawate.everest.models.requests.EverestRequest; import com.rohitawate.everest.models.responses.EverestResponse; import javafx.concurrent.Task; @@ -25,8 +24,8 @@ import javax.ws.rs.core.Response; public class DELETERequestManager extends RequestManager { - public DELETERequestManager(EverestRequest request) { - super(request); + DELETERequestManager() { + } @Override @@ -36,9 +35,9 @@ public class DELETERequestManager extends RequestManager { protected EverestResponse call() throws Exception { Invocation invocation = requestBuilder.buildDelete(); - long initialTime = System.currentTimeMillis(); + initialTime = System.currentTimeMillis(); Response serverResponse = invocation.invoke(); - response.setTime(initialTime, System.currentTimeMillis()); + finalTime = System.currentTimeMillis(); processServerResponse(serverResponse); diff --git a/src/main/java/com/rohitawate/everest/requestmanager/DataDispatchRequestManager.java b/src/main/java/com/rohitawate/everest/requestmanager/DataDispatchRequestManager.java index 42d0ce8..ede26dc 100644 --- a/src/main/java/com/rohitawate/everest/requestmanager/DataDispatchRequestManager.java +++ b/src/main/java/com/rohitawate/everest/requestmanager/DataDispatchRequestManager.java @@ -17,7 +17,6 @@ package com.rohitawate.everest.requestmanager; import com.rohitawate.everest.models.requests.DataDispatchRequest; -import com.rohitawate.everest.models.requests.EverestRequest; import com.rohitawate.everest.models.responses.EverestResponse; import javafx.concurrent.Task; import org.glassfish.jersey.media.multipart.FormDataMultiPart; @@ -42,8 +41,8 @@ public class DataDispatchRequestManager extends RequestManager { private DataDispatchRequest dataDispatchRequest; private String requestType; - public DataDispatchRequestManager(EverestRequest request) { - super(request); + DataDispatchRequestManager() { + } @Override @@ -55,9 +54,9 @@ public class DataDispatchRequestManager extends RequestManager { requestType = dataDispatchRequest.getRequestType(); Invocation invocation = appendBody(); - long initialTime = System.currentTimeMillis(); + initialTime = System.currentTimeMillis(); Response serverResponse = invocation.invoke(); - response.setTime(initialTime, System.currentTimeMillis()); + finalTime = System.currentTimeMillis(); processServerResponse(serverResponse); diff --git a/src/main/java/com/rohitawate/everest/requestmanager/GETRequestManager.java b/src/main/java/com/rohitawate/everest/requestmanager/GETRequestManager.java index bbf18dc..951510f 100644 --- a/src/main/java/com/rohitawate/everest/requestmanager/GETRequestManager.java +++ b/src/main/java/com/rohitawate/everest/requestmanager/GETRequestManager.java @@ -16,7 +16,6 @@ package com.rohitawate.everest.requestmanager; -import com.rohitawate.everest.models.requests.EverestRequest; import com.rohitawate.everest.models.responses.EverestResponse; import javafx.concurrent.Task; @@ -24,8 +23,8 @@ import javax.ws.rs.core.Response; public class GETRequestManager extends RequestManager { - public GETRequestManager(EverestRequest request) { - super(request); + GETRequestManager() { + } @Override @@ -33,9 +32,9 @@ public class GETRequestManager extends RequestManager { return new Task() { @Override protected EverestResponse call() throws Exception { - long initialTime = System.currentTimeMillis(); + initialTime = System.currentTimeMillis(); Response serverResponse = requestBuilder.get(); - response.setTime(initialTime, System.currentTimeMillis()); + finalTime = System.currentTimeMillis(); processServerResponse(serverResponse); diff --git a/src/main/java/com/rohitawate/everest/requestmanager/RequestManager.java b/src/main/java/com/rohitawate/everest/requestmanager/RequestManager.java index 532183f..405fd05 100644 --- a/src/main/java/com/rohitawate/everest/requestmanager/RequestManager.java +++ b/src/main/java/com/rohitawate/everest/requestmanager/RequestManager.java @@ -34,11 +34,19 @@ import java.util.Map; public abstract class RequestManager extends Service { private final Client client; + long initialTime; + long finalTime; + EverestRequest request; EverestResponse response; Builder requestBuilder; - RequestManager(EverestRequest request) { + RequestManager() { + this.client = initClient(); + } + + private Client initClient() { + Client client; client = ClientBuilder.newBuilder() .register(MultiPartFeature.class) .build(); @@ -48,9 +56,7 @@ public abstract class RequestManager extends Service { client.property(ClientProperties.CONNECT_TIMEOUT, Settings.connectionTimeOut); if (Settings.connectionReadTimeOutEnable) client.property(ClientProperties.READ_TIMEOUT, Settings.connectionReadTimeOut); - - response = new EverestResponse(); - setRequest(request); + return client; } public void setRequest(EverestRequest request) { @@ -80,7 +86,9 @@ public abstract class RequestManager extends Service { } String responseBody = serverResponse.readEntity(String.class); + response = new EverestResponse(); + response.setTime(initialTime, finalTime); response.setBody(responseBody); response.setMediaType(serverResponse.getMediaType()); response.setStatusCode(serverResponse.getStatus()); diff --git a/src/main/java/com/rohitawate/everest/requestmanager/RequestManagersPool.java b/src/main/java/com/rohitawate/everest/requestmanager/RequestManagersPool.java new file mode 100644 index 0000000..a081c0e --- /dev/null +++ b/src/main/java/com/rohitawate/everest/requestmanager/RequestManagersPool.java @@ -0,0 +1,94 @@ +/* + * Copyright 2018 Rohit Awate. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.rohitawate.everest.requestmanager; + +import java.util.LinkedList; + +public class RequestManagersPool { + private LinkedList getManagers; + private LinkedList dataManagers; + private LinkedList deleteManagers; + + public GETRequestManager get() { + if (getManagers == null) { + GETRequestManager newManager = new GETRequestManager(); + + new Thread(() -> { + getManagers = new LinkedList<>(); + getManagers.add(newManager); + }).start(); + + return newManager; + } else { + for (GETRequestManager getManager : getManagers) { + if (!getManager.isRunning()) + return getManager; + } + + GETRequestManager newManager = new GETRequestManager(); + getManagers.add(newManager); + + return newManager; + } + } + + public DataDispatchRequestManager data() { + if (dataManagers == null) { + DataDispatchRequestManager newManager = new DataDispatchRequestManager(); + + new Thread(() -> { + dataManagers = new LinkedList<>(); + dataManagers.add(newManager); + }).start(); + + return newManager; + } else { + for (DataDispatchRequestManager dataManager : dataManagers) { + if (!dataManager.isRunning()) + return dataManager; + } + + DataDispatchRequestManager newManager = new DataDispatchRequestManager(); + dataManagers.add(newManager); + + return newManager; + } + } + + public DELETERequestManager delete() { + if (deleteManagers == null) { + DELETERequestManager newManager = new DELETERequestManager(); + + new Thread(() -> { + deleteManagers = new LinkedList<>(); + deleteManagers.add(newManager); + }).start(); + + return newManager; + } else { + for (DELETERequestManager deleteManager : deleteManagers) { + if (!deleteManager.isRunning()) + return deleteManager; + } + + DELETERequestManager newManager = new DELETERequestManager(); + deleteManagers.add(newManager); + + return newManager; + } + } +} diff --git a/src/main/java/com/rohitawate/everest/util/Services.java b/src/main/java/com/rohitawate/everest/util/Services.java index d165e1a..4f84c57 100644 --- a/src/main/java/com/rohitawate/everest/util/Services.java +++ b/src/main/java/com/rohitawate/everest/util/Services.java @@ -18,6 +18,7 @@ package com.rohitawate.everest.util; import com.google.common.util.concurrent.MoreExecutors; import com.rohitawate.everest.controllers.HomeWindowController; +import com.rohitawate.everest.requestmanager.RequestManagersPool; import com.rohitawate.everest.util.history.HistoryManager; import com.rohitawate.everest.util.logging.Level; import com.rohitawate.everest.util.logging.LoggingService; @@ -30,12 +31,14 @@ public class Services { public static LoggingService loggingService; public static HomeWindowController homeWindowController; public static Executor singleExecutor; + public static RequestManagersPool pool; public static void start() { startServicesThread = new Thread(() -> { loggingService = new LoggingService(Level.INFO); historyManager = new HistoryManager(); singleExecutor = MoreExecutors.directExecutor(); + pool = new RequestManagersPool(); }); startServicesThread.start(); diff --git a/src/main/resources/templates/LogsFile.html b/src/main/resources/templates/LogsFile.html index 927daf1..5cf28b2 100644 --- a/src/main/resources/templates/LogsFile.html +++ b/src/main/resources/templates/LogsFile.html @@ -24,7 +24,7 @@ margin: 0px; background-color: #282828; font-size: 15px; - font-family: monospace; + font-family: "Liberation Mono", monospace; } h1 { From b81b67cc6fd7d27d35ee66a04bb8c8f2a16d9c13 Mon Sep 17 00:00:00 2001 From: Rohit Awate Date: Sat, 28 Apr 2018 22:43:02 +0530 Subject: [PATCH 02/15] Added Request object recycling to DashboardController --- .../controllers/BodyTabController.java | 12 ++-- .../controllers/DashboardController.java | 71 +++++++++++++++---- .../models/requests/DataDispatchRequest.java | 4 ++ 3 files changed, 68 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/rohitawate/everest/controllers/BodyTabController.java b/src/main/java/com/rohitawate/everest/controllers/BodyTabController.java index e9147f9..2ea9289 100644 --- a/src/main/java/com/rohitawate/everest/controllers/BodyTabController.java +++ b/src/main/java/com/rohitawate/everest/controllers/BodyTabController.java @@ -44,16 +44,16 @@ public class BodyTabController implements Initializable { @FXML private TabPane bodyTabPane; @FXML - private ComboBox rawInputTypeBox; + ComboBox rawInputTypeBox; @FXML - private TextArea rawInputArea; + TextArea rawInputArea; @FXML - private Tab rawTab, binaryTab, formTab, urlTab; + Tab rawTab, binaryTab, formTab, urlTab; @FXML - private TextField filePathField; + TextField filePathField; - private FormDataTabController formDataTabController; - private URLTabController urlTabController; + FormDataTabController formDataTabController; + URLTabController urlTabController; @Override public void initialize(URL location, ResourceBundle resources) { diff --git a/src/main/java/com/rohitawate/everest/controllers/DashboardController.java b/src/main/java/com/rohitawate/everest/controllers/DashboardController.java index fe04cfc..003e349 100644 --- a/src/main/java/com/rohitawate/everest/controllers/DashboardController.java +++ b/src/main/java/com/rohitawate/everest/controllers/DashboardController.java @@ -47,6 +47,7 @@ import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javax.ws.rs.ProcessingException; +import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.io.FileNotFoundException; import java.io.IOException; @@ -73,7 +74,7 @@ public class DashboardController implements Initializable { private Label statusCode, statusCodeDescription, responseTime, responseSize, errorTitle, errorDetails, responseType; @FXML - private JFXButton sendButton, cancelButton; + private JFXButton cancelButton; @FXML TabPane requestOptionsTab; @FXML @@ -93,6 +94,10 @@ public class DashboardController implements Initializable { private IntegerProperty paramsCountProperty; private Accordion accordion; + private GETRequest getRequest; + private DataDispatchRequest dataRequest; + private DELETERequest deleteRequest; + @Override public void initialize(URL url, ResourceBundle rb) { applyDashboardSettings(); @@ -142,7 +147,7 @@ public class DashboardController implements Initializable { }); errorTitle.setText("Oops... That's embarrassing!"); - errorDetails.setText("Something went wrong. Try to make another request.\nRestart Everest if that doesn't work."); + errorDetails.setText("Something went wrong. Try to make another getRequest.\nRestart Everest if that doesn't work."); setupVisualizer(); } @@ -164,7 +169,10 @@ public class DashboardController implements Initializable { } switch (httpMethodBox.getValue()) { case "GET": - GETRequest getRequest = new GETRequest(addressField.getText()); + if (getRequest == null) + getRequest = new GETRequest(); + + getRequest.setTarget(addressField.getText()); getRequest.setHeaders(headerTabController.getHeaders()); requestManager = Services.pool.get(); @@ -174,24 +182,61 @@ public class DashboardController implements Initializable { configureRequestManager(); requestManager.start(); break; - // DataDispatchRequestManager will generate appropriate request based on the type. + // DataDispatchRequestManager will generate appropriate getRequest based on the type. case "POST": case "PUT": case "PATCH": - DataDispatchRequest dataDispatchRequest = - bodyTabController.getBasicRequest(httpMethodBox.getValue()); - dataDispatchRequest.setTarget(addressField.getText()); - dataDispatchRequest.setHeaders(headerTabController.getHeaders()); + if (dataRequest == null) + dataRequest = new DataDispatchRequest(); + + dataRequest.setRequestType(httpMethodBox.getValue()); + dataRequest.setTarget(addressField.getText()); + dataRequest.setHeaders(headerTabController.getHeaders()); + + if (bodyTabController.rawTab.isSelected()) { + String contentType; + switch (bodyTabController.rawInputTypeBox.getValue()) { + case "PLAIN TEXT": + contentType = MediaType.TEXT_PLAIN; + break; + case "JSON": + contentType = MediaType.APPLICATION_JSON; + break; + case "XML": + contentType = MediaType.APPLICATION_XML; + break; + case "HTML": + contentType = MediaType.TEXT_HTML; + break; + default: + contentType = MediaType.TEXT_PLAIN; + } + dataRequest.setContentType(contentType); + dataRequest.setBody(bodyTabController.rawInputArea.getText()); + } else if (bodyTabController.formTab.isSelected()) { + dataRequest.setStringTuples(bodyTabController.formDataTabController.getStringTuples()); + dataRequest.setFileTuples(bodyTabController.formDataTabController.getFileTuples()); + dataRequest.setContentType(MediaType.MULTIPART_FORM_DATA); + } else if (bodyTabController.binaryTab.isSelected()) { + dataRequest.setBody(bodyTabController.filePathField.getText()); + dataRequest.setContentType(MediaType.APPLICATION_OCTET_STREAM); + } else if (bodyTabController.urlTab.isSelected()) { + dataRequest.setStringTuples(bodyTabController.urlTabController.getStringTuples()); + dataRequest.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + } requestManager = Services.pool.data(); - requestManager.setRequest(dataDispatchRequest); + requestManager.setRequest(dataRequest); cancelButton.setOnAction(e -> requestManager.cancel()); configureRequestManager(); requestManager.start(); break; case "DELETE": - DELETERequest deleteRequest = new DELETERequest(addressField.getText()); + if (deleteRequest == null) + deleteRequest = new DELETERequest(); + + deleteRequest.setTarget(addressField.getText()); deleteRequest.setHeaders(headerTabController.getHeaders()); requestManager = Services.pool.delete(); @@ -212,7 +257,7 @@ public class DashboardController implements Initializable { Services.loggingService.logSevere("Request execution failed.", E, LocalDateTime.now()); errorLayer.setVisible(true); errorTitle.setText("Oops... That's embarrassing!"); - errorDetails.setText("Something went wrong. Try to make another request.\nRestart Everest if that doesn't work."); + errorDetails.setText("Something went wrong. Try to make another getRequest.\nRestart Everest if that doesn't work."); } } @@ -228,7 +273,7 @@ public class DashboardController implements Initializable { promptLayer.setVisible(false); Throwable throwable = requestManager.getException(); Exception exception = (Exception) throwable; - Services.loggingService.logWarning(httpMethodBox.getValue() + " request could not be processed.", exception, LocalDateTime.now()); + Services.loggingService.logWarning(httpMethodBox.getValue() + " getRequest could not be processed.", exception, LocalDateTime.now()); if (throwable.getClass() == UnreliableResponseException.class) { UnreliableResponseException URE = (UnreliableResponseException) throwable; @@ -249,7 +294,7 @@ public class DashboardController implements Initializable { if (requestManager.getClass() == DataDispatchRequestManager.class) { if (throwable.getCause() != null && throwable.getCause().getClass() == IllegalArgumentException.class) { errorTitle.setText("Did you forget something?"); - errorDetails.setText("Please specify at least one body part for your " + httpMethodBox.getValue() + " request."); + errorDetails.setText("Please specify at least one body part for your " + httpMethodBox.getValue() + " getRequest."); } else if (throwable.getClass() == FileNotFoundException.class) { errorTitle.setText("File(s) not found:"); errorDetails.setText(throwable.getMessage()); diff --git a/src/main/java/com/rohitawate/everest/models/requests/DataDispatchRequest.java b/src/main/java/com/rohitawate/everest/models/requests/DataDispatchRequest.java index d8a499a..2638b3b 100644 --- a/src/main/java/com/rohitawate/everest/models/requests/DataDispatchRequest.java +++ b/src/main/java/com/rohitawate/everest/models/requests/DataDispatchRequest.java @@ -71,4 +71,8 @@ public class DataDispatchRequest extends EverestRequest implements Serializable public String getRequestType() { return requestType; } + + public void setRequestType(String requestType) { + this.requestType = requestType; + } } From c0e4d3f72ba175a451d7fac84cf010f688e2c177 Mon Sep 17 00:00:00 2001 From: Rohit Awate Date: Sat, 28 Apr 2018 23:54:09 +0530 Subject: [PATCH 03/15] Heavily optimized Visualizer by switching from Accordions to TreeView --- .../controllers/DashboardController.java | 60 +++++++++---------- src/main/resources/css/Adreana.css | 52 +++++++++------- 2 files changed, 61 insertions(+), 51 deletions(-) diff --git a/src/main/java/com/rohitawate/everest/controllers/DashboardController.java b/src/main/java/com/rohitawate/everest/controllers/DashboardController.java index 003e349..7ebf50a 100644 --- a/src/main/java/com/rohitawate/everest/controllers/DashboardController.java +++ b/src/main/java/com/rohitawate/everest/controllers/DashboardController.java @@ -38,7 +38,6 @@ import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.fxml.Initializable; -import javafx.scene.CacheHint; import javafx.scene.Parent; import javafx.scene.control.*; import javafx.scene.input.KeyCode; @@ -92,7 +91,7 @@ public class DashboardController implements Initializable { private HeaderTabController headerTabController; private BodyTabController bodyTabController; private IntegerProperty paramsCountProperty; - private Accordion accordion; + private TreeView treeView; private GETRequest getRequest; private DataDispatchRequest dataRequest; @@ -355,9 +354,10 @@ public class DashboardController implements Initializable { responseType.setText("JSON"); JsonNode node = EverestUtilities.mapper.readTree(responseBody); responseArea.setText(EverestUtilities.mapper.writeValueAsString(node)); - accordion.getPanes().clear(); visualizerTab.setDisable(false); - populateVisualizer(accordion, "root", node); + TreeItem root = new TreeItem<>(); + treeView.setRoot(root); + populateVisualizer(root, "root", node); break; case "application/xml": responseType.setText("XML"); @@ -385,18 +385,19 @@ public class DashboardController implements Initializable { } private void setupVisualizer() { - accordion = new Accordion(); - accordion.setCache(true); - accordion.setCacheHint(CacheHint.SPEED); - visualizer.setContent(accordion); + treeView = new TreeView<>(); + visualizer.setContent(treeView); } - private void populateVisualizer(Accordion rootAccordion, String rootName, JsonNode root) { + private void populateVisualizer(TreeItem rootItem, String rootName, JsonNode root) { + Label rootLabel = new Label(rootName); + rootLabel.getStyleClass().addAll("visualizerRootLabel", "visualizerLabel"); + rootItem.setValue(new HBox(rootLabel)); + JsonNode currentNode; - VBox container = new VBox(); - container.setStyle("-fx-padding: 3px 30px"); Label valueLabel; - TitledPane pane = new TitledPane(rootName, container); + HBox valueContainer; + List> items = new LinkedList<>(); Tooltip valueTooltip; if (root.isArray()) { @@ -407,23 +408,22 @@ public class DashboardController implements Initializable { if (currentNode.isValueNode()) { valueLabel = new Label(currentNode.toString()); - valueLabel.getStyleClass().add("visualizerValueLabel"); + valueLabel.getStyleClass().addAll("visualizerValueLabel", "visualizerLabel"); valueLabel.setWrapText(true); valueTooltip = new Tooltip(currentNode.toString()); valueLabel.setTooltip(valueTooltip); - container.getChildren().add(valueLabel); + valueContainer = new HBox(valueLabel); + items.add(new TreeItem<>(valueContainer)); } else if (currentNode.isObject()) { - Accordion arrayAccordion = new Accordion(); - container.getChildren().add(arrayAccordion); - populateVisualizer(arrayAccordion, "", currentNode); + TreeItem newRoot = new TreeItem<>(); + items.add(newRoot); + populateVisualizer(newRoot, "", currentNode); } } - rootAccordion.getPanes().add(pane); } else { Iterator> iterator = root.fields(); Entry currentEntry; - HBox valueContainer; Label keyLabel; Tooltip keyTooltip; @@ -433,32 +433,30 @@ public class DashboardController implements Initializable { if (currentNode.isValueNode()) { keyLabel = new Label(currentEntry.getKey() + ": "); - keyLabel.setStyle("-fx-font-weight: bold"); - keyLabel.getStyleClass().add("visualizerKeyLabel"); + keyLabel.getStyleClass().addAll("visualizerKeyLabel", "visualizerLabel"); keyTooltip = new Tooltip(currentEntry.getKey()); keyLabel.setTooltip(keyTooltip); valueLabel = new Label(currentNode.toString()); - valueLabel.getStyleClass().add("visualizerValueLabel"); + valueLabel.getStyleClass().addAll("visualizerValueLabel", "visualizerLabel"); valueLabel.setWrapText(true); valueTooltip = new Tooltip(currentNode.toString()); valueLabel.setTooltip(valueTooltip); valueContainer = new HBox(keyLabel, valueLabel); - container.getChildren().add(valueContainer); + items.add(new TreeItem<>(valueContainer)); } else if (currentNode.isArray() || currentNode.isObject()) { - Accordion arrayAccordion = new Accordion(); - container.getChildren().add(arrayAccordion); - populateVisualizer(arrayAccordion, currentEntry.getKey(), currentNode); + TreeItem newRoot = new TreeItem<>(); + items.add(newRoot); + populateVisualizer(newRoot, currentEntry.getKey(), currentNode); } } - rootAccordion.getPanes().add(pane); } - if (!rootName.equals("root")) { - pane.getStyleClass().add("nonRootTitledPane"); // Special CSS class to set padding for non-root panes only - } else { - rootAccordion.setExpandedPane(pane); + rootItem.getChildren().addAll(items); + + if (rootName.equals("root")) { + rootItem.setExpanded(true); } } diff --git a/src/main/resources/css/Adreana.css b/src/main/resources/css/Adreana.css index ae9f53f..46aa69c 100644 --- a/src/main/resources/css/Adreana.css +++ b/src/main/resources/css/Adreana.css @@ -312,41 +312,53 @@ -fx-background-color: #2a2a2a; } -.nonRootTitledPane { - -fx-padding: 3px 0px; -} - -.titled-pane .title { +.visualizerLabel { -fx-font-family: "Liberation Mono", monospace; - -fx-background-color: #404040; } -.titled-pane .content { - -fx-background-color: #282828; - -fx-border-width: 0px; -} - -.titled-pane .content { - -fx-font-family: "Liberation Mono", monospace; - -fx-padding: 3px 0px; +.visualizerRootLabel { + -fx-font-size: 19px; + -fx-text-fill: white; + -fx-font-weight: bold; } .visualizerKeyLabel { -fx-font-size: 18px; -fx-text-fill: #bababa; - -fx-text-alignment: left; + -fx-font-weight: bold; } .visualizerValueLabel { -fx-font-size: 18px; -fx-text-fill: #959595; - -fx-text-alignment: left; } -.titled-pane .title .text, -.titled-pane .title .arrow-button .arrow { - -fx-background-color: #dadada; - -fx-fill: #dadada; +/* Visualizer tree */ +.tree-view { + -fx-background-color: #353535; + -fx-padding: 0px; +} + +.tree-cell .tree-disclosure-node .arrow { + -fx-background-color: orangered; +} + +.tree-cell { + -fx-background-color: #282828; + -fx-border-width: 0px; +} + +.tree-cell:selected, +.tree-cell:expanded, +.tree-cell:focused { + -fx-background-color: #454545; +} + +.tree-view .scroll-bar:horizontal .increment-arrow, +.tree-view .scroll-bar:horizontal .decrement-arrow, +.tree-view .scroll-bar:horizontal .increment-button, +.tree-view .scroll-bar:horizontal .decrement-button { + -fx-padding: 0; } /* SnackBar */ From 6d065186933a15e0641b1cb915cefcac99bddece Mon Sep 17 00:00:00 2001 From: Rohit Awate Date: Sun, 29 Apr 2018 12:46:05 +0530 Subject: [PATCH 04/15] Minor improvements to Visualizer and moved its logic into new class --- .../controllers/DashboardController.java | 97 +++-------------- .../everest/controllers/Visualizer.java | 102 ++++++++++++++++++ src/main/resources/css/Adreana.css | 23 ++-- .../resources/fxml/homewindow/Dashboard.fxml | 2 +- 4 files changed, 135 insertions(+), 89 deletions(-) create mode 100644 src/main/java/com/rohitawate/everest/controllers/Visualizer.java diff --git a/src/main/java/com/rohitawate/everest/controllers/DashboardController.java b/src/main/java/com/rohitawate/everest/controllers/DashboardController.java index 7ebf50a..4f49e6a 100644 --- a/src/main/java/com/rohitawate/everest/controllers/DashboardController.java +++ b/src/main/java/com/rohitawate/everest/controllers/DashboardController.java @@ -53,8 +53,11 @@ import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.time.LocalDateTime; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; import java.util.Map.Entry; +import java.util.ResourceBundle; public class DashboardController implements Initializable { @FXML @@ -81,7 +84,7 @@ public class DashboardController implements Initializable { @FXML private Tab visualizerTab; @FXML - private ScrollPane visualizer; + private ScrollPane scrollPane; private JFXSnackbar snackbar; private final String[] httpMethods = {"GET", "POST", "PUT", "DELETE", "PATCH"}; @@ -91,7 +94,7 @@ public class DashboardController implements Initializable { private HeaderTabController headerTabController; private BodyTabController bodyTabController; private IntegerProperty paramsCountProperty; - private TreeView treeView; + private Visualizer visualizer; private GETRequest getRequest; private DataDispatchRequest dataRequest; @@ -148,11 +151,17 @@ public class DashboardController implements Initializable { errorTitle.setText("Oops... That's embarrassing!"); errorDetails.setText("Something went wrong. Try to make another getRequest.\nRestart Everest if that doesn't work."); - setupVisualizer(); + visualizer = new Visualizer(); + scrollPane.setContent(visualizer); } @FXML void sendRequest() { + if (requestManager != null && requestManager.isRunning()) { + snackbar.show("Please wait while the current request is processed.", 5000); + return; + } + promptLayer.setVisible(false); if (responseBox.getChildren().size() == 2) { responseBox.getChildren().remove(0); @@ -356,8 +365,8 @@ public class DashboardController implements Initializable { responseArea.setText(EverestUtilities.mapper.writeValueAsString(node)); visualizerTab.setDisable(false); TreeItem root = new TreeItem<>(); - treeView.setRoot(root); - populateVisualizer(root, "root", node); + visualizer.setRoot(root); + visualizer.populate(root, "root", node); break; case "application/xml": responseType.setText("XML"); @@ -384,82 +393,6 @@ public class DashboardController implements Initializable { } } - private void setupVisualizer() { - treeView = new TreeView<>(); - visualizer.setContent(treeView); - } - - private void populateVisualizer(TreeItem rootItem, String rootName, JsonNode root) { - Label rootLabel = new Label(rootName); - rootLabel.getStyleClass().addAll("visualizerRootLabel", "visualizerLabel"); - rootItem.setValue(new HBox(rootLabel)); - - JsonNode currentNode; - Label valueLabel; - HBox valueContainer; - List> items = new LinkedList<>(); - Tooltip valueTooltip; - - if (root.isArray()) { - Iterator iterator = root.elements(); - - while (iterator.hasNext()) { - currentNode = iterator.next(); - - if (currentNode.isValueNode()) { - valueLabel = new Label(currentNode.toString()); - valueLabel.getStyleClass().addAll("visualizerValueLabel", "visualizerLabel"); - valueLabel.setWrapText(true); - valueTooltip = new Tooltip(currentNode.toString()); - valueLabel.setTooltip(valueTooltip); - - valueContainer = new HBox(valueLabel); - items.add(new TreeItem<>(valueContainer)); - } else if (currentNode.isObject()) { - TreeItem newRoot = new TreeItem<>(); - items.add(newRoot); - populateVisualizer(newRoot, "", currentNode); - } - } - } else { - Iterator> iterator = root.fields(); - Entry currentEntry; - Label keyLabel; - Tooltip keyTooltip; - - while (iterator.hasNext()) { - currentEntry = iterator.next(); - currentNode = currentEntry.getValue(); - - if (currentNode.isValueNode()) { - keyLabel = new Label(currentEntry.getKey() + ": "); - keyLabel.getStyleClass().addAll("visualizerKeyLabel", "visualizerLabel"); - keyTooltip = new Tooltip(currentEntry.getKey()); - keyLabel.setTooltip(keyTooltip); - - valueLabel = new Label(currentNode.toString()); - valueLabel.getStyleClass().addAll("visualizerValueLabel", "visualizerLabel"); - valueLabel.setWrapText(true); - valueTooltip = new Tooltip(currentNode.toString()); - valueLabel.setTooltip(valueTooltip); - - valueContainer = new HBox(keyLabel, valueLabel); - items.add(new TreeItem<>(valueContainer)); - } else if (currentNode.isArray() || currentNode.isObject()) { - TreeItem newRoot = new TreeItem<>(); - items.add(newRoot); - populateVisualizer(newRoot, currentEntry.getKey(), currentNode); - } - } - } - - rootItem.getChildren().addAll(items); - - if (rootName.equals("root")) { - rootItem.setExpanded(true); - } - } - private void applyDashboardSettings() { String responseAreaCSS = "-fx-font-family: " + Settings.responseAreaFont + ";" + "-fx-font-size: " + Settings.responseAreaFontSize; diff --git a/src/main/java/com/rohitawate/everest/controllers/Visualizer.java b/src/main/java/com/rohitawate/everest/controllers/Visualizer.java new file mode 100644 index 0000000..3523ef7 --- /dev/null +++ b/src/main/java/com/rohitawate/everest/controllers/Visualizer.java @@ -0,0 +1,102 @@ +/* + * Copyright 2018 Rohit Awate. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.rohitawate.everest.controllers; + +import com.fasterxml.jackson.databind.JsonNode; +import javafx.scene.control.Label; +import javafx.scene.control.Tooltip; +import javafx.scene.control.TreeItem; +import javafx.scene.control.TreeView; +import javafx.scene.layout.HBox; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +class Visualizer extends TreeView { + Visualizer() { + this.setShowRoot(false); + } + + void populate(TreeItem rootItem, String rootName, JsonNode root) { + Label rootLabel = new Label(rootName); + rootLabel.getStyleClass().addAll("visualizerRootLabel", "visualizerLabel"); + rootItem.setValue(new HBox(rootLabel)); + + JsonNode currentNode; + Label valueLabel; + HBox valueContainer; + List> items = new LinkedList<>(); + Tooltip valueTooltip; + + if (root.isArray()) { + Iterator iterator = root.elements(); + + while (iterator.hasNext()) { + currentNode = iterator.next(); + + if (currentNode.isValueNode()) { + valueLabel = new Label(currentNode.toString()); + valueLabel.getStyleClass().addAll("visualizerValueLabel", "visualizerLabel"); + valueLabel.setWrapText(true); + valueTooltip = new Tooltip(currentNode.toString()); + valueLabel.setTooltip(valueTooltip); + + valueContainer = new HBox(valueLabel); + items.add(new TreeItem<>(valueContainer)); + } else if (currentNode.isObject()) { + TreeItem newRoot = new TreeItem<>(); + items.add(newRoot); + populate(newRoot, "[Anonymous Object]", currentNode); + } + } + } else { + Iterator> iterator = root.fields(); + Map.Entry currentEntry; + Label keyLabel; + Tooltip keyTooltip; + + while (iterator.hasNext()) { + currentEntry = iterator.next(); + currentNode = currentEntry.getValue(); + + if (currentNode.isValueNode()) { + keyLabel = new Label(currentEntry.getKey() + ": "); + keyLabel.getStyleClass().addAll("visualizerKeyLabel", "visualizerLabel"); + keyTooltip = new Tooltip(currentEntry.getKey()); + keyLabel.setTooltip(keyTooltip); + + valueLabel = new Label(currentNode.toString()); + valueLabel.getStyleClass().addAll("visualizerValueLabel", "visualizerLabel"); + valueLabel.setWrapText(true); + valueTooltip = new Tooltip(currentNode.toString()); + valueLabel.setTooltip(valueTooltip); + + valueContainer = new HBox(keyLabel, valueLabel); + items.add(new TreeItem<>(valueContainer)); + } else if (currentNode.isArray() || currentNode.isObject()) { + TreeItem newRoot = new TreeItem<>(); + items.add(newRoot); + populate(newRoot, currentEntry.getKey(), currentNode); + } + } + } + + rootItem.getChildren().addAll(items); + } +} diff --git a/src/main/resources/css/Adreana.css b/src/main/resources/css/Adreana.css index 46aa69c..aed6d41 100644 --- a/src/main/resources/css/Adreana.css +++ b/src/main/resources/css/Adreana.css @@ -317,8 +317,8 @@ } .visualizerRootLabel { - -fx-font-size: 19px; - -fx-text-fill: white; + -fx-font-size: 18px; + -fx-text-fill: #dedede; -fx-font-weight: bold; } @@ -336,24 +336,35 @@ /* Visualizer tree */ .tree-view { -fx-background-color: #353535; - -fx-padding: 0px; } .tree-cell .tree-disclosure-node .arrow { -fx-background-color: orangered; } +.tree-cell:selected .tree-disclosure-node .arrow, +.tree-cell:focused .tree-disclosure-node .arrow { + -fx-background-color: white; +} + .tree-cell { -fx-background-color: #282828; -fx-border-width: 0px; } -.tree-cell:selected, -.tree-cell:expanded, -.tree-cell:focused { +.tree-cell:expanded { -fx-background-color: #454545; } +.tree-cell:selected, +.tree-cell:focused { + -fx-background-color: cornflowerblue; +} + +.tree-cell:selected .label { + -fx-text-fill: white; +} + .tree-view .scroll-bar:horizontal .increment-arrow, .tree-view .scroll-bar:horizontal .decrement-arrow, .tree-view .scroll-bar:horizontal .increment-button, diff --git a/src/main/resources/fxml/homewindow/Dashboard.fxml b/src/main/resources/fxml/homewindow/Dashboard.fxml index c340562..fd3bc3d 100644 --- a/src/main/resources/fxml/homewindow/Dashboard.fxml +++ b/src/main/resources/fxml/homewindow/Dashboard.fxml @@ -210,7 +210,7 @@ - From 861b5deab176876979e41a2520244d9b79dc213a Mon Sep 17 00:00:00 2001 From: Rohit Awate Date: Sun, 29 Apr 2018 15:39:21 +0530 Subject: [PATCH 05/15] Added ResponseHeadersViewer and minor UI tweaks --- .../controllers/DashboardController.java | 28 ++++++---- .../controllers/ResponseHeadersViewer.java | 51 ++++++++++++++++++ .../everest/controllers/Visualizer.java | 26 ++++++--- .../models/responses/EverestResponse.java | 10 ++++ .../requestmanager/RequestManager.java | 1 + src/main/resources/assets/Copy.png | Bin 0 -> 203 bytes src/main/resources/css/Adreana.css | 10 ++-- .../resources/fxml/homewindow/Dashboard.fxml | 19 ++++--- 8 files changed, 118 insertions(+), 27 deletions(-) create mode 100644 src/main/java/com/rohitawate/everest/controllers/ResponseHeadersViewer.java create mode 100644 src/main/resources/assets/Copy.png diff --git a/src/main/java/com/rohitawate/everest/controllers/DashboardController.java b/src/main/java/com/rohitawate/everest/controllers/DashboardController.java index 4f49e6a..8928d40 100644 --- a/src/main/java/com/rohitawate/everest/controllers/DashboardController.java +++ b/src/main/java/com/rohitawate/everest/controllers/DashboardController.java @@ -76,15 +76,13 @@ public class DashboardController implements Initializable { private Label statusCode, statusCodeDescription, responseTime, responseSize, errorTitle, errorDetails, responseType; @FXML - private JFXButton cancelButton; + private JFXButton cancelButton, copyBodyButton; @FXML TabPane requestOptionsTab; @FXML Tab paramsTab, authTab, headersTab, bodyTab; @FXML - private Tab visualizerTab; - @FXML - private ScrollPane scrollPane; + private Tab visualizerTab, responseHeadersTab; private JFXSnackbar snackbar; private final String[] httpMethods = {"GET", "POST", "PUT", "DELETE", "PATCH"}; @@ -95,6 +93,7 @@ public class DashboardController implements Initializable { private BodyTabController bodyTabController; private IntegerProperty paramsCountProperty; private Visualizer visualizer; + private ResponseHeadersViewer responseHeadersViewer; private GETRequest getRequest; private DataDispatchRequest dataRequest; @@ -148,11 +147,21 @@ public class DashboardController implements Initializable { } }); + copyBodyButton.setOnAction(e -> { + responseArea.selectAll(); + responseArea.copy(); + responseArea.deselect(); + snackbar.show("Request body copied to clipboard.", 5000); + }); + errorTitle.setText("Oops... That's embarrassing!"); errorDetails.setText("Something went wrong. Try to make another getRequest.\nRestart Everest if that doesn't work."); visualizer = new Visualizer(); - scrollPane.setContent(visualizer); + visualizerTab.setContent(visualizer); + + responseHeadersViewer = new ResponseHeadersViewer(); + responseHeadersTab.setContent(responseHeadersViewer); } @FXML @@ -321,7 +330,7 @@ public class DashboardController implements Initializable { } private void onSucceeded() { - updateDashboard(requestManager.getValue()); + displayResponse(requestManager.getValue()); errorLayer.setVisible(false); loadingLayer.setVisible(false); requestManager.reset(); @@ -333,13 +342,14 @@ public class DashboardController implements Initializable { loadingLayer.setVisible(true); } - private void updateDashboard(EverestResponse response) { + private void displayResponse(EverestResponse response) { prettifyResponseBody(response); responseBox.getChildren().add(0, responseDetails); statusCode.setText(Integer.toString(response.getStatusCode())); statusCodeDescription.setText(Response.Status.fromStatusCode(response.getStatusCode()).getReasonPhrase()); responseTime.setText(Long.toString(response.getTime()) + " ms"); responseSize.setText(Integer.toString(response.getSize()) + " B"); + responseHeadersViewer.populate(response); } private void prettifyResponseBody(EverestResponse response) { @@ -364,9 +374,7 @@ public class DashboardController implements Initializable { JsonNode node = EverestUtilities.mapper.readTree(responseBody); responseArea.setText(EverestUtilities.mapper.writeValueAsString(node)); visualizerTab.setDisable(false); - TreeItem root = new TreeItem<>(); - visualizer.setRoot(root); - visualizer.populate(root, "root", node); + visualizer.populate(node); break; case "application/xml": responseType.setText("XML"); diff --git a/src/main/java/com/rohitawate/everest/controllers/ResponseHeadersViewer.java b/src/main/java/com/rohitawate/everest/controllers/ResponseHeadersViewer.java new file mode 100644 index 0000000..23f2dce --- /dev/null +++ b/src/main/java/com/rohitawate/everest/controllers/ResponseHeadersViewer.java @@ -0,0 +1,51 @@ +/* + * Copyright 2018 Rohit Awate. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.rohitawate.everest.controllers; + +import com.rohitawate.everest.models.responses.EverestResponse; +import javafx.geometry.Insets; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; + +class ResponseHeadersViewer extends ScrollPane { + private VBox container; + + ResponseHeadersViewer() { + this.container = new VBox(); + container.setPadding(new Insets(10, 20, 10, 20)); + this.setContent(container); + + this.setFitToHeight(true); + this.setFitToWidth(true); + } + + void populate(EverestResponse response) { + container.getChildren().clear(); + + response.getHeaders().forEach((key, value) -> { + Label keyLabel = new Label(key + ": "); + keyLabel.getStyleClass().addAll("visualizerKeyLabel", "visualizerLabel"); + + Label valueLabel = new Label(value.get(0)); + valueLabel.getStyleClass().addAll("visualizerValueLabel", "visualizerLabel"); + + container.getChildren().add(new HBox(keyLabel, valueLabel)); + }); + } +} diff --git a/src/main/java/com/rohitawate/everest/controllers/Visualizer.java b/src/main/java/com/rohitawate/everest/controllers/Visualizer.java index 3523ef7..b31215a 100644 --- a/src/main/java/com/rohitawate/everest/controllers/Visualizer.java +++ b/src/main/java/com/rohitawate/everest/controllers/Visualizer.java @@ -17,10 +17,7 @@ package com.rohitawate.everest.controllers; import com.fasterxml.jackson.databind.JsonNode; -import javafx.scene.control.Label; -import javafx.scene.control.Tooltip; -import javafx.scene.control.TreeItem; -import javafx.scene.control.TreeView; +import javafx.scene.control.*; import javafx.scene.layout.HBox; import java.util.Iterator; @@ -28,12 +25,27 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -class Visualizer extends TreeView { +class Visualizer extends ScrollPane { + private TreeView visualizer; + Visualizer() { - this.setShowRoot(false); + this.visualizer = new TreeView<>(); + this.visualizer.setShowRoot(false); + this.setContent(this.visualizer); + + this.setFitToHeight(true); + this.setFitToWidth(true); } - void populate(TreeItem rootItem, String rootName, JsonNode root) { + void populate(JsonNode node) { + this.populate(new TreeItem<>(), "root", node); + } + + private void populate(TreeItem rootItem, String rootName, JsonNode root) { + if (rootName.equals("root")) { + this.visualizer.setRoot(rootItem); + } + Label rootLabel = new Label(rootName); rootLabel.getStyleClass().addAll("visualizerRootLabel", "visualizerLabel"); rootItem.setValue(new HBox(rootLabel)); diff --git a/src/main/java/com/rohitawate/everest/models/responses/EverestResponse.java b/src/main/java/com/rohitawate/everest/models/responses/EverestResponse.java index d538920..1d7f0b6 100644 --- a/src/main/java/com/rohitawate/everest/models/responses/EverestResponse.java +++ b/src/main/java/com/rohitawate/everest/models/responses/EverestResponse.java @@ -17,6 +17,7 @@ package com.rohitawate.everest.models.responses; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; public class EverestResponse { private String body; @@ -24,6 +25,7 @@ public class EverestResponse { private long time; private int size; private MediaType mediaType; + private MultivaluedMap headers; public String getBody() { return body; @@ -64,4 +66,12 @@ public class EverestResponse { public void setStatusCode(int statusCode) { this.statusCode = statusCode; } + + public MultivaluedMap getHeaders() { + return headers; + } + + public void setHeaders(MultivaluedMap headers) { + this.headers = headers; + } } diff --git a/src/main/java/com/rohitawate/everest/requestmanager/RequestManager.java b/src/main/java/com/rohitawate/everest/requestmanager/RequestManager.java index 405fd05..58a1202 100644 --- a/src/main/java/com/rohitawate/everest/requestmanager/RequestManager.java +++ b/src/main/java/com/rohitawate/everest/requestmanager/RequestManager.java @@ -88,6 +88,7 @@ public abstract class RequestManager extends Service { String responseBody = serverResponse.readEntity(String.class); response = new EverestResponse(); + response.setHeaders(serverResponse.getStringHeaders()); response.setTime(initialTime, finalTime); response.setBody(responseBody); response.setMediaType(serverResponse.getMediaType()); diff --git a/src/main/resources/assets/Copy.png b/src/main/resources/assets/Copy.png new file mode 100644 index 0000000000000000000000000000000000000000..70eb0737882e53ec326b7a37cf335fea48f7b605 GIT binary patch literal 203 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8LpBu^K|kP61+1y(=wf7}1BXBM2m zXx(C%^S}P5zlcK01p!(4V=PuM2d#+)QjXc+PR{(F06zGjcUgI^OC8}K$A zSNM29e6^#>(FcqhRzo5hq8j? z)rtIe%{d+G6v7vWO_&kTRxp+K%M|{sslKWl4DUBa=UZy6U + + + + + + + + + @@ -208,13 +218,8 @@ wrapText="true"/> - - - - - - + + From 6f063a01c885719aa6a66e9212290f59544792cf Mon Sep 17 00:00:00 2001 From: Rohit Awate Date: Thu, 3 May 2018 13:08:35 +0530 Subject: [PATCH 06/15] Fixed crucial typo in DashboardController --- .../com/rohitawate/everest/controllers/DashboardController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/rohitawate/everest/controllers/DashboardController.java b/src/main/java/com/rohitawate/everest/controllers/DashboardController.java index 8928d40..722bca1 100644 --- a/src/main/java/com/rohitawate/everest/controllers/DashboardController.java +++ b/src/main/java/com/rohitawate/everest/controllers/DashboardController.java @@ -151,7 +151,7 @@ public class DashboardController implements Initializable { responseArea.selectAll(); responseArea.copy(); responseArea.deselect(); - snackbar.show("Request body copied to clipboard.", 5000); + snackbar.show("Response body copied to clipboard.", 5000); }); errorTitle.setText("Oops... That's embarrassing!"); From 411214311b2f36fdee43a46629093738de86e65a Mon Sep 17 00:00:00 2001 From: Rohit Awate Date: Thu, 3 May 2018 13:51:20 +0530 Subject: [PATCH 07/15] Added Travis CI --- .travis.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..4e62807 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +language: java +sudo: true + +script: mvn clean package + +deploy: + provider: releases + api_key: + secure: yep + file: target/Everest-Alpha-1.0.jar \ No newline at end of file From ee956baed6dd54a4ea786f4110cdcf3489696f88 Mon Sep 17 00:00:00 2001 From: Rohit Awate Date: Tue, 8 May 2018 21:56:34 +0530 Subject: [PATCH 08/15] Fixed #9: Custom content types now override the ones determined automatically --- .travis.yml | 2 +- .../DataDispatchRequestManager.java | 39 +++++++++++++------ .../requestmanager/RequestManager.java | 3 ++ 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4e62807..ba18bd4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,4 +7,4 @@ deploy: provider: releases api_key: secure: yep - file: target/Everest-Alpha-1.0.jar \ No newline at end of file + file: target/Everest-Alpha-1.0.jar diff --git a/src/main/java/com/rohitawate/everest/requestmanager/DataDispatchRequestManager.java b/src/main/java/com/rohitawate/everest/requestmanager/DataDispatchRequestManager.java index ede26dc..1ada390 100644 --- a/src/main/java/com/rohitawate/everest/requestmanager/DataDispatchRequestManager.java +++ b/src/main/java/com/rohitawate/everest/requestmanager/DataDispatchRequestManager.java @@ -72,6 +72,11 @@ public class DataDispatchRequestManager extends RequestManager { * @return invocation object */ private Invocation appendBody() throws Exception { + /* + Checks if a custom mime-type is mentioned in the headers. + If present, it will override the logical Content-Type. + */ + String overriddenContentType = request.getHeaders().get("Content-Type"); Invocation invocation = null; Map.Entry mapEntry; @@ -79,6 +84,7 @@ public class DataDispatchRequestManager extends RequestManager { case MediaType.MULTIPART_FORM_DATA: FormDataMultiPart formData = new FormDataMultiPart(); + // Adding the string tuples to the request HashMap pairs = dataDispatchRequest.getStringTuples(); for (Map.Entry entry : pairs.entrySet()) { mapEntry = (Map.Entry) entry; @@ -88,8 +94,10 @@ public class DataDispatchRequestManager extends RequestManager { String filePath; File file; boolean fileException = false; - StringBuilder fileExceptionMessage = new StringBuilder(); + String fileExceptionMessage = null; pairs = dataDispatchRequest.getFileTuples(); + + // Adding the file tuples to the request for (Map.Entry entry : pairs.entrySet()) { mapEntry = (Map.Entry) entry; filePath = mapEntry.getValue(); @@ -100,14 +108,13 @@ public class DataDispatchRequestManager extends RequestManager { file, MediaType.APPLICATION_OCTET_STREAM_TYPE)); else { fileException = true; - fileExceptionMessage.append(" - "); - fileExceptionMessage.append(filePath); - fileExceptionMessage.append("\n"); + // For pretty-printing FileNotFoundException to the UI + fileExceptionMessage = " - " + filePath + "\n"; } } if (fileException) { - throw new FileNotFoundException(fileExceptionMessage.toString()); + throw new FileNotFoundException(fileExceptionMessage); } formData.setMediaType(MediaType.MULTIPART_FORM_DATA_TYPE); @@ -118,6 +125,8 @@ public class DataDispatchRequestManager extends RequestManager { invocation = requestBuilder.buildPut(Entity.entity(formData, MediaType.MULTIPART_FORM_DATA_TYPE)); break; case MediaType.APPLICATION_OCTET_STREAM: + if (overriddenContentType == null) + overriddenContentType = MediaType.APPLICATION_OCTET_STREAM; filePath = dataDispatchRequest.getBody(); File check = new File(filePath); @@ -129,11 +138,14 @@ public class DataDispatchRequestManager extends RequestManager { FileInputStream stream = new FileInputStream(filePath); if (requestType.equals("POST")) - invocation = requestBuilder.buildPost(Entity.entity(stream, MediaType.APPLICATION_OCTET_STREAM_TYPE)); + invocation = requestBuilder.buildPost(Entity.entity(stream, overriddenContentType)); else - invocation = requestBuilder.buildPut(Entity.entity(stream, MediaType.APPLICATION_OCTET_STREAM_TYPE)); + invocation = requestBuilder.buildPut(Entity.entity(stream, overriddenContentType)); break; case MediaType.APPLICATION_FORM_URLENCODED: + if (overriddenContentType == null) + overriddenContentType = MediaType.APPLICATION_FORM_URLENCODED; + Form form = new Form(); for (Map.Entry entry : dataDispatchRequest.getStringTuples().entrySet()) { @@ -142,24 +154,27 @@ public class DataDispatchRequestManager extends RequestManager { } if (requestType.equals("POST")) - invocation = requestBuilder.buildPost(Entity.form(form)); + invocation = requestBuilder.buildPost(Entity.entity(form, overriddenContentType)); else - invocation = requestBuilder.buildPut(Entity.form(form)); + invocation = requestBuilder.buildPut(Entity.entity(form, overriddenContentType)); break; default: // Handles raw data types (JSON, Plain text, XML, HTML) + String originalContentType = dataDispatchRequest.getContentType(); + if (overriddenContentType == null) + overriddenContentType = originalContentType; switch (requestType) { case "POST": invocation = requestBuilder - .buildPost(Entity.entity(dataDispatchRequest.getBody(), dataDispatchRequest.getContentType())); + .buildPost(Entity.entity(dataDispatchRequest.getBody(), overriddenContentType)); break; case "PUT": invocation = requestBuilder - .buildPut(Entity.entity(dataDispatchRequest.getBody(), dataDispatchRequest.getContentType())); + .buildPut(Entity.entity(dataDispatchRequest.getBody(), overriddenContentType)); break; case "PATCH": invocation = requestBuilder - .build("PATCH", Entity.entity(dataDispatchRequest.getBody(), dataDispatchRequest.getContentType())); + .build("PATCH", Entity.entity(dataDispatchRequest.getBody(), overriddenContentType)); break; } } diff --git a/src/main/java/com/rohitawate/everest/requestmanager/RequestManager.java b/src/main/java/com/rohitawate/everest/requestmanager/RequestManager.java index 58a1202..2e9f84a 100644 --- a/src/main/java/com/rohitawate/everest/requestmanager/RequestManager.java +++ b/src/main/java/com/rohitawate/everest/requestmanager/RequestManager.java @@ -51,11 +51,14 @@ public abstract class RequestManager extends Service { .register(MultiPartFeature.class) .build(); + // Required for making PATCH requests through Jersey client.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true); + if (Settings.connectionTimeOutEnable) client.property(ClientProperties.CONNECT_TIMEOUT, Settings.connectionTimeOut); if (Settings.connectionReadTimeOutEnable) client.property(ClientProperties.READ_TIMEOUT, Settings.connectionReadTimeOut); + return client; } From 79861e0d7ec0fea5b1e6be07d7b687782f8cbbee Mon Sep 17 00:00:00 2001 From: Rohit Awate Date: Tue, 8 May 2018 22:35:40 +0530 Subject: [PATCH 09/15] Fixed #11: Application now scales correctly --- .../everest/controllers/DashboardController.java | 9 ++++----- src/main/java/com/rohitawate/everest/main/Main.java | 6 ++++++ src/main/resources/fxml/homewindow/Dashboard.fxml | 5 ++--- src/main/resources/fxml/homewindow/HomeWindow.fxml | 4 ++-- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/rohitawate/everest/controllers/DashboardController.java b/src/main/java/com/rohitawate/everest/controllers/DashboardController.java index 722bca1..941c252 100644 --- a/src/main/java/com/rohitawate/everest/controllers/DashboardController.java +++ b/src/main/java/com/rohitawate/everest/controllers/DashboardController.java @@ -155,7 +155,7 @@ public class DashboardController implements Initializable { }); errorTitle.setText("Oops... That's embarrassing!"); - errorDetails.setText("Something went wrong. Try to make another getRequest.\nRestart Everest if that doesn't work."); + errorDetails.setText("Something went wrong. Try to make another request.\nRestart Everest if that doesn't work."); visualizer = new Visualizer(); visualizerTab.setContent(visualizer); @@ -199,7 +199,6 @@ public class DashboardController implements Initializable { configureRequestManager(); requestManager.start(); break; - // DataDispatchRequestManager will generate appropriate getRequest based on the type. case "POST": case "PUT": case "PATCH": @@ -274,7 +273,7 @@ public class DashboardController implements Initializable { Services.loggingService.logSevere("Request execution failed.", E, LocalDateTime.now()); errorLayer.setVisible(true); errorTitle.setText("Oops... That's embarrassing!"); - errorDetails.setText("Something went wrong. Try to make another getRequest.\nRestart Everest if that doesn't work."); + errorDetails.setText("Something went wrong. Try to make another request.\nRestart Everest if that doesn't work."); } } @@ -290,7 +289,7 @@ public class DashboardController implements Initializable { promptLayer.setVisible(false); Throwable throwable = requestManager.getException(); Exception exception = (Exception) throwable; - Services.loggingService.logWarning(httpMethodBox.getValue() + " getRequest could not be processed.", exception, LocalDateTime.now()); + Services.loggingService.logWarning(httpMethodBox.getValue() + " request could not be processed.", exception, LocalDateTime.now()); if (throwable.getClass() == UnreliableResponseException.class) { UnreliableResponseException URE = (UnreliableResponseException) throwable; @@ -311,7 +310,7 @@ public class DashboardController implements Initializable { if (requestManager.getClass() == DataDispatchRequestManager.class) { if (throwable.getCause() != null && throwable.getCause().getClass() == IllegalArgumentException.class) { errorTitle.setText("Did you forget something?"); - errorDetails.setText("Please specify at least one body part for your " + httpMethodBox.getValue() + " getRequest."); + errorDetails.setText("Please specify at least one body part for your " + httpMethodBox.getValue() + " request."); } else if (throwable.getClass() == FileNotFoundException.class) { errorTitle.setText("File(s) not found:"); errorDetails.setText(throwable.getMessage()); diff --git a/src/main/java/com/rohitawate/everest/main/Main.java b/src/main/java/com/rohitawate/everest/main/Main.java index eaf7870..71976b8 100644 --- a/src/main/java/com/rohitawate/everest/main/Main.java +++ b/src/main/java/com/rohitawate/everest/main/Main.java @@ -21,9 +21,11 @@ import com.rohitawate.everest.util.settings.SettingsLoader; import com.rohitawate.everest.util.themes.ThemeManager; import javafx.application.Application; import javafx.fxml.FXMLLoader; +import javafx.geometry.Rectangle2D; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.image.Image; +import javafx.stage.Screen; import javafx.stage.Stage; public class Main extends Application { @@ -41,6 +43,10 @@ public class Main extends Application { Stage dashboardStage = new Stage(); ThemeManager.setTheme(homeWindow); + Rectangle2D screenBounds = Screen.getPrimary().getBounds(); + dashboardStage.setWidth(screenBounds.getWidth() * 0.83); + dashboardStage.setHeight(screenBounds.getHeight() * 0.74); + dashboardStage.getIcons().add(new Image(getClass().getResource("/assets/Logo.png").toExternalForm())); dashboardStage.setScene(new Scene(homeWindow)); dashboardStage.setTitle("Everest"); diff --git a/src/main/resources/fxml/homewindow/Dashboard.fxml b/src/main/resources/fxml/homewindow/Dashboard.fxml index 1d61d78..febed9f 100644 --- a/src/main/resources/fxml/homewindow/Dashboard.fxml +++ b/src/main/resources/fxml/homewindow/Dashboard.fxml @@ -22,9 +22,8 @@ - + diff --git a/src/main/resources/fxml/homewindow/HomeWindow.fxml b/src/main/resources/fxml/homewindow/HomeWindow.fxml index c160f68..63b6580 100644 --- a/src/main/resources/fxml/homewindow/HomeWindow.fxml +++ b/src/main/resources/fxml/homewindow/HomeWindow.fxml @@ -22,12 +22,12 @@ - - From c2f12d146edf41e325530f5fb0ec84586bc9fd02 Mon Sep 17 00:00:00 2001 From: Rohit Awate Date: Sat, 12 May 2018 12:19:15 +0530 Subject: [PATCH 10/15] Optimized and cleaned up the logging service --- .../rohitawate/everest/util/logging/Log.java | 40 ++--------- .../everest/util/logging/Logger.java | 47 ++++--------- .../everest/util/logging/LoggingService.java | 67 ++++++------------- src/main/resources/templates/LogEntry.html | 3 +- src/main/resources/templates/LogsFile.html | 3 - 5 files changed, 36 insertions(+), 124 deletions(-) diff --git a/src/main/java/com/rohitawate/everest/util/logging/Log.java b/src/main/java/com/rohitawate/everest/util/logging/Log.java index 397493c..8cb9032 100644 --- a/src/main/java/com/rohitawate/everest/util/logging/Log.java +++ b/src/main/java/com/rohitawate/everest/util/logging/Log.java @@ -17,40 +17,8 @@ package com.rohitawate.everest.util.logging; class Log { - private Level level; - private String message; - private String time; - private Exception exception; - - public Level getLevel() { - return level; - } - - public void setLevel(Level level) { - this.level = level; - } - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } - - public String getTime() { - return time; - } - - public void setTime(String time) { - this.time = time; - } - - public Exception getException() { - return exception; - } - - public void setException(Exception exception) { - this.exception = exception; - } + Level level; + String message; + String time; + Exception exception; } diff --git a/src/main/java/com/rohitawate/everest/util/logging/Logger.java b/src/main/java/com/rohitawate/everest/util/logging/Logger.java index cf678c3..ac21aef 100644 --- a/src/main/java/com/rohitawate/everest/util/logging/Logger.java +++ b/src/main/java/com/rohitawate/everest/util/logging/Logger.java @@ -39,15 +39,11 @@ class Logger { * @param log - The log to be written to file. */ synchronized void log(Log log) { - if (log.getLevel().greaterThanEqualTo(this.writerLevel)) { - try { - String logFileContents = readFile(logFilePath); - String logEntry = generateLogEntry(log); - logFileContents = logFileContents.replace("", logEntry); - BufferedWriter writer = new BufferedWriter(new FileWriter(logFilePath)); + System.out.println(log.message); + if (log.level.greaterThanEqualTo(this.writerLevel)) { + try (BufferedWriter writer = new BufferedWriter(new FileWriter(logFilePath, true))) { writer.flush(); - writer.write(logFileContents); - writer.close(); + writer.append(getLogEntry(log)); } catch (IOException e) { e.printStackTrace(); } @@ -63,19 +59,19 @@ class Logger { * Yellow = Warning * Green = Info */ - private String generateLogEntry(Log log) { + private String getLogEntry(Log log) { String logEntry = this.logEntryTemplate; - logEntry = logEntry.replace("%% LogLevel %%", log.getLevel().toString()); - logEntry = logEntry.replace("%% Time %%", log.getTime()); - logEntry = logEntry.replace("%% Message %%", log.getMessage()); + logEntry = logEntry.replace("%% LogLevel %%", log.level.toString()); + logEntry = logEntry.replace("%% Time %%", log.time); + logEntry = logEntry.replace("%% Message %%", log.message); StringBuilder builder = new StringBuilder(); - if (log.getException() != null) { - StackTraceElement[] stackTrace = log.getException().getStackTrace(); - builder.append(log.getException().toString()); + if (log.exception != null) { + StackTraceElement[] stackTrace = log.exception.getStackTrace(); + builder.append(log.exception.toString()); builder.append("
\n"); if (stackTrace.length != 0) { - for (StackTraceElement element : log.getException().getStackTrace()) { + for (StackTraceElement element : log.exception.getStackTrace()) { builder.append(" -- "); builder.append(element.toString()); builder.append("
\n"); @@ -129,23 +125,4 @@ class Logger { return builder.toString(); } - - private String readFile(String filePath) { - StringBuilder builder = new StringBuilder(); - try { - BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath)); - Scanner scanner = new Scanner(bufferedReader); - - while (scanner.hasNext()) { - builder.append(scanner.nextLine()); - builder.append("\n"); - } - scanner.close(); - bufferedReader.close(); - } catch (IOException e) { - e.printStackTrace(); - } - - return builder.toString(); - } } diff --git a/src/main/java/com/rohitawate/everest/util/logging/LoggingService.java b/src/main/java/com/rohitawate/everest/util/logging/LoggingService.java index 84f582c..b0d8c31 100644 --- a/src/main/java/com/rohitawate/everest/util/logging/LoggingService.java +++ b/src/main/java/com/rohitawate/everest/util/logging/LoggingService.java @@ -24,17 +24,12 @@ import java.time.format.DateTimeFormatter; public class LoggingService { private Logger logger; private DateTimeFormatter dateFormat; - private String message; - private Exception exception; - private LocalDateTime time; - - private SevereLogger severeLogger = new SevereLogger(); - private WarningLogger warningLogger = new WarningLogger(); - private InfoLogger infoLogger = new InfoLogger(); + private Log log; public LoggingService(Level writerLevel) { - logger = new Logger(writerLevel); - dateFormat = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss"); + this.log = new Log(); + this.logger = new Logger(writerLevel); + this.dateFormat = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss"); } public void logSevere(String message, Exception exception, LocalDateTime time) { @@ -53,47 +48,23 @@ public class LoggingService { } private void setValues(String message, Exception exception, LocalDateTime time) { - this.message = message; - this.exception = exception; - this.time = time; + this.log.message = message; + this.log.exception = exception; + this.log.time = dateFormat.format(time); } - private class SevereLogger implements Runnable { - @Override - public void run() { - System.out.println(message); - Log log = new Log(); - log.setLevel(Level.SEVERE); - log.setMessage(message); - log.setException(exception); - log.setTime(dateFormat.format(time)); - logger.log(log); - } - } + private Runnable severeLogger = () -> { + this.log.level = Level.SEVERE; + this.logger.log(this.log); + }; - private class WarningLogger implements Runnable { - @Override - public void run() { - System.out.println(message); - Log log = new Log(); - log.setLevel(Level.WARNING); - log.setMessage(message); - log.setException(exception); - log.setTime(dateFormat.format(time)); - logger.log(log); - } - } + private Runnable warningLogger = () -> { + this.log.level = Level.WARNING; + this.logger.log(log); + }; - - private class InfoLogger implements Runnable { - @Override - public void run() { - System.out.println(message); - Log log = new Log(); - log.setLevel(Level.INFO); - log.setMessage(message); - log.setTime(dateFormat.format(time)); - logger.log(log); - } - } + private Runnable infoLogger = () -> { + this.log.level = Level.INFO; + this.logger.log(log); + }; } diff --git a/src/main/resources/templates/LogEntry.html b/src/main/resources/templates/LogEntry.html index 257d07f..579419f 100644 --- a/src/main/resources/templates/LogEntry.html +++ b/src/main/resources/templates/LogEntry.html @@ -2,5 +2,4 @@ %% LogLevel %% %% Time %%
%% Message %%
%% StackTrace %% -

- \ No newline at end of file +

\ No newline at end of file diff --git a/src/main/resources/templates/LogsFile.html b/src/main/resources/templates/LogsFile.html index 5cf28b2..bc47a82 100644 --- a/src/main/resources/templates/LogsFile.html +++ b/src/main/resources/templates/LogsFile.html @@ -55,6 +55,3 @@

Everest Log: %% Date %%

- - - \ No newline at end of file From c90a22cc7be9ad7028d64446ee1f2a09234c7efe Mon Sep 17 00:00:00 2001 From: Rohit Awate Date: Sun, 13 May 2018 21:48:38 +0530 Subject: [PATCH 11/15] Improved recycling of HashMaps --- .../controllers/BodyTabController.java | 22 +++++----- .../controllers/DashboardController.java | 15 ++++--- .../controllers/FormDataTabController.java | 41 +++++++++++-------- .../controllers/HeaderTabController.java | 11 ++++- .../everest/controllers/URLTabController.java | 10 +++-- .../everest/controllers/Visualizer.java | 5 ++- .../everest/util/history/HistoryManager.java | 4 +- .../everest/util/settings/Settings.java | 2 +- .../everest/util/settings/SettingsLoader.java | 4 +- .../everest/util/themes/ThemeManager.java | 3 +- 10 files changed, 67 insertions(+), 50 deletions(-) diff --git a/src/main/java/com/rohitawate/everest/controllers/BodyTabController.java b/src/main/java/com/rohitawate/everest/controllers/BodyTabController.java index 2ea9289..21cb390 100644 --- a/src/main/java/com/rohitawate/everest/controllers/BodyTabController.java +++ b/src/main/java/com/rohitawate/everest/controllers/BodyTabController.java @@ -135,24 +135,16 @@ public class BodyTabController implements Initializable { try { switch (dashboardState.getContentType()) { case MediaType.TEXT_PLAIN: - rawInputArea.setText(dashboardState.getBody()); - rawInputTypeBox.getSelectionModel().select("PLAIN TEXT"); - bodyTabPane.getSelectionModel().select(rawTab); + setRawTab(dashboardState, "PLAIN TEXT"); break; case MediaType.APPLICATION_JSON: - rawInputArea.setText(dashboardState.getBody()); - rawInputTypeBox.getSelectionModel().select("JSON"); - bodyTabPane.getSelectionModel().select(rawTab); + setRawTab(dashboardState, "JSON"); break; case MediaType.APPLICATION_XML: - rawInputArea.setText(dashboardState.getBody()); - rawInputTypeBox.getSelectionModel().select("XML"); - bodyTabPane.getSelectionModel().select(rawTab); + setRawTab(dashboardState, "XML"); break; case MediaType.TEXT_HTML: - rawInputArea.setText(dashboardState.getBody()); - rawInputTypeBox.getSelectionModel().select("HTML"); - bodyTabPane.getSelectionModel().select(rawTab); + setRawTab(dashboardState, "HTML"); break; case MediaType.MULTIPART_FORM_DATA: // For file tuples @@ -178,4 +170,10 @@ public class BodyTabController implements Initializable { Services.loggingService.logInfo("Dashboard loaded with blank request body.", LocalDateTime.now()); } } + + private void setRawTab(DashboardState dashboardState, String contentType) { + rawInputArea.setText(dashboardState.getBody()); + rawInputTypeBox.getSelectionModel().select(contentType); + bodyTabPane.getSelectionModel().select(rawTab); + } } diff --git a/src/main/java/com/rohitawate/everest/controllers/DashboardController.java b/src/main/java/com/rohitawate/everest/controllers/DashboardController.java index 941c252..946caaf 100644 --- a/src/main/java/com/rohitawate/everest/controllers/DashboardController.java +++ b/src/main/java/com/rohitawate/everest/controllers/DashboardController.java @@ -98,6 +98,7 @@ public class DashboardController implements Initializable { private GETRequest getRequest; private DataDispatchRequest dataRequest; private DELETERequest deleteRequest; + private HashMap params; @Override public void initialize(URL url, ResourceBundle rb) { @@ -190,7 +191,7 @@ public class DashboardController implements Initializable { getRequest = new GETRequest(); getRequest.setTarget(addressField.getText()); - getRequest.setHeaders(headerTabController.getHeaders()); + getRequest.setHeaders(headerTabController.getSelectedHeaders()); requestManager = Services.pool.get(); requestManager.setRequest(getRequest); @@ -207,7 +208,7 @@ public class DashboardController implements Initializable { dataRequest.setRequestType(httpMethodBox.getValue()); dataRequest.setTarget(addressField.getText()); - dataRequest.setHeaders(headerTabController.getHeaders()); + dataRequest.setHeaders(headerTabController.getSelectedHeaders()); if (bodyTabController.rawTab.isSelected()) { String contentType; @@ -253,7 +254,7 @@ public class DashboardController implements Initializable { deleteRequest = new DELETERequest(); deleteRequest.setTarget(addressField.getText()); - deleteRequest.setHeaders(headerTabController.getHeaders()); + deleteRequest.setHeaders(headerTabController.getSelectedHeaders()); requestManager = Services.pool.delete(); requestManager.setRequest(deleteRequest); @@ -430,8 +431,10 @@ public class DashboardController implements Initializable { } private HashMap getParams() { - HashMap params = new HashMap<>(); + if (params == null) + params = new HashMap<>(); + params.clear(); for (StringKeyValueFieldController controller : paramsControllers) if (controller.isChecked()) params.put(controller.getHeader().getKey(), controller.getHeader().getValue()); @@ -500,7 +503,7 @@ public class DashboardController implements Initializable { case "PUT": case "PATCH": dashboardState = new DashboardState(bodyTabController.getBasicRequest(httpMethodBox.getValue())); - dashboardState.setHeaders(headerTabController.getHeaders()); + dashboardState.setHeaders(headerTabController.getSelectedHeaders()); break; default: // For GET, DELETE requests @@ -513,7 +516,7 @@ public class DashboardController implements Initializable { Services.loggingService.logInfo("Dashboard state was saved with an invalid URL.", LocalDateTime.now()); } dashboardState.setHttpMethod(httpMethodBox.getValue()); - dashboardState.setHeaders(headerTabController.getHeaders()); + dashboardState.setHeaders(headerTabController.getSelectedHeaders()); dashboardState.setParams(getParams()); return dashboardState; diff --git a/src/main/java/com/rohitawate/everest/controllers/FormDataTabController.java b/src/main/java/com/rohitawate/everest/controllers/FormDataTabController.java index 4c77787..0935258 100644 --- a/src/main/java/com/rohitawate/everest/controllers/FormDataTabController.java +++ b/src/main/java/com/rohitawate/everest/controllers/FormDataTabController.java @@ -44,6 +44,9 @@ public class FormDataTabController implements Initializable { private List fileControllers; private IntegerProperty fileControllersCount, stringControllersCount; + private HashMap stringMap; + private HashMap fileMap; + @Override public void initialize(URL location, ResourceBundle resources) { stringControllers = new ArrayList<>(); @@ -56,24 +59,22 @@ public class FormDataTabController implements Initializable { addStringField(); } - private void addFileField() { - addFileField("", "", null); - } @FXML private void addFileField(ActionEvent event) { addFileField("", "", event); } + private void addFileField() { + addFileField("", "", null); + } + public void addFileField(String key, String value) { addFileField(key, value, null); } private void addFileField(String key, String value, ActionEvent event) { - /* - Re-uses previous field if it is empty, - else loads a new one. - */ + //Re-uses previous field if it is empty else loads a new one. if (fileControllers.size() > 0 && event == null) { FileKeyValueFieldController previousController = fileControllers.get(fileControllers.size() - 1); @@ -106,15 +107,15 @@ public class FormDataTabController implements Initializable { } } - private void addStringField() { - addStringField("", "", null); - } - @FXML private void addStringField(ActionEvent event) { addStringField("", "", event); } + private void addStringField() { + addStringField("", "", null); + } + public void addStringField(String key, String value) { addStringField(key, value, null); } @@ -156,20 +157,26 @@ public class FormDataTabController implements Initializable { } public HashMap getStringTuples() { - HashMap tuples = new HashMap<>(); + if (stringMap == null) + stringMap = new HashMap<>(); + + stringMap.clear(); for (StringKeyValueFieldController controller : stringControllers) { if (controller.isChecked()) - tuples.put(controller.getHeader().getKey(), controller.getHeader().getValue()); + stringMap.put(controller.getHeader().getKey(), controller.getHeader().getValue()); } - return tuples; + return stringMap; } public HashMap getFileTuples() { - HashMap tuples = new HashMap<>(); + if (fileMap == null) + fileMap = new HashMap<>(); + + fileMap.clear(); for (FileKeyValueFieldController controller : fileControllers) { if (controller.isChecked()) - tuples.put(controller.getHeader().getKey(), controller.getHeader().getValue()); + fileMap.put(controller.getHeader().getKey(), controller.getHeader().getValue()); } - return tuples; + return fileMap; } } diff --git a/src/main/java/com/rohitawate/everest/controllers/HeaderTabController.java b/src/main/java/com/rohitawate/everest/controllers/HeaderTabController.java index 2722c1e..cbe6f6f 100644 --- a/src/main/java/com/rohitawate/everest/controllers/HeaderTabController.java +++ b/src/main/java/com/rohitawate/everest/controllers/HeaderTabController.java @@ -43,6 +43,8 @@ public class HeaderTabController implements Initializable { private List controllers; private IntegerProperty controllersCount; + private HashMap headers; + @Override public void initialize(URL location, ResourceBundle resources) { controllers = new ArrayList<>(); @@ -100,9 +102,14 @@ public class HeaderTabController implements Initializable { } } + /** + * Returns a map of the selected headers. + */ + public HashMap getSelectedHeaders() { + if (headers == null) + headers = new HashMap<>(); - public HashMap getHeaders() { - HashMap headers = new HashMap<>(); + headers.clear(); for (StringKeyValueFieldController controller : controllers) { if (controller.isChecked()) headers.put(controller.getHeader().getKey(), controller.getHeader().getValue()); diff --git a/src/main/java/com/rohitawate/everest/controllers/URLTabController.java b/src/main/java/com/rohitawate/everest/controllers/URLTabController.java index cb52339..69449e1 100644 --- a/src/main/java/com/rohitawate/everest/controllers/URLTabController.java +++ b/src/main/java/com/rohitawate/everest/controllers/URLTabController.java @@ -42,6 +42,7 @@ public class URLTabController implements Initializable { private List controllers; private IntegerProperty controllersCount; + private HashMap tuples; @Override public void initialize(URL location, ResourceBundle resources) { @@ -101,11 +102,14 @@ public class URLTabController implements Initializable { } public HashMap getStringTuples() { - HashMap headers = new HashMap<>(); + if (tuples == null) + tuples = new HashMap<>(); + + tuples.clear(); for (StringKeyValueFieldController controller : controllers) { if (controller.isChecked()) - headers.put(controller.getHeader().getKey(), controller.getHeader().getValue()); + tuples.put(controller.getHeader().getKey(), controller.getHeader().getValue()); } - return headers; + return tuples; } } diff --git a/src/main/java/com/rohitawate/everest/controllers/Visualizer.java b/src/main/java/com/rohitawate/everest/controllers/Visualizer.java index b31215a..3b55f79 100644 --- a/src/main/java/com/rohitawate/everest/controllers/Visualizer.java +++ b/src/main/java/com/rohitawate/everest/controllers/Visualizer.java @@ -58,12 +58,13 @@ class Visualizer extends ScrollPane { if (root.isArray()) { Iterator iterator = root.elements(); + int i = 0; while (iterator.hasNext()) { currentNode = iterator.next(); if (currentNode.isValueNode()) { - valueLabel = new Label(currentNode.toString()); + valueLabel = new Label(i++ + ": " + currentNode.toString()); valueLabel.getStyleClass().addAll("visualizerValueLabel", "visualizerLabel"); valueLabel.setWrapText(true); valueTooltip = new Tooltip(currentNode.toString()); @@ -74,7 +75,7 @@ class Visualizer extends ScrollPane { } else if (currentNode.isObject()) { TreeItem newRoot = new TreeItem<>(); items.add(newRoot); - populate(newRoot, "[Anonymous Object]", currentNode); + populate(newRoot, i++ + ": [Anonymous Object]", currentNode); } } } else { diff --git a/src/main/java/com/rohitawate/everest/util/history/HistoryManager.java b/src/main/java/com/rohitawate/everest/util/history/HistoryManager.java index 3512860..bb7d4f0 100644 --- a/src/main/java/com/rohitawate/everest/util/history/HistoryManager.java +++ b/src/main/java/com/rohitawate/everest/util/history/HistoryManager.java @@ -61,8 +61,8 @@ public class HistoryManager { /** * Creates and initializes the database with necessary tables if not already done. * - * @throws IOException - * @throws SQLException + * @throws IOException - If unable to establish a connection to the database. + * @throws SQLException - If invalid statement is executed on the database. */ private void initDatabase() throws IOException, SQLException { // Read all queries from Queries.json diff --git a/src/main/java/com/rohitawate/everest/util/settings/Settings.java b/src/main/java/com/rohitawate/everest/util/settings/Settings.java index c310279..bb01168 100644 --- a/src/main/java/com/rohitawate/everest/util/settings/Settings.java +++ b/src/main/java/com/rohitawate/everest/util/settings/Settings.java @@ -31,5 +31,5 @@ public class Settings { public static int connectionReadTimeOut = 30000; public static String theme = "Adreana"; - public static int showHistoryRange = 3; + public static int showHistoryRange = 7; } diff --git a/src/main/java/com/rohitawate/everest/util/settings/SettingsLoader.java b/src/main/java/com/rohitawate/everest/util/settings/SettingsLoader.java index 94b50d3..c1465fa 100644 --- a/src/main/java/com/rohitawate/everest/util/settings/SettingsLoader.java +++ b/src/main/java/com/rohitawate/everest/util/settings/SettingsLoader.java @@ -17,7 +17,6 @@ package com.rohitawate.everest.util.settings; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import com.rohitawate.everest.util.EverestUtilities; import com.rohitawate.everest.util.Services; @@ -44,8 +43,7 @@ public class SettingsLoader implements Runnable { System.out.println("Settings file found. Loading settings... "); - ObjectMapper mapper = new ObjectMapper(); - nodes = mapper.readTree(settingsFile); + nodes = EverestUtilities.mapper.readTree(settingsFile); Settings.responseAreaFont = setStringSetting(Settings.responseAreaFont, "responseAreaFont"); Settings.responseAreaFontSize = setIntegerSetting(Settings.responseAreaFontSize, "responseAreaFontSize"); diff --git a/src/main/java/com/rohitawate/everest/util/themes/ThemeManager.java b/src/main/java/com/rohitawate/everest/util/themes/ThemeManager.java index 7a6a0d2..921c26b 100644 --- a/src/main/java/com/rohitawate/everest/util/themes/ThemeManager.java +++ b/src/main/java/com/rohitawate/everest/util/themes/ThemeManager.java @@ -27,6 +27,7 @@ import java.util.List; public class ThemeManager { private static List parentNodes = new ArrayList<>(); + private static File themeFile = new File("Everest/themes/" + Settings.theme + ".css"); /** * Refreshes the theme of all the registered parents by replacing @@ -35,7 +36,6 @@ public class ThemeManager { */ public static void refreshTheme() { if (!Settings.theme.equals("Adreana")) { - File themeFile = new File("Everest/themes/" + Settings.theme + ".css"); if (themeFile.exists()) { String themePath = themeFile.toURI().toString(); @@ -53,7 +53,6 @@ public class ThemeManager { public static void setTheme(Parent parent) { if (!Settings.theme.equals("Adreana")) { - File themeFile = new File("Everest/themes/" + Settings.theme + ".css"); if (themeFile.exists()) { parent.getStylesheets().add(themeFile.toURI().toString()); parentNodes.add(parent); From d837cd50005f16984cb3eba84c85067e71dd4849 Mon Sep 17 00:00:00 2001 From: Rohit Awate Date: Thu, 24 May 2018 19:24:24 +0530 Subject: [PATCH 12/15] Shifted LoadingLayer GIF to JFXProgressBar --- .../controllers/DashboardController.java | 4 + .../assets/LoadingCircle_WhiteOnOrange.gif | Bin 71698 -> 0 bytes .../resources/fxml/homewindow/Dashboard.fxml | 102 +++++++++--------- 3 files changed, 53 insertions(+), 53 deletions(-) delete mode 100644 src/main/resources/assets/LoadingCircle_WhiteOnOrange.gif diff --git a/src/main/java/com/rohitawate/everest/controllers/DashboardController.java b/src/main/java/com/rohitawate/everest/controllers/DashboardController.java index 946caaf..c7055fc 100644 --- a/src/main/java/com/rohitawate/everest/controllers/DashboardController.java +++ b/src/main/java/com/rohitawate/everest/controllers/DashboardController.java @@ -17,6 +17,7 @@ package com.rohitawate.everest.controllers; import com.fasterxml.jackson.databind.JsonNode; import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXProgressBar; import com.jfoenix.controls.JFXSnackbar; import com.rohitawate.everest.exceptions.RedirectException; import com.rohitawate.everest.exceptions.UnreliableResponseException; @@ -83,6 +84,8 @@ public class DashboardController implements Initializable { Tab paramsTab, authTab, headersTab, bodyTab; @FXML private Tab visualizerTab, responseHeadersTab; + @FXML + private JFXProgressBar progressBar; private JFXSnackbar snackbar; private final String[] httpMethods = {"GET", "POST", "PUT", "DELETE", "PATCH"}; @@ -279,6 +282,7 @@ public class DashboardController implements Initializable { } private void configureRequestManager() { + progressBar.progressProperty().bind(requestManager.progressProperty()); requestManager.setOnRunning(e -> whileRunning()); requestManager.setOnSucceeded(e -> onSucceeded()); requestManager.setOnCancelled(e -> onCancelled()); diff --git a/src/main/resources/assets/LoadingCircle_WhiteOnOrange.gif b/src/main/resources/assets/LoadingCircle_WhiteOnOrange.gif deleted file mode 100644 index af7cf6ef6022d90027bb51ae905ec1479d754800..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 71698 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 Q@<)u;1&Ue+W@0>l(j! zpHH=@pGKpR(T@Z=5{gbL3{Mj1 zzbwH1(S3CC&QA*Za^gWkuEFm7rb7Mp$N!wF^f!}Iogb-WY0hvFq&G{w&5jO-+_WGg zgtqgMTDD#NQUv#WULS7tCT0-o;7hstZa9P7?OdW<4ZeEmPvUXeXY0Fj9^YPmzoFM) z$8%kDbGJ~p`vHGcHAx_bQBtU4)WCsCCf;!NcwZ+NbH|APm1)oh3%`v;OqK7;Ae+1M z7ITGI-1fW#d}ialO7kVp1YUvYTq_RSGS*1v>^72AqaMXj-F6!A985y}MpnqYyUNqA z#7uf0)V9AQn)sZyo8Spo7#2j<_IxC43V7wir7?Fp+A>WS+@M*Fr`PnAq0jcW<|u(S z{RAC8G@?F;>f3cMxKPI%aAgx;%Az`S*&ZhcxHc(QeM z7dymHYjD8yBPl=*nzTzbr$TH+5E#ZRxEsT)3Tv44<4jnJW6neLQ#;YOZj#uY_yNjUT%T!B2Lq^?+NEW_f1XMih!L*idCPz-{fI zHi@=7C3s}^IE7pQOGMrlo&BL#UNOK@voQiCo<#;DTT@+VVG)>WwLF;FTHw=@RSs|( z%&BgOg&KHr3^3&~2^y(q+G9zMy@bICmceCygIU1lRz*gg%pL^PvkSaLU^tfGEL+*C z#h>F~nK-pD2P7Lf{V|(c+rQ&-6@TAs&virBWRCrXQJ_EV-=Q;G$g?DT{XDpow+KplLh2}c!d#|_DND-$;&G4nufTIu*}8mYs=*Nz<~QR|_so-0?H&EZWCL#XL!1qx zV==vb6B83*O8daFD7!Bym1W^2=5OP81y)%Ef`}&!xbJ63y6)R;tBeqck2}ALn)_-{ z@LWEx!X)C&$Y=C7Jf&dh{?*=9QsH3peTo9rj!x+Px|St@_tgFeiec3e`JFt?`=Bo_ zsX5WFS;74)m_rl(SY*u1isfWxd=Aw+@{irMQt{47QazY~n=pN> zySju{l7v2^cmHrF6?pX$9FGp3Z_B$&anZm57tayk!(Ea5r4Z_NF*ca~dhxV^Y>4pK z7dH|D8q4>60ZdaA+%rA z%V0j3(RJ6zPJMi(I7tO7{l(%m+2d6UkuahLk44JVNAq`@DQIcEzR2oI=Y)vL5*5;C zEv3=(`1*jZJq#DrQoZiT;U%j=e-lE2NwZQ%7q5Ii+~QM;OuYoXaz;I7u6+6v8sBh! zD~V{j0ypxCqSqP4p;28n-GnbHl8pG~V)utdZgWCWm_%wLJ_^Gj1Y014OY(N8?thbXa8 zL>G-6zYrqIFDLoH=Esj9~&A-8dSzQN2aH7bu5WlFX0I; zZ)W-eE$HQv2T^52(5NY)AwEqP(p6fazxUx+gQJz*mZ* z_L?Fg9ixGPodX+vc&KV1f9%YD4ky5eh?5c#&H^}$@063>sYvc>$$m-V7$m&1* z`OoprzvYwvUqVx9?r53C`_=@>vSSa@@_1X>tzO>82V|s0CNcOmA|-u{uT8GZB;Up7 zG&>l|ydoDy^b+wptUk3pY{8-vjBYdEk!sw@es#~nD8>Wq&e{`xX}M68rY%v;zeyH5 zHWH7K*s*L$g(f_bH3#6RJkN4lj6w9e8R#3L3T89Ef=f!k?J=H`?alx z_6E}U4McbXIFxbxXz;1Xm2RRj&;)SI8?=V0g}xy2KN`Stg$7?-hn0|3f6oWik*SBK&8Cwj3BG!P&NF$1M zu7VP#*YaQ{fjN+nBGm|ZlN8SUwOlJ00$`*}XWF_%?s;g^A48rA@HHCqhk&yv5R5HG z8bPMKHz|^7WA+%-t}*%`fO)ar%PLRNe2jsmt&Q`lwY8U)fWxuk^QtZLWd@)uv@gu6 z-Se3=MZ|^6_!z`JAldSlJ#$Rn*2k5G3j?G{YupBR1#BNZY9U)h*P4#NwaD^(%q?4$;9P(ZzEuq z41yB#n5o4Q45tnkDje_Hu|IXIzf>$6Q-3A9hf<06+VNSE&GQ2@g*U1*G+LAML$y4%ct*mb zO#a_=!ybgM2J`n`mOMXw;Vl%s>kb+5<*&6kHDM+ew;>xOvl^e?EI!#)TF&^{kTqF*In1Px6`A1&ZbI?rG9KcQR8M=EFQO(xgd-n>M?wtbjymZh6$2mSTK3Cwm%)b~5i~vZ*TJ%=>SpM%$l;9CQb#BiD!b^^ z-BuQU{f-daX{=WH8+3t%(n1+HG&F}V+<4@4+cV;EB0U+>#U!3|I+($eqz&J5Auyt! z-1bOrQDZQ^v4`skDrO6g7RGA!y+QOq>uM|;oP^X5*Alde<*e+E@0TyL5Hgj{F(rsq zj5K^hW}%VYb3LxovWjS()r1w#;|$FsXw-`@GDf zed#QNv?7jrh%Swb>*i%Z6*vvi;YV`GufNX18dOrE0z?g@C9f+3Y`b5&=` z^@^GQ=se#}MOR>+&0mj{Y4H60?rvm<%9~Kb(Ic|NQ+~EIli2FV0c8Xp{T|;q_ zEq|78J1LU>iD7@8a{JC>Cj(^vM2*SdMt&=&n7*vtP9+ zq?fy6W3Kzt;!igFsX`s^Y8-EAAm{FXvf0r^?)%2spYLb<25J8c%r=kN?DyKDe+x4G ziy!j8hOEq#C{)~{mN2EpY_=3CMF`63V$sN}V5|A>Z~OU97_JVnPc}dJCO^SUSoDrR zPB`pSJC4m_R|xPu*HgYty?!om*+kA2yVc<$@lmf~G@cKAufVJ+tFN|JrWll7GX|*+ zW6lE3b2jA?I{}A#RVRt|#~>e9JIW5dt6;_tyUFTnycPBDt?HV!I^BrKySJJBUR_Bd zD_)sld(S(xXI#nCb70~TQ`#j89_W?De&>~zo!)1c`RFK=_hfc@3Gq}GDU>em*(FOe z5e`fFVCkM0y85TX5vB^8gtqt+@<3J1$`7nQY!cZ6;_E77dyz)K75z)b>RWE&D-tpI z^tozZrM-=T?b2oD>a?6Y8xm8}N~~&lkL=AQc4y|9)ll~B3V>ev_McVJ_I;o)drK}4 zzB0ddHM!fIl4TPpa@{1V_fY8SRGqPmKtd^vs5;4jkzqn^&$UNi>4#S*Y)ktujrJQN z7`E5{EZy7Fo@!mh;S3y{}XS*fFFtz&(} z#*szaDyLJgEFHj7UZCnu&}0j=!nvo^+OXyVooRV@z;72%w=~PuQ`yJ!xoSQ94YTw&W4!TeW}o3Uy4!R1$E5Es;< zCXM?fcITR25T9J)V+Yj6a|ju_>3!;PzuIWA+DyMYSh|)oUx);j*BGR7NM#Pt#gL^& z?&q8?UNRRVljP$7+_ljo#Nw)#72p6C-VNY*fF7pUDoYfm_K@`gZhN$v*7HOwKMZ@&Qv_$eONC+m8?|m=A!tZ+Yi6^ zs{I30A>vjl3o+3!lBFSy(@l}Lo2f`SYQO=5 z%4*prBMm~Q+_NJW&RCbKO{piNdQ-jv*AT%Xuq;&tlZRKzW;rYgGUUdx0MYRvs&huh z66{^&%#=CWp4Y+dz3%)^LbEHs)G6VGy(>=i!qtghR{t<1mwcF#^9o!;nz$<|J9a*Q z0Lw>k-!E0EOYWsF_glh|7wD0Ptp(nmCc0QX?k$Jsv7i*5tl@$qJy1o0AkljA4;G%Z z5WAxMK$^cg-2ITFI!qs&`?~{iXR3}Ek0xCI+9v*2SoJ4BgFC6V%*y?|?47>;j#WR*=O&RCnRefSG<4ot<8g4n z>rPh@?pA;8@MbLnlkVnJ-TLYkI~8hN?RqzYTNgj?Y-lw=gKw9b@T%yi+A&&-U70WH zDoYg~<+xhhnAL|^D4cg}U|a1@(f^p{LR@Mxe*1#&2UA|2^;lP*$w+4xeXF^$3*5oG zBz&vQy{d|3*NCm{B1S6?Gc~qIUw;`b1x1Nh=zrO*D;>%ccWn6T>@bPaA01rimA@Z6 z{E7HEZ3Wtt)}(`YGBAP!9l6=b;s7PRHbbA_LEry`+y1bV zHa4+P6wHs-(_zw|E!IcdgWFHu-SlI3w2p+|Ifg4i_ubN_pH8aD%t{DW4^Bg=3LGXq zAG`HUIX5co$~=|EM$n2&B`&0xaR(6fhA!ywj^bI=2+v4P3laoI#kh9UX~;KjPC7ac zRM5L|=59>HC-t9eObSL^3JVwmlq+fa;1h&}&*YXWYqC&nXE-hZM)0xiu?H7G0Yc(r z>L&WFM0U)`t%8_C1J{j-40;SkvJ~eH$T*xKA}e0QJ)wCV!Bl}sYVF={L6!{|<}=X` z@?8Lcv{Wt`t9Gg=VMtbQZ(mKYK~yqIRx*m`=yqFO`XHeczW~Wk$fC)yEiO&Zad;gR z&T3QJuE=V+5Q_`2G}5n?8sKD zExJ0UT20X~tXh*qdWAHfZY_JPv{E6Pq>akfnYj6yKf$_0sgHfG?sXgk#M>T}L00?3 zgiUjEqWhKXphuJV-0B3X;w27By|xnr%Vawxy)(H?!+rFQ0nx)PFjMG=cL58xH$%-; z<%S}ucCJGZ-Q?{x14-4vVxZs~%=nzKP&H^f428b1o1x->$%1jxi!26$afi-r-aY`H8EHFr56W*NT8q~npu%xo;E%C9Th2SS-HRe?Jv2ONtGZ!+skj=yp=+?gyb z-fi($csBZup`&_C`>FT>bh8VXsN8B$5~XW?*BLPZscr&nOsz4iVPF5CvPFg#)L=*G z-*S-XjgL}`_M8cvIMwAQXxuUT0)aK9csKf{T7$^*whA)EgU^W=)u=Cei6y^KD87y! zH*h&r(xO~9aTE4wv0&tKc{j=BKz>78&wIWWh!whkx9_;(%IylXid2q3Ph4;jLA_C! zFNDzBvY@>^I(kL2nUokC%QK(%rsi#l_%9nGK9)JH`sX}2;uvlSNTgBEKC^la64eWq zsGT7trD;m1umTD1zBd)C$EC?A2sSTr_ohi}v`*~jhm{+yoL z4P*(HG*3ckUpU2tYgQZJ_2Oa>%I`KH9E#lFTu`b#dkQm-x%eG~2tBVh{IO_Ql+vLq z{foD|p&XvDr`hd(_pTTFfrbP_uOn*k2J)p}7#TAezRuKB#KIIEI)6n1?kaXk_5uxa z-!)gb2OEisbRM%Dj6n+ER*Mla0^dx>4s5);0+T{|okR!2TOT03PH*L@qLmQ(kGzdi ziV1z-5}uEwi>Uh2KwS5syjgOlj1;}7C_!?zvV5aGeK}^bSPw+osH>LKK+Je7N)+^9 zmd}9-6gGT@>P+<}8v|Qv3VEr%UR0fYaoliQ_++xNa+{p{gTCU-+M$&wbZm5>7-zmK zd_|kcN%<0naH4yE&rP9LX&($~+|Z6y?W=|O{0vmMr8mU5U|3P*sA7b*3@i{@lmag% zAG_kezTic*jU9D24}TgfBx{P+F#cjWl)%J~b&6EcAJ3j+Om1Uw#d{qBCMJ?jDg1%#%L zJy6HROMd}EC(*lqJm7kkf=l0I6HnW*FS9u$M?9~iMM+c*5i0W(mn6+F#81q!!jNO$W zzuH^c^;Y=&rUyG4(_+vl*-{g*#e8ZUul32xyq zd>6X3ZTha#8dR{RT0AqwvvuQEClQNPEQ1s04$DqOJ2gCWzti-qp6ObH>!G~?z<}4G zTf8yszUJM?N-_-fKLKC_!DVf(HuiY>v0@Er0?yaMdn~?sLUbf) z=fBJZF$0@4m;orh*zUpukXaxEG>d&%Rz%#)ruDzI-3<#xIVE>#NNBb0A!`3DemEK`&j#oBpid`-(iK6Asc)UkO1)YH#4G5Yt zov;KKW6YHp%(+y_v;RC052lMjf@5t;3TGe32?X->ayu!dNF7j7EH-#ET+4s zKcAat zOc0bV4k-0?z%GV0a8O~2H&gn6eG*h;0livu6qpk|Oe#451?M>HWuO+!d?v!f&2E;e zY~7~EE_^K?v&V@rnyQ_!?L(mAOT`(x zy4}=Ry(1syVQo2O$7AWt;OL^-HL%8}ev1L)Zl2q9@4-!*tE!o#Ap8sX1!my)o?A8q z8c$^h?M*vW`p})pi0_VXstmOcOjhXGsEtd$Xss&~0Lb5uR22}Uq|ksfN44f7BRUqWmrMamlScxA;i z;!$!0YwXOsr`O^@PLqm`_EBZo*t2hz2Kh~blG2=wil5W*n=ldS{b*Qa?KI3?mx~FXlP#DI7gDv*r8sIw*8`dE^hj@N7Yc45 zE_y9iAlWGj|I0KQz0^agy+RJ*{#jkWd|qvZ)!FJeT^S#h;q$n>byqIeq6Uz(`$c`I z4rzpe7uBZ}v)#=t_N`IDZHpZ$ zxL;Ba)4}Y!EVYd4o;(I)9rR%nkJT44_x0pdr@AQZ#u_EG1VaP>phiNUSr8(Aw@c(O z^)l+?CUM2tqCT~vG}OjZ3%Zi9fhhzxKhYN*Zc^Um<%rbEkW1J>1>GB__A!+8&3n0*Ms5aV`t}_1@OII%OzhNDhMrM>o1avLL4T(0NP=&K^>m?8{co9&flXqb;Q8 z!kGgN-8-@ISDQC8?!~r|Shn`kTXHh4A${T-+V9$FERpPVSxQp4B@J^+u!bm8PLRg? zg~lBEg@k3VXPEe04D#7^RtU2+OvNrkgc_J-MQioFSW3D=?#z?yP`exQ9raUNfLf%Y+65Dj4(L0O7Aut)+kMdbXB${5EGb=WEsB~ z!4@qu*L-E4LM9XD^K!Dz=KLj%5uJ98`VgF3ij{W3)6LN<()UK{u8p-PbDNWm>{>y( z{KR+eYBy~f_GLZgF#KA%o!wVp8d+*eZ2uuffofxZZGWeY%-QSuEwlLan}e;7IL(6! z#gi9XGv#i9=Q!5=83jma z`{L8dHu$kGHEB{kd`pOfv%evh8pt!R9LE6ogf4geUNt_tvrwdsx+BaYRQDlWWE48| zQINc7Xv}$pb_CPbxk(EtD~w)fx2RBhG^HqbJ_hp41~oF2wooq)Kua)AX5Hh6O)w-1 z%}k`-qKLP8hSDnQ>2YYdC}GZMKLBI0Mb8Ke$>>|Ou#^X-Sh+G}A>u6dJbg2hoIDuJ z!y|7(;7`|5mVt+vhZ4)##7`Y#Zx%<^X9g9``(eeDR4ryphxBNxE#w+RXF;%prnG)Tufp8 zc4HNho-_Fy{9y#k+czWnmud)n{bNed#rxb{u1ebEnPSOtd6|wXGKaoNCe5}KK}oV( zZ@S@0GEz-*f7VR@N@mVusGr|^ptf}bG>FEy3X^g+hAb(Q|Z(3#GRPAalMkY+}th6 z7N+N9TOsM4J!2ou7oM3e7j5nvzc;@2)dfGS%v0aE!g0(HWdXXb*XZ&_ki1PF`%RsL zG`F*}Qm+Wd-Fnk&jGKj0--7B8V>UOe$WdD`uES%QuF^XQTlJwOP%*GkKBhyvB6aQ~ z$Ae}z3%bK^*-{^Rpn~`_G~Qx;m_eZEFpBDV8Sn-))l508*U{8VbjcM}@blex`5Wxp zVIZe)&aA}MN8%1kTDq)o9Bv_fZ!Qt4sTVUMrJ@&khp;Z<*~-=zYRS;+1azW17dCW7 z`vO!Bq*FYwYXOXN0+Zc3GM(^vN^CvB$$~_rqD0J#buoBuY34DeZ@Iv78vinw#vtFG zP-aG#=OIV>V_ZT~nfz`#wJ+L{T)eoTZGa~g!i{*cK@#=_n_nCT#AB{YQe{jJZUu_4 z17$DD@|iPQF^6$JZMujUZi>kt9>Ja2Vm-qiFo1 z`T(asnb|Qv?)#vgiiZH6FU$zg98~&!xkWnL*T)hi(B|l@&9nvA45_tL+5-~7c>adY zpnKx)lB?TbYe0m|b6grq_VaMbr>V@q%|7@&OStJCLV=yyPvGGeP~h>htSHu{Cs3cu zk7)ruB3#V1gm|Lcv)DK@Y_f@TRM#{~BbAn0iElKdU|O;$i0t|iej~)q=OJ#!DrfV$ zvr2hGyT${2&jX_n*XH{9EH^Mt+i>Tf_Anhs>=Z62AP;f;=l=Wi-*4c*-@t#rf&YF3 M|NRF3fBy#l50VS{R{#J2 diff --git a/src/main/resources/fxml/homewindow/Dashboard.fxml b/src/main/resources/fxml/homewindow/Dashboard.fxml index febed9f..6fbcdd6 100644 --- a/src/main/resources/fxml/homewindow/Dashboard.fxml +++ b/src/main/resources/fxml/homewindow/Dashboard.fxml @@ -16,36 +16,47 @@ ~ limitations under the License. --> - - - - - - - + + + + + + + + + + + + + + + + + + + + + - + - + - + - + @@ -58,7 +69,7 @@ - + @@ -70,16 +81,14 @@
- +
- + @@ -139,8 +148,7 @@ - + @@ -161,31 +169,30 @@ - + - + @@ -213,31 +220,22 @@ -