Merge pull request #21 from RohitAwate/pseudo-tab-switching

Pseudo tab switching
This commit is contained in:
Rohit Awate 2018-08-21 19:05:18 +05:30 committed by GitHub
commit 94db944e28
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
67 changed files with 2987 additions and 2140 deletions

View file

@ -37,7 +37,7 @@ public class HomeWindowController implements Initializable {
@FXML @FXML
private SplitPane splitPane; private SplitPane splitPane;
@FXML @FXML
private TabPane homeWindowTabPane; private TabPane tabPane;
@FXML @FXML
private TextField historyTextField; private TextField historyTextField;
@FXML @FXML

View file

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.rohitawate</groupId> <groupId>com.rohitawate</groupId>
<artifactId>Everest</artifactId> <artifactId>Everest</artifactId>
<version>Alpha-1.2</version> <version>Alpha-1.3</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

View file

@ -15,7 +15,6 @@
*/ */
package com.rohitawate.everest; package com.rohitawate.everest;
import com.rohitawate.everest.misc.Services;
import com.rohitawate.everest.misc.ThemeManager; import com.rohitawate.everest.misc.ThemeManager;
import com.rohitawate.everest.settings.SettingsLoader; import com.rohitawate.everest.settings.SettingsLoader;
import javafx.application.Application; import javafx.application.Application;
@ -30,15 +29,11 @@ import javafx.stage.Stage;
public class Main extends Application { public class Main extends Application {
@Override @Override
public void start(Stage primaryStage) throws Exception { public void start(Stage primaryStage) throws Exception {
Services.start();
Services.startServicesThread.join();
SettingsLoader settingsLoader = new SettingsLoader(); SettingsLoader settingsLoader = new SettingsLoader();
settingsLoader.settingsLoaderThread.join(); settingsLoader.settingsLoaderThread.join();
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/homewindow/HomeWindow.fxml")); FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/homewindow/HomeWindow.fxml"));
Parent homeWindow = loader.load(); Parent homeWindow = loader.load();
Services.homeWindowController = loader.getController();
Stage dashboardStage = new Stage(); Stage dashboardStage = new Stage();
ThemeManager.setTheme(homeWindow); ThemeManager.setTheme(homeWindow);

View file

@ -17,17 +17,19 @@
package com.rohitawate.everest.controllers; package com.rohitawate.everest.controllers;
import com.rohitawate.everest.controllers.codearea.EverestCodeArea; import com.rohitawate.everest.controllers.codearea.EverestCodeArea;
import com.rohitawate.everest.controllers.codearea.EverestCodeArea.HighlightMode; import com.rohitawate.everest.controllers.codearea.highlighters.HighlighterFactory;
import com.rohitawate.everest.controllers.state.DashboardState; import com.rohitawate.everest.logging.LoggingService;
import com.rohitawate.everest.controllers.state.FieldState;
import com.rohitawate.everest.misc.Services;
import com.rohitawate.everest.misc.ThemeManager; import com.rohitawate.everest.misc.ThemeManager;
import com.rohitawate.everest.models.requests.HTTPConstants;
import com.rohitawate.everest.state.ComposerState;
import com.rohitawate.everest.state.FieldState;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
import javafx.scene.Parent; import javafx.scene.Parent;
import javafx.scene.control.ComboBox; import javafx.scene.control.ComboBox;
import javafx.scene.control.Tab; import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.stage.FileChooser; import javafx.stage.FileChooser;
@ -53,6 +55,8 @@ public class BodyTabController implements Initializable {
@FXML @FXML
TextField filePathField; TextField filePathField;
@FXML @FXML
private TabPane bodyTabPane;
@FXML
private VBox rawVBox; private VBox rawVBox;
EverestCodeArea rawInputArea; EverestCodeArea rawInputArea;
@ -61,7 +65,12 @@ public class BodyTabController implements Initializable {
@Override @Override
public void initialize(URL location, ResourceBundle resources) { public void initialize(URL location, ResourceBundle resources) {
rawInputTypeBox.getItems().addAll("PLAIN TEXT", "JSON", "XML", "HTML"); rawInputTypeBox.getItems().addAll(
HTTPConstants.JSON,
HTTPConstants.XML,
HTTPConstants.HTML,
HTTPConstants.PLAIN_TEXT
);
rawInputTypeBox.getSelectionModel().select(0); rawInputTypeBox.getSelectionModel().select(0);
rawInputArea = new EverestCodeArea(); rawInputArea = new EverestCodeArea();
@ -71,21 +80,13 @@ public class BodyTabController implements Initializable {
rawInputTypeBox.valueProperty().addListener(change -> { rawInputTypeBox.valueProperty().addListener(change -> {
String type = rawInputTypeBox.getValue(); String type = rawInputTypeBox.getValue();
HighlightMode mode;
switch (type) { if (type.equals(HTTPConstants.PLAIN_TEXT)) {
case "JSON": rawInputArea.setHighlighter(HighlighterFactory.getHighlighter(type));
mode = HighlightMode.JSON; return;
break;
case "XML":
mode = HighlightMode.XML;
break;
case "HTML":
mode = HighlightMode.HTML;
break;
default:
mode = HighlightMode.PLAIN;
} }
rawInputArea.setMode(mode);
rawInputArea.setHighlighter(HighlighterFactory.getHighlighter(type));
}); });
try { try {
@ -99,14 +100,14 @@ public class BodyTabController implements Initializable {
urlTab.setContent(formTabContent); urlTab.setContent(formTabContent);
urlTabController = loader.getController(); urlTabController = loader.getController();
} catch (IOException e) { } catch (IOException e) {
Services.loggingService.logSevere("Could not load URL tab.", e, LocalDateTime.now()); LoggingService.logSevere("Could not load URL tab.", e, LocalDateTime.now());
} }
} }
@FXML @FXML
private void browseFile() { private void browseFile() {
FileChooser fileChooser = new FileChooser(); FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Choose a binary file to add to request..."); fileChooser.setTitle("Choose a binary file to add to the request");
Window dashboardWindow = filePathField.getScene().getWindow(); Window dashboardWindow = filePathField.getScene().getWindow();
String filePath; String filePath;
@ -119,51 +120,51 @@ public class BodyTabController implements Initializable {
filePathField.setText(filePath); filePathField.setText(filePath);
} }
public DashboardState getState() { @FXML
DashboardState state = new DashboardState(); private void clearFilePath() {
filePathField.clear();
}
public ComposerState getState() {
ComposerState state = new ComposerState();
state.rawBodyType = rawInputTypeBox.getValue();
state.rawBody = rawInputArea.getText();
state.urlStringTuples = urlTabController.getFieldStates(); state.urlStringTuples = urlTabController.getFieldStates();
state.formStringTuples = formDataTabController.getStringFieldStates(); state.formStringTuples = formDataTabController.getStringFieldStates();
state.formFileTuples = formDataTabController.getFileFieldStates(); state.formFileTuples = formDataTabController.getFileFieldStates();
state.binaryFilePath = filePathField.getText(); state.binaryFilePath = filePathField.getText();
if (rawTab.isSelected()) { state.rawBody = rawInputArea.getText();
switch (rawInputTypeBox.getValue()) { state.rawBodyBoxValue = HTTPConstants.getComplexContentType(rawInputTypeBox.getValue());
case "JSON":
state.contentType = MediaType.APPLICATION_JSON; switch (bodyTabPane.getSelectionModel().getSelectedIndex()) {
case 1:
state.contentType = MediaType.APPLICATION_FORM_URLENCODED;
break; break;
case "XML": case 2:
state.contentType = MediaType.APPLICATION_XML; state.contentType = state.rawBodyBoxValue;
break; break;
case "HTML": case 3:
state.contentType = MediaType.TEXT_HTML; state.contentType = MediaType.APPLICATION_OCTET_STREAM;
break; break;
default: default:
state.contentType = MediaType.TEXT_PLAIN;
}
} else if (formTab.isSelected()) {
state.contentType = MediaType.MULTIPART_FORM_DATA; state.contentType = MediaType.MULTIPART_FORM_DATA;
} else if (urlTab.isSelected()) {
state.contentType = MediaType.APPLICATION_FORM_URLENCODED;
} else {
state.contentType = MediaType.APPLICATION_OCTET_STREAM;
} }
return state; return state;
} }
public void setState(DashboardState state) { public void setState(ComposerState state) {
// Adding URL tab's tuples // Adding URL tab's tuples
if (state.urlStringTuples != null) if (state.urlStringTuples != null) {
for (FieldState fieldState : state.urlStringTuples) for (FieldState fieldState : state.urlStringTuples)
urlTabController.addField(fieldState); urlTabController.addField(fieldState);
}
// Adding Form tab's string tuples // Adding Form tab's string tuples
if (state.formStringTuples != null) if (state.formStringTuples != null) {
for (FieldState fieldState : state.formStringTuples) for (FieldState fieldState : state.formStringTuples)
formDataTabController.addStringField(fieldState); formDataTabController.addStringField(fieldState);
}
// Adding Form tab's file tuples // Adding Form tab's file tuples
if (state.formFileTuples != null) if (state.formFileTuples != null)
@ -171,30 +172,47 @@ public class BodyTabController implements Initializable {
formDataTabController.addFileField(fieldState); formDataTabController.addFileField(fieldState);
setRawTab(state); setRawTab(state);
filePathField.setText(state.binaryFilePath); filePathField.setText(state.binaryFilePath);
}
private void setRawTab(DashboardState state) { int tab = 0;
HighlightMode mode; if (state.contentType != null) {
switch (state.contentType) {
if (state.rawBodyType != null && state.rawBody != null) { case MediaType.APPLICATION_OCTET_STREAM:
switch (state.rawBodyType) { tab = 3;
case "JSON":
mode = HighlightMode.JSON;
break; break;
case "XML": case MediaType.APPLICATION_FORM_URLENCODED:
mode = HighlightMode.XML; tab = 1;
break; break;
case "HTML": case MediaType.APPLICATION_JSON:
mode = HighlightMode.HTML; case MediaType.APPLICATION_XML:
case MediaType.TEXT_HTML:
case MediaType.TEXT_PLAIN:
tab = 2;
break; break;
default: default:
mode = HighlightMode.PLAIN; tab = 0;
} }
rawInputArea.setText(state.rawBody, mode); }
bodyTabPane.getSelectionModel().select(tab);
}
void reset() {
urlTabController.clear();
formDataTabController.clear();
rawInputArea.clear();
rawInputTypeBox.setValue(HTTPConstants.PLAIN_TEXT);
filePathField.clear();
}
private void setRawTab(ComposerState state) {
if (state.rawBodyBoxValue != null && state.rawBody != null) {
String simplifiedContentType = HTTPConstants.getSimpleContentType(state.rawBodyBoxValue);
rawInputTypeBox.setValue(simplifiedContentType);
rawInputArea.setText(state.rawBody, HighlighterFactory.getHighlighter(simplifiedContentType));
} else { } else {
rawInputArea.setText("", HighlightMode.PLAIN); rawInputTypeBox.setValue(HTTPConstants.PLAIN_TEXT);
rawInputArea.setHighlighter(HighlighterFactory.getHighlighter(HTTPConstants.PLAIN_TEXT));
} }
} }
} }

View file

@ -15,29 +15,34 @@
*/ */
package com.rohitawate.everest.controllers; package com.rohitawate.everest.controllers;
import com.fasterxml.jackson.databind.JsonNode;
import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXProgressBar; import com.jfoenix.controls.JFXProgressBar;
import com.jfoenix.controls.JFXSnackbar; import com.jfoenix.controls.JFXSnackbar;
import com.rohitawate.everest.controllers.codearea.EverestCodeArea; import com.rohitawate.everest.controllers.codearea.EverestCodeArea;
import com.rohitawate.everest.controllers.codearea.EverestCodeArea.HighlightMode; import com.rohitawate.everest.controllers.codearea.highlighters.HighlighterFactory;
import com.rohitawate.everest.controllers.state.DashboardState; import com.rohitawate.everest.controllers.visualizers.TreeVisualizer;
import com.rohitawate.everest.controllers.state.FieldState; import com.rohitawate.everest.controllers.visualizers.Visualizer;
import com.rohitawate.everest.exceptions.NullResponseException;
import com.rohitawate.everest.exceptions.RedirectException; import com.rohitawate.everest.exceptions.RedirectException;
import com.rohitawate.everest.exceptions.UnreliableResponseException; import com.rohitawate.everest.format.FormatterFactory;
import com.rohitawate.everest.logging.LoggingService;
import com.rohitawate.everest.misc.EverestUtilities; import com.rohitawate.everest.misc.EverestUtilities;
import com.rohitawate.everest.misc.Services;
import com.rohitawate.everest.misc.ThemeManager; import com.rohitawate.everest.misc.ThemeManager;
import com.rohitawate.everest.models.requests.DELETERequest; import com.rohitawate.everest.models.requests.DELETERequest;
import com.rohitawate.everest.models.requests.DataDispatchRequest; import com.rohitawate.everest.models.requests.DataRequest;
import com.rohitawate.everest.models.requests.GETRequest; import com.rohitawate.everest.models.requests.GETRequest;
import com.rohitawate.everest.models.requests.HTTPConstants;
import com.rohitawate.everest.models.responses.EverestResponse; import com.rohitawate.everest.models.responses.EverestResponse;
import com.rohitawate.everest.requestmanager.DataDispatchRequestManager;
import com.rohitawate.everest.requestmanager.RequestManager; import com.rohitawate.everest.requestmanager.RequestManager;
import com.rohitawate.everest.requestmanager.RequestManagersPool;
import com.rohitawate.everest.state.ComposerState;
import com.rohitawate.everest.state.DashboardState;
import com.rohitawate.everest.state.FieldState;
import com.rohitawate.everest.sync.SyncManager;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty; import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleIntegerProperty;
import javafx.event.ActionEvent; import javafx.event.Event;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
@ -46,14 +51,11 @@ import javafx.scene.control.*;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCode;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import org.fxmisc.flowless.VirtualizedScrollPane; import org.fxmisc.flowless.VirtualizedScrollPane;
import javax.ws.rs.ProcessingException; import javax.ws.rs.ProcessingException;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.awt.*; import java.awt.*;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
@ -68,22 +70,20 @@ import java.util.ResourceBundle;
public class DashboardController implements Initializable { public class DashboardController implements Initializable {
@FXML @FXML
private StackPane dashboard; private VBox dashboard;
@FXML @FXML
TextField addressField; TextField addressField;
@FXML @FXML
ComboBox<String> httpMethodBox, responseTypeBox; ComboBox<String> httpMethodBox, responseTypeBox;
@FXML @FXML
private VBox responseBox, loadingLayer, promptLayer, errorLayer, paramsBox; private VBox responseBox, responseLayer, loadingLayer, promptLayer, errorLayer, paramsBox;
@FXML
private HBox responseDetails;
@FXML @FXML
private Label statusCode, statusCodeDescription, responseTime, private Label statusCode, statusCodeDescription, responseTime,
responseSize, errorTitle, errorDetails; responseSize, errorTitle, errorDetails;
@FXML @FXML
private JFXButton cancelButton, copyBodyButton; private JFXButton sendButton, cancelButton, copyBodyButton;
@FXML @FXML
TabPane requestOptionsTab; TabPane requestOptionsTab, responseTabPane;
@FXML @FXML
Tab paramsTab, authTab, headersTab, bodyTab; Tab paramsTab, authTab, headersTab, bodyTab;
@FXML @FXML
@ -92,21 +92,33 @@ public class DashboardController implements Initializable {
private JFXProgressBar progressBar; private JFXProgressBar progressBar;
private JFXSnackbar snackbar; private JFXSnackbar snackbar;
private final String[] httpMethods = {"GET", "POST", "PUT", "DELETE", "PATCH"};
private List<StringKeyValueFieldController> paramsControllers; private List<StringKeyValueFieldController> paramsControllers;
private List<String> appendedParams;
private RequestManager requestManager; private RequestManager requestManager;
private HeaderTabController headerTabController; private HeaderTabController headerTabController;
private BodyTabController bodyTabController; private BodyTabController bodyTabController;
private IntegerProperty paramsCountProperty; private IntegerProperty paramsCountProperty;
private Visualizer visualizer; private Visualizer visualizer;
private ResponseHeadersViewer responseHeadersViewer; private ResponseHeadersViewer responseHeadersViewer;
private SyncManager syncManager;
private GETRequest getRequest; private GETRequest getRequest;
private DataDispatchRequest dataRequest; private DataRequest dataRequest;
private DELETERequest deleteRequest; private DELETERequest deleteRequest;
private HashMap<String, String> params; private HashMap<String, String> params;
private EverestCodeArea responseArea; private EverestCodeArea responseArea;
private ResponseLayer visibleLayer;
public enum ResponseLayer {
PROMPT, LOADING, RESPONSE, ERROR
}
public enum ResponseTab {
BODY, VISUALIZER, HEADERS
}
public enum ComposerTab {
PARAMS, AUTH, HEADERS, BODY
}
@Override @Override
public void initialize(URL url, ResourceBundle rb) { public void initialize(URL url, ResourceBundle rb) {
@ -118,34 +130,37 @@ public class DashboardController implements Initializable {
headerTabController = headerTabLoader.getController(); headerTabController = headerTabLoader.getController();
headersTab.setContent(headerTabContent); headersTab.setContent(headerTabContent);
// Loading the rawBody tab // Loading the body tab
FXMLLoader bodyTabLoader = new FXMLLoader(getClass().getResource("/fxml/homewindow/BodyTab.fxml")); FXMLLoader bodyTabLoader = new FXMLLoader(getClass().getResource("/fxml/homewindow/BodyTab.fxml"));
Parent bodyTabContent = bodyTabLoader.load(); Parent bodyTabContent = bodyTabLoader.load();
ThemeManager.setTheme(bodyTabContent); ThemeManager.setTheme(bodyTabContent);
bodyTabController = bodyTabLoader.getController(); bodyTabController = bodyTabLoader.getController();
bodyTab.setContent(bodyTabContent); bodyTab.setContent(bodyTabContent);
} catch (IOException e) { } catch (IOException e) {
Services.loggingService.logSevere("Could not load headers/rawBody tabs.", e, LocalDateTime.now()); LoggingService.logSevere("Could not load headers/body tabs.", e, LocalDateTime.now());
} }
snackbar = new JFXSnackbar(dashboard); snackbar = new JFXSnackbar(dashboard);
responseBox.getChildren().remove(0); showLayer(ResponseLayer.PROMPT);
promptLayer.setVisible(true); httpMethodBox.getItems().addAll(
httpMethodBox.getItems().addAll(httpMethods); HTTPConstants.GET,
HTTPConstants.POST,
HTTPConstants.PUT,
HTTPConstants.PATCH,
HTTPConstants.DELETE);
// Select GET by default // Select GET by default
httpMethodBox.getSelectionModel().select("GET"); httpMethodBox.getSelectionModel().select(HTTPConstants.GET);
paramsControllers = new ArrayList<>(); paramsControllers = new ArrayList<>();
paramsCountProperty = new SimpleIntegerProperty(paramsControllers.size()); paramsCountProperty = new SimpleIntegerProperty(0);
appendedParams = new ArrayList<>();
addParamField(); // Adds a blank param field addParamField(); // Adds a blank param field
bodyTab.disableProperty().bind( bodyTab.disableProperty().bind(
Bindings.or(httpMethodBox.valueProperty().isEqualTo("GET"), Bindings.or(httpMethodBox.valueProperty().isEqualTo(HTTPConstants.GET),
httpMethodBox.valueProperty().isEqualTo("DELETE"))); httpMethodBox.valueProperty().isEqualTo(HTTPConstants.DELETE)));
// Disabling Ctrl+Tab navigation // Disabling Ctrl+Tab navigation
requestOptionsTab.setOnKeyPressed(e -> { requestOptionsTab.setOnKeyPressed(e -> {
@ -161,36 +176,27 @@ public class DashboardController implements Initializable {
snackbar.show("Response body copied to clipboard.", 5000); snackbar.show("Response body copied to clipboard.", 5000);
}); });
responseTypeBox.getItems().addAll("JSON", "XML", "HTML", "PLAIN TEXT"); responseTypeBox.getItems().addAll(
HTTPConstants.JSON,
HTTPConstants.XML,
HTTPConstants.HTML,
HTTPConstants.PLAIN_TEXT);
responseTypeBox.valueProperty().addListener(change -> { responseTypeBox.valueProperty().addListener(change -> {
String type = responseTypeBox.getValue(); String type = responseTypeBox.getValue();
HighlightMode mode;
switch (type) { if (type.equals(HTTPConstants.JSON)) {
case "JSON": responseArea.setText(responseArea.getText(),
try { FormatterFactory.getHighlighter(type),
JsonNode node = EverestUtilities.jsonMapper.readTree(responseArea.getText()); HighlighterFactory.getHighlighter(type));
responseArea.setText(EverestUtilities.jsonMapper.writeValueAsString(node), HighlightMode.JSON);
} catch (IOException e) {
Services.loggingService.logWarning("Response could not be parsed.", e, LocalDateTime.now());
}
return; return;
case "XML":
mode = HighlightMode.XML;
break;
case "HTML":
mode = HighlightMode.XML;
break;
default:
mode = HighlightMode.PLAIN;
} }
responseArea.setMode(mode);
responseArea.setHighlighter(HighlighterFactory.getHighlighter(type));
}); });
errorTitle.setText("Oops... That's embarrassing!"); visualizer = new TreeVisualizer();
errorDetails.setText("Something went wrong. Try to make another request.\nRestart Everest if that doesn't work.");
visualizer = new Visualizer();
visualizerTab.setContent(visualizer); visualizerTab.setContent(visualizer);
responseArea = new EverestCodeArea(); responseArea = new EverestCodeArea();
@ -204,22 +210,17 @@ public class DashboardController implements Initializable {
@FXML @FXML
void sendRequest() { void sendRequest() {
if (requestManager != null && requestManager.isRunning()) { if (requestManager != null) {
snackbar.show("Please wait while the current request is processed.", 5000); while (requestManager.isRunning())
return; requestManager.cancel();
} requestManager.reset();
promptLayer.setVisible(false);
if (responseBox.getChildren().size() == 2) {
responseBox.getChildren().remove(0);
responseArea.clear();
} }
try { try {
String address = addressField.getText().trim(); String address = addressField.getText().trim();
if (address.equals("")) { if (address.equals("")) {
promptLayer.setVisible(true); showLayer(ResponseLayer.PROMPT);
snackbar.show("Please enter an address.", 3000); snackbar.show("Please enter an address.", 3000);
return; return;
} }
@ -234,49 +235,28 @@ public class DashboardController implements Initializable {
addressField.setText(address); addressField.setText(address);
switch (httpMethodBox.getValue()) { switch (httpMethodBox.getValue()) {
case "GET": case HTTPConstants.GET:
if (getRequest == null) if (getRequest == null)
getRequest = new GETRequest(); getRequest = new GETRequest();
getRequest.setTarget(address); getRequest.setTarget(address);
getRequest.setHeaders(headerTabController.getHeaders()); getRequest.setHeaders(headerTabController.getHeaders());
requestManager = Services.pool.get(); requestManager = RequestManagersPool.manager();
requestManager.setRequest(getRequest); requestManager.setRequest(getRequest);
cancelButton.setOnAction(e -> requestManager.cancel());
configureRequestManager();
requestManager.start();
break; break;
case "POST": case HTTPConstants.POST:
case "PUT": case HTTPConstants.PUT:
case "PATCH": case HTTPConstants.PATCH:
if (dataRequest == null) if (dataRequest == null)
dataRequest = new DataDispatchRequest(); dataRequest = new DataRequest();
dataRequest.setRequestType(httpMethodBox.getValue()); dataRequest.setRequestType(httpMethodBox.getValue());
dataRequest.setTarget(address); dataRequest.setTarget(address);
dataRequest.setHeaders(headerTabController.getHeaders()); dataRequest.setHeaders(headerTabController.getHeaders());
if (bodyTabController.rawTab.isSelected()) { if (bodyTabController.rawTab.isSelected()) {
String contentType; dataRequest.setContentType(HTTPConstants.getComplexContentType(bodyTabController.rawInputTypeBox.getValue()));
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()); dataRequest.setBody(bodyTabController.rawInputArea.getText());
} else if (bodyTabController.formTab.isSelected()) { } else if (bodyTabController.formTab.isSelected()) {
dataRequest.setStringTuples(bodyTabController.formDataTabController.getStringTuples()); dataRequest.setStringTuples(bodyTabController.formDataTabController.getStringTuples());
@ -290,59 +270,46 @@ public class DashboardController implements Initializable {
dataRequest.setContentType(MediaType.APPLICATION_FORM_URLENCODED); dataRequest.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
} }
requestManager = Services.pool.data(); requestManager = RequestManagersPool.manager();
requestManager.setRequest(dataRequest); requestManager.setRequest(dataRequest);
cancelButton.setOnAction(e -> requestManager.cancel());
configureRequestManager();
requestManager.start();
break; break;
case "DELETE": case HTTPConstants.DELETE:
if (deleteRequest == null) if (deleteRequest == null)
deleteRequest = new DELETERequest(); deleteRequest = new DELETERequest();
deleteRequest.setTarget(address); deleteRequest.setTarget(address);
deleteRequest.setHeaders(headerTabController.getHeaders()); deleteRequest.setHeaders(headerTabController.getHeaders());
requestManager = Services.pool.delete(); requestManager = RequestManagersPool.manager();
requestManager.setRequest(deleteRequest); requestManager.setRequest(deleteRequest);
cancelButton.setOnAction(e -> requestManager.cancel());
configureRequestManager();
requestManager.start();
break; break;
default: default:
loadingLayer.setVisible(false); showLayer(ResponseLayer.PROMPT);
} }
Services.historyManager.saveHistory(getState()); cancelButton.setOnAction(e -> requestManager.cancel());
requestManager.addHandlers(this::whileRunning, this::onSucceeded, this::onFailed, this::onCancelled);
requestManager.start();
syncManager.saveState(getState().composer);
} catch (MalformedURLException MURLE) { } catch (MalformedURLException MURLE) {
promptLayer.setVisible(true); showLayer(ResponseLayer.PROMPT);
snackbar.show("Invalid address. Please verify and try again.", 3000); snackbar.show("Invalid address. Please verify and try again.", 3000);
} catch (Exception E) { } catch (Exception E) {
Services.loggingService.logSevere("Request execution failed.", E, LocalDateTime.now()); LoggingService.logSevere("Request execution failed.", E, LocalDateTime.now());
errorLayer.setVisible(true);
errorTitle.setText("Oops... That's embarrassing!"); 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 request.\nRestart Everest if that doesn't work.");
showLayer(ResponseLayer.ERROR);
} }
} }
private void configureRequestManager() { // TODO: Clean this method
progressBar.progressProperty().bind(requestManager.progressProperty()); private void onFailed(Event event) {
requestManager.setOnRunning(e -> whileRunning()); showLayer(ResponseLayer.ERROR);
requestManager.setOnSucceeded(e -> onSucceeded());
requestManager.setOnCancelled(e -> onCancelled());
requestManager.setOnFailed(e -> onFailed());
}
private void onFailed() {
loadingLayer.setVisible(false);
promptLayer.setVisible(false);
Throwable throwable = requestManager.getException(); Throwable throwable = requestManager.getException();
Exception exception = (Exception) throwable; Exception exception = (Exception) throwable;
Services.loggingService.logWarning(httpMethodBox.getValue() + " request could not be processed.", exception, LocalDateTime.now()); LoggingService.logWarning(httpMethodBox.getValue() + " request could not be processed.", exception, LocalDateTime.now());
if (throwable.getClass() == UnreliableResponseException.class) { if (throwable.getClass() == NullResponseException.class) {
UnreliableResponseException URE = (UnreliableResponseException) throwable; NullResponseException URE = (NullResponseException) throwable;
errorTitle.setText(URE.getExceptionTitle()); errorTitle.setText(URE.getExceptionTitle());
errorDetails.setText(URE.getExceptionDetails()); errorDetails.setText(URE.getExceptionDetails());
} else if (throwable.getClass() == ProcessingException.class) { } else if (throwable.getClass() == ProcessingException.class) {
@ -357,82 +324,128 @@ public class DashboardController implements Initializable {
return; return;
} }
if (requestManager.getClass() == DataDispatchRequestManager.class) { if (requestManager.getRequest().getClass().equals(DataRequest.class)) {
if (throwable.getCause() != null && throwable.getCause().getClass() == IllegalArgumentException.class) { if (throwable.getClass() == FileNotFoundException.class) {
errorTitle.setText("Did you forget something?");
errorDetails.setText("Please specify at least one rawBody part for your " + httpMethodBox.getValue() + " request.");
} else if (throwable.getClass() == FileNotFoundException.class) {
errorTitle.setText("File(s) not found:"); errorTitle.setText("File(s) not found:");
errorDetails.setText(throwable.getMessage()); errorDetails.setText(throwable.getMessage());
} }
} }
errorLayer.setVisible(true);
requestManager.reset(); requestManager.reset();
} }
private void onCancelled() { private void onCancelled(Event event) {
loadingLayer.setVisible(false); showLayer(ResponseLayer.PROMPT);
promptLayer.setVisible(true); requestManager.reset();
snackbar.show("Request canceled.", 2000); addressField.requestFocus();
}
private void onSucceeded(Event event) {
showLayer(ResponseLayer.RESPONSE);
showResponse(requestManager.getValue());
requestManager.reset(); requestManager.reset();
} }
private void onSucceeded() { private void whileRunning(Event event) {
displayResponse(requestManager.getValue()); progressBar.requestLayout();
errorLayer.setVisible(false); progressBar.progressProperty().bind(requestManager.progressProperty());
loadingLayer.setVisible(false);
requestManager.reset();
}
private void whileRunning() {
responseArea.clear(); responseArea.clear();
errorLayer.setVisible(false); showLayer(ResponseLayer.LOADING);
loadingLayer.setVisible(true);
} }
private void displayResponse(EverestResponse response) { private void showLayer(ResponseLayer layer) {
this.visibleLayer = layer;
switch (layer) {
case ERROR:
errorLayer.setVisible(true);
loadingLayer.setVisible(false);
promptLayer.setVisible(false);
responseLayer.setVisible(false);
break;
case LOADING:
loadingLayer.setVisible(true);
errorLayer.setVisible(false);
promptLayer.setVisible(false);
responseLayer.setVisible(false);
break;
case RESPONSE:
responseLayer.setVisible(true);
errorLayer.setVisible(false);
loadingLayer.setVisible(false);
promptLayer.setVisible(false);
break;
case PROMPT:
default:
promptLayer.setVisible(true);
loadingLayer.setVisible(false);
errorLayer.setVisible(false);
responseLayer.setVisible(false);
break;
}
}
private void showResponse(EverestResponse response) {
if (response == null)
return;
prettifyResponseBody(response); prettifyResponseBody(response);
responseBox.getChildren().add(0, responseDetails);
statusCode.setText(Integer.toString(response.getStatusCode())); statusCode.setText(Integer.toString(response.getStatusCode()));
statusCodeDescription.setText(Response.Status.fromStatusCode(response.getStatusCode()).getReasonPhrase()); statusCodeDescription.setText(EverestResponse.getReasonPhrase(response.getStatusCode()));
responseTime.setText(Long.toString(response.getTime()) + " ms"); responseTime.setText(Long.toString(response.getTime()) + " ms");
responseSize.setText(Integer.toString(response.getSize()) + " B"); responseSize.setText(Integer.toString(response.getSize()) + " B");
responseHeadersViewer.populate(response); responseHeadersViewer.populate(response);
} }
private void prettifyResponseBody(EverestResponse response) { private void showResponse(DashboardState state) {
String type; prettifyResponseBody(state.responseBody, state.responseType);
statusCode.setText(Integer.toString(state.statusCode));
statusCodeDescription.setText(EverestResponse.getReasonPhrase(state.statusCode));
responseTime.setText(Long.toString(state.responseTime) + " ms");
responseSize.setText(Integer.toString(state.responseSize) + " B");
responseHeadersViewer.populate(state.responseHeaders);
if (response.getMediaType() != null) if (state.visibleResponseTab != null) {
type = response.getMediaType().toString(); int tab;
else switch (state.visibleResponseTab) {
type = null; case VISUALIZER:
tab = 1;
break;
case HEADERS:
tab = 2;
break;
default:
tab = 0;
}
String responseBody = response.getBody(); responseTabPane.getSelectionModel().select(tab);
}
}
private void prettifyResponseBody(String body, String contentType) {
showLayer(ResponseLayer.RESPONSE);
visualizerTab.setDisable(true); visualizerTab.setDisable(true);
visualizer.clear();
try {
if (type != null) {
// Selects only the part preceding the ';', skipping the character encoding
type = type.split(";")[0];
switch (type.toLowerCase()) { try {
String simplifiedContentType;
if (contentType != null) {
/*
Selects only the part preceding the ';', skipping the character encoding.
For example, "application/json; charset=utf-8" becomes "application/json"
*/
contentType = contentType.split(";")[0];
switch (contentType.toLowerCase()) {
case "application/json": case "application/json":
responseTypeBox.setValue("JSON"); simplifiedContentType = HTTPConstants.JSON;
JsonNode node = EverestUtilities.jsonMapper.readTree(responseBody);
responseArea.setText(EverestUtilities.jsonMapper.writeValueAsString(node), HighlightMode.JSON);
visualizerTab.setDisable(false); visualizerTab.setDisable(false);
visualizer.populate(node); visualizer.populate(body);
break; break;
case "application/xml": case "application/xml":
responseTypeBox.setValue("XML"); simplifiedContentType = HTTPConstants.XML;
responseArea.setText(responseBody, HighlightMode.XML);
break; break;
case "text/html": case "text/html":
responseTypeBox.setValue("HTML"); simplifiedContentType = HTTPConstants.HTML;
responseArea.setText(responseBody, HighlightMode.HTML);
if (Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { if (Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
snackbar.show("Open link in browser?", "YES", 5000, e -> { snackbar.show("Open link in browser?", "YES", 5000, e -> {
snackbar.close(); snackbar.close();
@ -440,50 +453,83 @@ public class DashboardController implements Initializable {
try { try {
Desktop.getDesktop().browse(new URI(addressField.getText())); Desktop.getDesktop().browse(new URI(addressField.getText()));
} catch (Exception ex) { } catch (Exception ex) {
Services.loggingService.logWarning("Invalid URL encountered while opening in browser.", ex, LocalDateTime.now()); LoggingService.logWarning("Invalid URL encountered while opening in browser.", ex, LocalDateTime.now());
} }
}).start(); }).start();
}); });
} }
break; break;
default: default:
responseTypeBox.setValue("PLAIN TEXT"); simplifiedContentType = HTTPConstants.PLAIN_TEXT;
responseArea.setText(responseBody, HighlightMode.PLAIN);
} }
} else { } else {
responseTypeBox.setValue("PLAIN"); simplifiedContentType = HTTPConstants.PLAIN_TEXT;
responseArea.setText("No rawBody found in the response.", HighlightMode.PLAIN);
} }
if (body == null || body.equals(""))
body = "No body returned in the response.";
responseArea.setText(body,
FormatterFactory.getHighlighter(simplifiedContentType),
HighlighterFactory.getHighlighter(simplifiedContentType));
responseTypeBox.setValue(simplifiedContentType);
} catch (Exception e) { } catch (Exception e) {
snackbar.show("Response could not be parsed.", 5000); String errorMessage = "Response could not be parsed.";
Services.loggingService.logSevere("Response could not be parsed.", e, LocalDateTime.now()); snackbar.show(errorMessage, 5000);
errorLayer.setVisible(true); LoggingService.logSevere(errorMessage, e, LocalDateTime.now());
errorTitle.setText("Parsing Error"); errorTitle.setText("Parsing Error");
errorDetails.setText("Everest could not parse the response."); errorDetails.setText(errorMessage);
showLayer(ResponseLayer.ERROR);
} }
} }
private void prettifyResponseBody(EverestResponse response) {
String type;
if (response.getMediaType() != null)
type = response.getMediaType().toString();
else
type = "";
String responseBody = response.getBody();
prettifyResponseBody(responseBody, type);
}
@FXML @FXML
private void clearResponseArea() { private void clearResponseArea() {
responseBox.getChildren().remove(0);
responseArea.clear(); responseArea.clear();
promptLayer.setVisible(true); showLayer(ResponseLayer.PROMPT);
addressField.requestFocus();
} }
@FXML @FXML
private void appendParams() { private void appendParams() {
String pair, key, value; StringBuilder url = new StringBuilder();
url.append(addressField.getText().split("\\?")[0]);
boolean addedQuestionMark = false;
String key, value;
for (StringKeyValueFieldController controller : paramsControllers) { for (StringKeyValueFieldController controller : paramsControllers) {
if (controller.isChecked()) { if (controller.isChecked()) {
if (!addedQuestionMark) {
url.append("?");
addedQuestionMark = true;
} else {
url.append("&");
}
key = controller.getHeader().getKey(); key = controller.getHeader().getKey();
value = controller.getHeader().getValue(); value = controller.getHeader().getValue();
pair = key + value; url.append(key);
if (!appendedParams.contains(pair)) { url.append("=");
addressField.appendText("?" + key + "=" + value + "&"); url.append(value);
appendedParams.add(pair);
}
} }
} }
addressField.clear();
addressField.setText(EverestUtilities.encodeURL(url.toString()));
} }
/** /**
@ -492,43 +538,48 @@ public class DashboardController implements Initializable {
public ArrayList<FieldState> getParamFieldStates() { public ArrayList<FieldState> getParamFieldStates() {
ArrayList<FieldState> states = new ArrayList<>(); ArrayList<FieldState> states = new ArrayList<>();
for (StringKeyValueFieldController controller : paramsControllers) for (StringKeyValueFieldController controller : paramsControllers) {
if (!controller.isKeyFieldEmpty() && !controller.isValueFieldEmpty()) if (!controller.isKeyFieldEmpty() && !controller.isValueFieldEmpty())
states.add(controller.getState()); states.add(controller.getState());
}
return states; return states;
} }
private void addParamField() { private void addParamField() {
addParamField("", "", null, false); addParamField("", "", false);
} }
private void addParamField(FieldState state) { private void addParamField(FieldState state) {
addParamField(state.key, state.value, null, state.checked); addParamField(state.key, state.value, state.checked);
}
@FXML
private void addParamField(ActionEvent event) {
addParamField("", "", event, false);
} }
/** /**
* Adds a new URL-parameter field * Adds a new URL-parameter field
*/ */
private void addParamField(String key, String value, ActionEvent event, boolean checked) { private void addParamField(String key, String value, boolean checked) {
/* /*
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.
A value of null for the 'event' parameter indicates that the method call A value of null for the 'event' parameter indicates that the method call
came from code and not from the user. This call is made while recovering came from code and not from the user. This call is made while recovering
the application state. the application state.
*/ */
if (paramsControllers.size() > 0 && event == null) { if (paramsControllers.size() > 0) {
StringKeyValueFieldController previousController = paramsControllers.get(paramsControllers.size() - 1); StringKeyValueFieldController previousController = paramsControllers.get(paramsControllers.size() - 1);
if (previousController.isKeyFieldEmpty() && if (previousController.isKeyFieldEmpty() &&
previousController.isValueFieldEmpty()) { previousController.isValueFieldEmpty()) {
previousController.setKeyField(key); previousController.setKeyField(key);
previousController.setValueField(value); previousController.setValueField(value);
previousController.setChecked(checked);
/*
For when the last field is loaded from setState.
This makes sure an extra blank field is always present.
*/
if (!(key.equals("") && value.equals("")))
addParamField();
return; return;
} }
} }
@ -547,10 +598,45 @@ public class DashboardController implements Initializable {
paramsBox.getChildren().remove(headerField); paramsBox.getChildren().remove(headerField);
paramsControllers.remove(controller); paramsControllers.remove(controller);
paramsCountProperty.set(paramsCountProperty.get() - 1); paramsCountProperty.set(paramsCountProperty.get() - 1);
appendParams();
}); });
controller.setKeyHandler(keyEvent -> addParamField());
controller.getSelectedProperty().addListener(e -> appendParams());
controller.getKeyProperty().addListener(e -> appendParams());
controller.getValueProperty().addListener(e -> appendParams());
paramsBox.getChildren().add(headerField); paramsBox.getChildren().add(headerField);
} catch (IOException e) { } catch (IOException e) {
Services.loggingService.logSevere("Could not append params field.", e, LocalDateTime.now()); LoggingService.logSevere("Could not append params field.", e, LocalDateTime.now());
}
}
public void setSyncManager(SyncManager syncManager) {
this.syncManager = syncManager;
}
public ComposerTab getVisibleComposerTab() {
int visibleTab = requestOptionsTab.getSelectionModel().getSelectedIndex();
switch (visibleTab) {
case 1:
return ComposerTab.AUTH;
case 2:
return ComposerTab.HEADERS;
case 3:
return ComposerTab.BODY;
default:
return ComposerTab.PARAMS;
}
}
private ResponseTab getVisibleResponseTab() {
int visibleTab = responseTabPane.getSelectionModel().getSelectedIndex();
switch (visibleTab) {
case 1:
return ResponseTab.VISUALIZER;
case 2:
return ResponseTab.HEADERS;
default:
return ResponseTab.BODY;
} }
} }
@ -558,22 +644,55 @@ public class DashboardController implements Initializable {
* @return Current state of the Dashboard. * @return Current state of the Dashboard.
*/ */
public DashboardState getState() { public DashboardState getState() {
DashboardState dashboardState; DashboardState dashboardState = new DashboardState();
ComposerState composerState;
switch (httpMethodBox.getValue()) { switch (httpMethodBox.getValue()) {
case "POST": case HTTPConstants.POST:
case "PUT": case HTTPConstants.PUT:
case "PATCH": case HTTPConstants.PATCH:
dashboardState = bodyTabController.getState(); composerState = bodyTabController.getState();
break; break;
default: default:
// For GET, DELETE requests // For GET, DELETE requests
dashboardState = new DashboardState(); composerState = new ComposerState();
} }
dashboardState.target = addressField.getText(); composerState.target = addressField.getText();
dashboardState.httpMethod = httpMethodBox.getValue(); composerState.httpMethod = httpMethodBox.getValue();
dashboardState.headers = headerTabController.getFieldStates(); composerState.headers = headerTabController.getFieldStates();
dashboardState.params = getParamFieldStates(); composerState.params = getParamFieldStates();
dashboardState.composer = composerState;
dashboardState.visibleResponseLayer = visibleLayer;
dashboardState.visibleComposerTab = getVisibleComposerTab();
switch (visibleLayer) {
case RESPONSE:
dashboardState.visibleResponseTab = getVisibleResponseTab();
dashboardState.responseHeaders = responseHeadersViewer.getHeaders();
dashboardState.statusCode = Integer.parseInt(statusCode.getText());
String temp = responseSize.getText();
temp = temp.substring(0, temp.length() - 2);
dashboardState.responseSize = Integer.parseInt(temp);
temp = responseTime.getText();
temp = temp.substring(0, temp.length() - 3);
dashboardState.responseTime = Integer.parseInt(temp);
dashboardState.responseBody = responseArea.getText();
dashboardState.responseType = HTTPConstants.getComplexContentType(responseTypeBox.getValue());
break;
case ERROR:
dashboardState.errorTitle = errorTitle.getText();
dashboardState.errorDetails = errorDetails.getText();
break;
case LOADING:
dashboardState.handOverRequest(requestManager);
requestManager = null;
break;
}
return dashboardState; return dashboardState;
} }
@ -584,6 +703,60 @@ public class DashboardController implements Initializable {
* @param state - State of the dashboard * @param state - State of the dashboard
*/ */
public void setState(DashboardState state) { public void setState(DashboardState state) {
if (state == null)
return;
if (state.visibleComposerTab != null) {
int tab;
switch (state.visibleComposerTab) {
case AUTH:
tab = 1;
break;
case HEADERS:
tab = 2;
break;
case BODY:
tab = 3;
break;
default:
tab = 0;
}
requestOptionsTab.getSelectionModel().select(tab);
}
if (state.visibleResponseLayer != null) {
switch (state.visibleResponseLayer) {
case RESPONSE:
showResponse(state);
break;
case ERROR:
errorTitle.setText(state.errorTitle);
errorDetails.setText(state.errorDetails);
showLayer(ResponseLayer.ERROR);
break;
case LOADING:
/*
Accepts a RequestManager which is in the RUNNING state
and switches its handlers.
The handlers affect the Dashboard directly rather than the DashboardState.
*/
requestManager = state.getRequestManager();
requestManager.removeHandlers();
requestManager.addHandlers(this::whileRunning, this::onSucceeded, this::onFailed, this::onCancelled);
showLayer(ResponseLayer.LOADING);
break;
default:
showLayer(ResponseLayer.PROMPT);
break;
}
} else {
showLayer(ResponseLayer.PROMPT);
}
if (state.composer == null)
return;
/* /*
The only value from a set of constants in the state.json file is the httpMethod The only value from a set of constants in the state.json file is the httpMethod
which, if manipulated to a non-standard value by the user, would still which, if manipulated to a non-standard value by the user, would still
@ -595,30 +768,58 @@ public class DashboardController implements Initializable {
This is an extreme case, but still something to be taken care of. This is an extreme case, but still something to be taken care of.
*/ */
boolean validMethod = false; boolean validMethod = false;
String[] httpMethods =
{HTTPConstants.GET, HTTPConstants.POST, HTTPConstants.PUT, HTTPConstants.PATCH, HTTPConstants.DELETE};
for (String method : httpMethods) { for (String method : httpMethods) {
if (state.httpMethod.equals(method)) if (method.equals(state.composer.httpMethod))
validMethod = true; validMethod = true;
} }
if (!validMethod) { if (!validMethod) {
Services.loggingService.logInfo("Application state file was tampered with. State could not be recovered.", LocalDateTime.now()); LoggingService.logInfo("Application state file was tampered with. State could not be recovered.", LocalDateTime.now());
return; return;
} }
httpMethodBox.setValue(state.httpMethod); httpMethodBox.setValue(state.composer.httpMethod);
if (state.target != null) if (state.composer.target != null)
addressField.setText(state.target); addressField.setText(state.composer.target);
if (state.headers != null) if (state.composer.headers != null) {
for (FieldState fieldState : state.headers) for (FieldState fieldState : state.composer.headers)
headerTabController.addHeader(fieldState); headerTabController.addHeader(fieldState);
}
if (state.params != null) if (state.composer.params != null) {
for (FieldState fieldState : state.params) for (FieldState fieldState : state.composer.params)
addParamField(fieldState); addParamField(fieldState);
appendParams();
}
if (!(httpMethodBox.getValue().equals("GET") || httpMethodBox.getValue().equals("DELETE"))) if (!(state.composer.httpMethod.equals(HTTPConstants.GET) || state.composer.httpMethod.equals(HTTPConstants.DELETE)))
bodyTabController.setState(state); bodyTabController.setState(state.composer);
}
void reset() {
httpMethodBox.setValue(HTTPConstants.GET);
addressField.clear();
headerTabController.clear();
clearParams();
bodyTabController.reset();
responseArea.clear();
showLayer(ResponseLayer.PROMPT);
responseTabPane.getSelectionModel().select(0);
}
void clearParams() {
if (params != null)
params.clear();
if (paramsControllers != null)
paramsControllers.clear();
paramsBox.getChildren().clear();
paramsCountProperty.set(0);
addParamField();
} }
} }

View file

@ -18,11 +18,13 @@ package com.rohitawate.everest.controllers;
import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXCheckBox; import com.jfoenix.controls.JFXCheckBox;
import com.rohitawate.everest.controllers.state.FieldState; import com.rohitawate.everest.state.FieldState;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.event.EventHandler;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.stage.FileChooser; import javafx.stage.FileChooser;
import javafx.stage.Window; import javafx.stage.Window;
import javafx.util.Pair; import javafx.util.Pair;
@ -75,7 +77,7 @@ public class FileKeyValueFieldController implements Initializable {
@FXML @FXML
private void browseFile() { private void browseFile() {
FileChooser fileChooser = new FileChooser(); FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Choose a binary file to add to request..."); fileChooser.setTitle("Choose a binary file to add to the request");
Window dashboardWindow = fileValueField.getScene().getWindow(); Window dashboardWindow = fileValueField.getScene().getWindow();
String filePath; String filePath;
try { try {
@ -117,4 +119,9 @@ public class FileKeyValueFieldController implements Initializable {
public void setChecked(boolean checked) { public void setChecked(boolean checked) {
checkBox.setSelected(checked); checkBox.setSelected(checked);
} }
public void setKeyHandler(EventHandler<KeyEvent> handler) {
fileKeyField.setOnKeyPressed(handler);
fileValueField.setOnKeyPressed(handler);
}
} }

View file

@ -16,9 +16,9 @@
package com.rohitawate.everest.controllers; package com.rohitawate.everest.controllers;
import com.rohitawate.everest.controllers.state.FieldState; import com.rohitawate.everest.logging.LoggingService;
import com.rohitawate.everest.misc.Services;
import com.rohitawate.everest.misc.ThemeManager; import com.rohitawate.everest.misc.ThemeManager;
import com.rohitawate.everest.state.FieldState;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty; import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleIntegerProperty;
@ -51,42 +51,46 @@ public class FormDataTabController implements Initializable {
@Override @Override
public void initialize(URL location, ResourceBundle resources) { public void initialize(URL location, ResourceBundle resources) {
stringControllers = new ArrayList<>(); stringControllers = new ArrayList<>();
stringControllersCount = new SimpleIntegerProperty(stringControllers.size()); stringControllersCount = new SimpleIntegerProperty(0);
fileControllers = new ArrayList<>(); fileControllers = new ArrayList<>();
fileControllersCount = new SimpleIntegerProperty(fileControllers.size()); fileControllersCount = new SimpleIntegerProperty(0);
addFileField(); addFileField();
addStringField(); addStringField();
} }
public void addFileField(FieldState state) { public void addFileField(FieldState state) {
addFileField(state.key, state.value, null, state.checked); addFileField(state.key, state.value, state.checked);
}
@FXML
private void addFileField(ActionEvent event) {
addFileField("", "", event, false);
} }
private void addFileField() { private void addFileField() {
addFileField("", "", null, false); addFileField("", "", false);
} }
private void addFileField(String key, String value, ActionEvent event, boolean checked) { private void addFileField(String key, String value, boolean checked) {
/* /*
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.
A value of null for the 'event' parameter indicates that the method call A value of null for the 'event' parameter indicates that the method call
came from code and not from the user. This call is made while recovering came from code and not from the user. This call is made while recovering
the application state. the application state.
*/ */
if (fileControllers.size() > 0 && event == null) { if (fileControllers.size() > 0) {
FileKeyValueFieldController previousController = fileControllers.get(fileControllers.size() - 1); FileKeyValueFieldController previousController = fileControllers.get(fileControllers.size() - 1);
if (previousController.isFileKeyFieldEmpty() && if (previousController.isFileKeyFieldEmpty() &&
previousController.isFileValueFieldEmpty()) { previousController.isFileValueFieldEmpty()) {
previousController.setFileKeyField(key); previousController.setFileKeyField(key);
previousController.setFileValueField(value); previousController.setFileValueField(value);
previousController.setChecked(checked);
/*
For when the last field is loaded from setState.
This makes sure an extra blank field is always present.
*/
if (!key.equals("") && !value.equals(""))
addFileField();
return; return;
} }
} }
@ -107,9 +111,10 @@ public class FormDataTabController implements Initializable {
fileControllers.remove(controller); fileControllers.remove(controller);
fileControllersCount.set(fileControllersCount.get() - 1); fileControllersCount.set(fileControllersCount.get() - 1);
}); });
controller.setKeyHandler(keyEvent -> addFileField());
fieldsBox.getChildren().add(fileField); fieldsBox.getChildren().add(fileField);
} catch (IOException e) { } catch (IOException e) {
Services.loggingService.logSevere("Could not add file field.", e, LocalDateTime.now()); LoggingService.logSevere("Could not add file field.", e, LocalDateTime.now());
} }
} }
@ -117,11 +122,6 @@ public class FormDataTabController implements Initializable {
addStringField(state.key, state.value, null, state.checked); addStringField(state.key, state.value, null, state.checked);
} }
@FXML
private void addStringField(ActionEvent event) {
addStringField("", "", event, false);
}
private void addStringField() { private void addStringField() {
addStringField("", "", null, false); addStringField("", "", null, false);
} }
@ -140,6 +140,15 @@ public class FormDataTabController implements Initializable {
previousController.isValueFieldEmpty()) { previousController.isValueFieldEmpty()) {
previousController.setKeyField(key); previousController.setKeyField(key);
previousController.setValueField(value); previousController.setValueField(value);
previousController.setChecked(checked);
/*
For when the last field is loaded from setState.
This makes sure an extra blank field is always present.
*/
if (!key.equals("") && !value.equals(""))
addStringField();
return; return;
} }
} }
@ -159,9 +168,10 @@ public class FormDataTabController implements Initializable {
stringControllers.remove(controller); stringControllers.remove(controller);
stringControllersCount.set(stringControllersCount.get() - 1); stringControllersCount.set(stringControllersCount.get() - 1);
}); });
controller.setKeyHandler(keyEvent -> addStringField());
fieldsBox.getChildren().add(stringField); fieldsBox.getChildren().add(stringField);
} catch (IOException e) { } catch (IOException e) {
Services.loggingService.logSevere("Could not add string field.", e, LocalDateTime.now()); LoggingService.logSevere("Could not add string field.", e, LocalDateTime.now());
} }
} }
@ -198,29 +208,53 @@ public class FormDataTabController implements Initializable {
/** /**
* @return List of the states of all the non-empty string fields in the Form data tab. * @return List of the states of all the string fields in the Form data tab.
*/ */
public ArrayList<FieldState> getStringFieldStates() { public ArrayList<FieldState> getStringFieldStates() {
ArrayList<FieldState> states = new ArrayList<>(); ArrayList<FieldState> states = new ArrayList<>();
for (StringKeyValueFieldController controller : stringControllers) for (StringKeyValueFieldController controller : stringControllers) {
if (!controller.isKeyFieldEmpty() && !controller.isValueFieldEmpty()) if (!controller.isKeyFieldEmpty() && !controller.isValueFieldEmpty())
states.add(controller.getState()); states.add(controller.getState());
}
return states; return states;
} }
/** /**
* @return List of the states of all the non-empty file fields in the Form data tab. * @return List of the states of all the file fields in the Form data tab.
*/ */
public ArrayList<FieldState> getFileFieldStates() { public ArrayList<FieldState> getFileFieldStates() {
ArrayList<FieldState> states = new ArrayList<>(); ArrayList<FieldState> states = new ArrayList<>();
for (FileKeyValueFieldController controller : fileControllers) for (FileKeyValueFieldController controller : fileControllers) {
if (!controller.isFileKeyFieldEmpty() && !controller.isFileValueFieldEmpty()) if (!controller.isFileKeyFieldEmpty() && !controller.isFileValueFieldEmpty())
states.add(controller.getState()); states.add(controller.getState());
}
return states; return states;
} }
void clear() {
if (stringMap != null)
stringMap.clear();
if (fileMap != null)
fileMap.clear();
if (stringControllers != null)
stringControllers.clear();
if (fileControllers != null)
fileControllers.clear();
fieldsBox.getChildren().clear();
stringControllersCount.set(0);
fileControllersCount.set(0);
addStringField();
addFileField();
}
} }

View file

@ -16,13 +16,12 @@
package com.rohitawate.everest.controllers; package com.rohitawate.everest.controllers;
import com.rohitawate.everest.controllers.state.FieldState; import com.rohitawate.everest.logging.LoggingService;
import com.rohitawate.everest.misc.Services;
import com.rohitawate.everest.misc.ThemeManager; import com.rohitawate.everest.misc.ThemeManager;
import com.rohitawate.everest.state.FieldState;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty; import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleIntegerProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
@ -49,37 +48,41 @@ public class HeaderTabController implements Initializable {
@Override @Override
public void initialize(URL location, ResourceBundle resources) { public void initialize(URL location, ResourceBundle resources) {
controllers = new ArrayList<>(); controllers = new ArrayList<>();
controllersCount = new SimpleIntegerProperty(controllers.size()); controllersCount = new SimpleIntegerProperty(0);
addHeader(); addHeader();
} }
public void addHeader(FieldState state) { public void addHeader(FieldState state) {
addHeader(state.key, state.value, null, state.checked); addHeader(state.key, state.value, state.checked);
} }
private void addHeader() { private void addHeader() {
addHeader("", "", null, false); addHeader("", "", false);
} }
@FXML private void addHeader(String key, String value, boolean checked) {
private void addHeader(ActionEvent event) {
addHeader("", "", event, false);
}
private void addHeader(String key, String value, ActionEvent event, boolean checked) {
/* /*
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.
A value of null for the 'event' parameter indicates that the method call A value of null for the 'event' parameter indicates that the method call
came from code and not from the user. This call is made while recovering came from code and not from the user. This call is made while recovering
the application state. the application state.
*/ */
if (controllers.size() > 0 && event == null) { if (controllers.size() > 0) {
StringKeyValueFieldController previousController = controllers.get(controllers.size() - 1); StringKeyValueFieldController previousController = controllers.get(controllers.size() - 1);
if (previousController.isKeyFieldEmpty() && if (previousController.isKeyFieldEmpty() &&
previousController.isValueFieldEmpty()) { previousController.isValueFieldEmpty()) {
previousController.setKeyField(key); previousController.setKeyField(key);
previousController.setValueField(value); previousController.setValueField(value);
previousController.setChecked(checked);
/*
For when the last field is loaded from setState.
This makes sure an extra blank field is always present.
*/
if (!key.equals("") && !value.equals(""))
addHeader();
return; return;
} }
} }
@ -100,9 +103,10 @@ public class HeaderTabController implements Initializable {
controllers.remove(controller); controllers.remove(controller);
controllersCount.set(controllersCount.get() - 1); controllersCount.set(controllersCount.get() - 1);
}); });
controller.setKeyHandler(keyEvent -> addHeader());
headersBox.getChildren().add(headerField); headersBox.getChildren().add(headerField);
} catch (IOException e) { } catch (IOException e) {
Services.loggingService.logSevere("Could not add string field.", e, LocalDateTime.now()); LoggingService.logSevere("Could not add string field.", e, LocalDateTime.now());
} }
} }
@ -123,15 +127,27 @@ public class HeaderTabController implements Initializable {
} }
/** /**
* Return a list of the state of all the non-empty fields in the Headers tab. * Return a list of the state of all the fields in the Headers tab.
*/ */
public ArrayList<FieldState> getFieldStates() { public List<FieldState> getFieldStates() {
ArrayList<FieldState> states = new ArrayList<>(); ArrayList<FieldState> states = new ArrayList<>();
for (StringKeyValueFieldController controller : controllers) for (StringKeyValueFieldController controller : controllers) {
if (!controller.isKeyFieldEmpty() && !controller.isValueFieldEmpty()) if (!controller.isKeyFieldEmpty() && !controller.isValueFieldEmpty())
states.add(controller.getState()); states.add(controller.getState());
}
return states; return states;
} }
void clear() {
if (headers != null)
headers.clear();
if (controllers != null)
controllers.clear();
headersBox.getChildren().clear();
controllersCount.set(0);
addHeader();
}
} }

View file

@ -16,54 +16,76 @@
package com.rohitawate.everest.controllers; package com.rohitawate.everest.controllers;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.LocalDateTime;
import java.util.ResourceBundle;
import javax.ws.rs.core.MediaType;
import com.rohitawate.everest.controllers.search.Searchable; import com.rohitawate.everest.controllers.search.Searchable;
import com.rohitawate.everest.controllers.state.DashboardState; import com.rohitawate.everest.logging.LoggingService;
import com.rohitawate.everest.controllers.state.FieldState; import com.rohitawate.everest.models.requests.HTTPConstants;
import com.rohitawate.everest.misc.Services; import com.rohitawate.everest.state.ComposerState;
import com.rohitawate.everest.state.FieldState;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.Tooltip; import javafx.scene.control.Tooltip;
public class HistoryItemController implements Initializable, Searchable<DashboardState> { import java.net.MalformedURLException;
import java.net.URL;
import java.time.LocalDateTime;
import java.util.ResourceBundle;
public class HistoryItemController implements Initializable, Searchable<ComposerState> {
@FXML @FXML
private Label requestType, address; private Label methodLabel, address;
@FXML @FXML
private Tooltip tooltip; private Tooltip tooltip;
private DashboardState state; private static final String GETStyle = "-fx-text-fill: orangered";
private static final String POSTStyle = "-fx-text-fill: cornflowerblue";
private static final String PUTStyle = "-fx-text-fill: deeppink";
private static final String PATCHStyle = "-fx-text-fill: teal";
private static final String DELETEStyle = "-fx-text-fill: limegreen";
private ComposerState state;
@Override @Override
public void initialize(URL location, ResourceBundle resources) { public void initialize(URL location, ResourceBundle resources) {
tooltip.textProperty().bind(address.textProperty()); tooltip.textProperty().bind(address.textProperty());
} }
public DashboardState getState() { public ComposerState getState() {
return state; return state;
} }
public void setState(DashboardState state) { public void setState(ComposerState state) {
this.state = state; this.state = state;
this.requestType.setText(state.httpMethod); this.methodLabel.setText(state.httpMethod);
switch (state.httpMethod) {
case HTTPConstants.GET:
methodLabel.setStyle(GETStyle);
break;
case HTTPConstants.POST:
methodLabel.setStyle(POSTStyle);
break;
case HTTPConstants.PUT:
methodLabel.setStyle(PUTStyle);
break;
case HTTPConstants.PATCH:
methodLabel.setStyle(PATCHStyle);
break;
case HTTPConstants.DELETE:
methodLabel.setStyle(DELETEStyle);
break;
}
this.address.setText(state.target); this.address.setText(state.target);
} }
public int getRelativityIndex(String searchString) { public int getRelativityIndex(String searchString) {
int index = 0;
searchString = searchString.toLowerCase(); searchString = searchString.toLowerCase();
String comparisonString; String comparisonString;
// Checks if matches with target // Checks if matches with target
comparisonString = state.target.toLowerCase(); comparisonString = state.target.toLowerCase();
if (comparisonString.contains(searchString)) if (comparisonString.contains(searchString))
return 10; index += 10;
try { try {
URL url = new URL(state.target); URL url = new URL(state.target);
@ -71,71 +93,70 @@ public class HistoryItemController implements Initializable, Searchable<Dashboar
// Checks if matches with target's hostname // Checks if matches with target's hostname
comparisonString = url.getHost().toLowerCase(); comparisonString = url.getHost().toLowerCase();
if (comparisonString.contains(searchString)) if (comparisonString.contains(searchString))
return 10; index += 10;
// Checks if matches with target's path // Checks if matches with target's path
comparisonString = url.getPath().toLowerCase(); comparisonString = url.getPath().toLowerCase();
if (comparisonString.contains(searchString)) if (comparisonString.contains(searchString))
return 9; index += 9;
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
Services.loggingService.logInfo("Failed to parse URL while calculating relativity index.", LocalDateTime.now()); LoggingService.logInfo("Failed to parse URL while calculating relativity index.", LocalDateTime.now());
} }
// Checks if matches with HTTP method // Checks if matches with HTTP method
comparisonString = state.httpMethod.toLowerCase(); comparisonString = state.httpMethod.toLowerCase();
if (comparisonString.contains(searchString)) if (comparisonString.contains(searchString))
return 7; index += 7;
// Checks for a match in the params // Checks for a match in the params
for (FieldState state : state.params) { for (FieldState state : state.params) {
if (state.key.toLowerCase().contains(searchString) || if (state.key.toLowerCase().contains(searchString) ||
state.value.toLowerCase().contains(searchString)) state.value.toLowerCase().contains(searchString))
return 5; index += 7;
} }
// Checks for a match in the headers // Checks for a match in the headers
for (FieldState state : state.headers) { for (FieldState state : state.headers) {
if (state.key.toLowerCase().contains(searchString) || if (state.key.toLowerCase().contains(searchString) ||
state.value.toLowerCase().contains(searchString)) state.value.toLowerCase().contains(searchString))
return 6; index += 7;
} }
if (state.httpMethod.equals("POST") || state.httpMethod.equals("PUT")) { if (!(state.httpMethod.equals(HTTPConstants.GET) || state.httpMethod.equals(HTTPConstants.DELETE))) {
switch (state.contentType) { // Checks for match in body of the request
case MediaType.TEXT_PLAIN:
case MediaType.APPLICATION_JSON:
case MediaType.APPLICATION_XML:
case MediaType.TEXT_HTML:
case MediaType.APPLICATION_OCTET_STREAM:
// Checks for match in rawBody of the request
comparisonString = state.rawBody.toLowerCase(); comparisonString = state.rawBody.toLowerCase();
if (comparisonString.contains(searchString)) if (comparisonString.contains(searchString))
return 8; index += 8;
break;
case MediaType.APPLICATION_FORM_URLENCODED: comparisonString = state.rawBodyBoxValue.toLowerCase();
if (comparisonString.contains(searchString))
index += 6;
comparisonString = state.binaryFilePath.toLowerCase();
if (comparisonString.contains(searchString))
index += 8;
// Checks for match in string tuples // Checks for match in string tuples
for (FieldState state : state.urlStringTuples) { for (FieldState state : state.urlStringTuples) {
if (state.key.toLowerCase().contains(searchString) || if (state.key.toLowerCase().contains(searchString) ||
state.value.toLowerCase().contains(searchString)) state.value.toLowerCase().contains(searchString))
return 8; index += 8;
} }
break;
case MediaType.MULTIPART_FORM_DATA:
// Checks for match in string and file tuples // Checks for match in string and file tuples
for (FieldState state : state.formStringTuples) { for (FieldState state : state.formStringTuples) {
if (state.key.toLowerCase().contains(searchString) || if (state.key.toLowerCase().contains(searchString) ||
state.value.toLowerCase().contains(searchString)) state.value.toLowerCase().contains(searchString))
return 8; index += 8;
} }
for (FieldState state : state.formFileTuples) { for (FieldState state : state.formFileTuples) {
if (state.key.toLowerCase().contains(searchString) || if (state.key.toLowerCase().contains(searchString) ||
state.value.toLowerCase().contains(searchString)) state.value.toLowerCase().contains(searchString))
return 8; index += 8;
}
break;
} }
} }
return 0;
return index;
} }
} }

View file

@ -17,8 +17,8 @@
package com.rohitawate.everest.controllers; package com.rohitawate.everest.controllers;
import com.rohitawate.everest.controllers.search.SearchablePaneController; import com.rohitawate.everest.controllers.search.SearchablePaneController;
import com.rohitawate.everest.controllers.state.DashboardState; import com.rohitawate.everest.state.ComposerState;
import com.rohitawate.everest.misc.Services; import com.rohitawate.everest.sync.SyncManager;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.Parent; import javafx.scene.Parent;
import javafx.scene.input.MouseButton; import javafx.scene.input.MouseButton;
@ -28,16 +28,16 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
public class HistoryPaneController extends SearchablePaneController<DashboardState> { public class HistoryPaneController extends SearchablePaneController<ComposerState> {
private List<Consumer<ComposerState>> stateClickHandler = new LinkedList<>();
private List<Consumer<DashboardState>> stateClickHandler = new LinkedList<>(); private SyncManager syncManager;
@Override @Override
protected List<DashboardState> loadInitialEntries() { protected List<ComposerState> loadInitialEntries() {
return Services.historyManager.getHistory(); return syncManager.getHistory();
} }
protected SearchEntry<DashboardState> createEntryFromState(DashboardState state) throws IOException { protected SearchEntry<ComposerState> createEntryFromState(ComposerState state) throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/homewindow/HistoryItem.fxml")); FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/homewindow/HistoryItem.fxml"));
Parent historyItem = loader.load(); Parent historyItem = loader.load();
@ -53,13 +53,17 @@ public class HistoryPaneController extends SearchablePaneController<DashboardSta
return new SearchEntry<>(historyItem, controller); return new SearchEntry<>(historyItem, controller);
} }
private void handleClick(DashboardState state) { private void handleClick(ComposerState state) {
for (Consumer<DashboardState> consumer : stateClickHandler) { for (Consumer<ComposerState> consumer : stateClickHandler) {
consumer.accept(state); consumer.accept(state);
} }
} }
public void addItemClickHandler(Consumer<DashboardState> handler) { public void addItemClickHandler(Consumer<ComposerState> handler) {
stateClickHandler.add(handler); stateClickHandler.add(handler);
} }
public void setSyncManager(SyncManager syncManager) {
this.syncManager = syncManager;
}
} }

View file

@ -17,14 +17,18 @@
package com.rohitawate.everest.controllers; package com.rohitawate.everest.controllers;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.rohitawate.everest.controllers.state.DashboardState; import com.rohitawate.everest.logging.LoggingService;
import com.rohitawate.everest.misc.EverestUtilities; import com.rohitawate.everest.misc.EverestUtilities;
import com.rohitawate.everest.misc.KeyMap; import com.rohitawate.everest.misc.KeyMap;
import com.rohitawate.everest.misc.Services;
import com.rohitawate.everest.misc.ThemeManager; import com.rohitawate.everest.misc.ThemeManager;
import com.rohitawate.everest.models.requests.HTTPConstants;
import com.rohitawate.everest.state.ComposerState;
import com.rohitawate.everest.state.DashboardState;
import com.rohitawate.everest.sync.SyncManager;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.binding.Bindings; import javafx.beans.Observable;
import javafx.beans.property.StringProperty; import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
@ -47,144 +51,188 @@ import java.util.ResourceBundle;
public class HomeWindowController implements Initializable { public class HomeWindowController implements Initializable {
@FXML @FXML
private StackPane homeWindowSP; private StackPane homeWindowSP, dashboardContainer;
@FXML @FXML
private SplitPane splitPane; private SplitPane splitPane;
@FXML @FXML
private TabPane homeWindowTabPane; private TabPane tabPane;
@FXML
private HistoryPaneController historyPaneController;
private HashMap<Tab, DashboardController> tabControllerMap; private HashMap<Tab, DashboardState> tabStateMap;
private HistoryPaneController historyController;
private DashboardController dashboard;
private StringProperty addressProperty;
private SyncManager syncManager;
@Override @Override
public void initialize(URL location, ResourceBundle resources) { public void initialize(URL location, ResourceBundle resources) {
// Using LinkedHashMap because they retain order syncManager = new SyncManager(this);
tabControllerMap = new LinkedHashMap<>();
try {
FXMLLoader historyLoader = new FXMLLoader(getClass().getResource("/fxml/homewindow/HistoryPane.fxml"));
Parent historyFXML = historyLoader.load();
splitPane.getItems().add(0, historyFXML);
historyController = historyLoader.getController();
historyController.setSyncManager(syncManager);
historyController.addItemClickHandler(this::addTab);
FXMLLoader dashboardLoader = new FXMLLoader(getClass().getResource("/fxml/homewindow/Dashboard.fxml"));
Parent dashboardFXML = dashboardLoader.load();
dashboard = dashboardLoader.getController();
dashboard.setSyncManager(syncManager);
dashboardContainer.getChildren().add(dashboardFXML);
addressProperty = dashboard.addressField.textProperty();
} catch (IOException e) {
e.printStackTrace();
}
// Using LinkedHashMap because it retains order
tabStateMap = new LinkedHashMap<>();
recoverState(); recoverState();
historyPaneController.addItemClickHandler(this::addTab);
homeWindowSP.setFocusTraversable(true); homeWindowSP.setFocusTraversable(true);
Platform.runLater(() -> { Platform.runLater(() -> {
homeWindowSP.requestFocus(); homeWindowSP.requestFocus();
this.setGlobalShortcuts(); new KeymapHandler();
// Saves the state of the application before closing // Saves the state of the application before closing
Stage thisStage = (Stage) homeWindowSP.getScene().getWindow(); Stage thisStage = (Stage) homeWindowSP.getScene().getWindow();
thisStage.setOnCloseRequest(e -> saveState()); thisStage.setOnCloseRequest(e -> saveState());
}); });
tabPane.getSelectionModel().selectedItemProperty().addListener(this::onTabSwitched);
addressProperty.addListener(this::onTargetChanged);
} }
private void setGlobalShortcuts() { /**
Scene thisScene = homeWindowSP.getScene(); * Sets up the reflection of the address in the selected tab.
* Displays the current target if it is not empty, "New Tab" otherwise.
*/
private void onTargetChanged(Observable observable, String oldValue, String newValue) {
Tab activeTab = tabPane.getSelectionModel().getSelectedItem();
if (activeTab == null)
return;
thisScene.setOnKeyPressed(e -> { if (newValue.equals(""))
if (KeyMap.newTab.match(e)) { activeTab.setText("New Tab");
addTab(); else
} else if (KeyMap.focusAddressBar.match(e)) { activeTab.setText(newValue);
Tab activeTab = getActiveTab();
tabControllerMap.get(activeTab).addressField.requestFocus();
} else if (KeyMap.focusMethodBox.match(e)) {
Tab activeTab = getActiveTab();
tabControllerMap.get(activeTab).httpMethodBox.show();
} else if (KeyMap.sendRequest.match(e)) {
Tab activeTab = getActiveTab();
tabControllerMap.get(activeTab).sendRequest();
} else if (KeyMap.toggleHistory.match(e)) {
toggleHistoryPane();
} else if (KeyMap.closeTab.match(e)) {
Tab activeTab = getActiveTab();
if (homeWindowTabPane.getTabs().size() == 1)
addTab();
homeWindowTabPane.getTabs().remove(activeTab);
tabControllerMap.remove(activeTab);
} else if (KeyMap.searchHistory.match(e)) {
historyPaneController.focusSearchField();
} else if (KeyMap.focusParams.match(e)) {
Tab activeTab = getActiveTab();
DashboardController controller = tabControllerMap.get(activeTab);
controller.requestOptionsTab.getSelectionModel().select(controller.paramsTab);
} else if (KeyMap.focusAuth.match(e)) {
Tab activeTab = getActiveTab();
DashboardController controller = tabControllerMap.get(activeTab);
controller.requestOptionsTab.getSelectionModel().select(controller.authTab);
} else if (KeyMap.focusHeaders.match(e)) {
Tab activeTab = getActiveTab();
DashboardController controller = tabControllerMap.get(activeTab);
controller.requestOptionsTab.getSelectionModel().select(controller.headersTab);
} else if (KeyMap.focusBody.match(e)) {
Tab activeTab = getActiveTab();
DashboardController controller = tabControllerMap.get(activeTab);
String httpMethod = controller.httpMethodBox.getValue();
if (!httpMethod.equals("GET") && !httpMethod.equals("DELETE")) {
controller.requestOptionsTab.getSelectionModel().select(controller.bodyTab);
}
} else if (KeyMap.refreshTheme.match(e)) {
ThemeManager.refreshTheme();
}
});
} }
private Tab getActiveTab() { /**
return homeWindowTabPane.getSelectionModel().getSelectedItem(); * Updates the current state of the Dashboard in the tabStateMap
* corresponding to the previously selected tab. (calls DashboardController.getState())
* Fetches the state of the new tab from tabStateMap and applies it to the Dashboard.
*
* @param prevTab The tab that was selected before the switch.
* @param newTab The tab that must be selected after the switch.
*/
private void onTabSwitched(ObservableValue<? extends Tab> obs, Tab prevTab, Tab newTab) {
DashboardState dashboardState = dashboard.getState();
tabStateMap.replace(prevTab, dashboardState);
dashboardState = tabStateMap.get(newTab);
dashboard.reset();
dashboard.setState(dashboardState);
} }
private void toggleHistoryPane() { /**
historyPaneController.toggleVisibilityIn(splitPane); * Updates the current state of the Dashboard in the tabStateMap
* corresponding to the previously selected tab.
* Fetches the state of the new tab from tabStateMap and applies it to the Dashboard.
*
* @param prevState The state of the Dashboard before the switch.
* @param prevTab The tab that was selected before the switch.
* @param newTab The tab that must be selected after the switch.
*/
private void onTabSwitched(DashboardState prevState, Tab prevTab, Tab newTab) {
tabStateMap.replace(prevTab, prevState);
DashboardState newState = tabStateMap.get(newTab);
dashboard.reset();
dashboard.setState(newState);
} }
@FXML
private void addTab() { private void addTab() {
addTab(null); addTab(new ComposerState());
} }
private void addTab(DashboardState dashboardState) { /**
try { * Adds a new tab to the tabPane initialized with
* the ComposerState provided.
*/
private void addTab(ComposerState composerState) {
Tab newTab = new Tab(); Tab newTab = new Tab();
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/homewindow/Dashboard.fxml"));
Parent dashboard = loader.load();
DashboardController controller = loader.getController();
if (dashboardState != null) /*
controller.setState(dashboardState); Initializing the tab text based on the target in the ComposerState.
Further handling of the tab text is done by onTargetChanged().
*/
String target = composerState.target;
if (target == null || target.equals(""))
newTab.setText("New Tab");
else
newTab.setText(target);
newTab.setContent(dashboard); DashboardState newState = new DashboardState(composerState);
tabStateMap.put(newTab, newState);
// Binds the addressField text to the Tab text /*
StringProperty addressProperty = controller.addressField.textProperty(); DO NOT mess with the following code. The sequence of these steps is very crucial:
newTab.textProperty().bind( 1. Get the currently selected tab.
Bindings.when(addressProperty.isNotEmpty()) 2. Get the current state of the dashboard to save to the map.
.then(addressProperty) 3. Add the new tab, since the previous state is now with us.
.otherwise("New Tab")); 4. Switch to the new tab.
5. Call onTabSwitched() to update the Dashboard and save the oldState.
*/
Tab prevTab = tabPane.getSelectionModel().getSelectedItem();
DashboardState prevState = dashboard.getState();
tabPane.getTabs().add(newTab);
tabPane.getSelectionModel().select(newTab);
onTabSwitched(prevState, prevTab, newTab);
// Tab closing procedure
newTab.setOnCloseRequest(e -> { newTab.setOnCloseRequest(e -> {
if (homeWindowTabPane.getTabs().size() == 1) removeTab(newTab);
addTab();
tabControllerMap.remove(newTab);
});
homeWindowTabPane.getTabs().add(newTab); // Closes the application if the last tab is closed
homeWindowTabPane.getSelectionModel().select(newTab); if (tabPane.getTabs().size() == 0) {
tabControllerMap.put(newTab, controller); saveState();
} catch (IOException e) { Stage thisStage = (Stage) homeWindowSP.getScene().getWindow();
Services.loggingService.logSevere("Could not add a new tab.", e, LocalDateTime.now()); thisStage.close();
} }
});
}
private void removeTab(Tab newTab) {
DashboardState state = tabStateMap.remove(newTab);
state = null;
tabPane.getTabs().remove(newTab);
newTab.setOnCloseRequest(null);
newTab = null;
} }
private void saveState() { private void saveState() {
ArrayList<DashboardState> dashboardStates = new ArrayList<>(); /*
Updating the state of the selected tab before saving it.
Other tabs will already have their states saved when they
were loaded from state.json or on a tab switch.
*/
Tab currentTab = tabPane.getSelectionModel().getSelectedItem();
DashboardState currentState = dashboard.getState();
tabStateMap.put(currentTab, currentState);
// Get the states of all the tabs ArrayList<ComposerState> composerStates = new ArrayList<>();
for (DashboardController controller : tabControllerMap.values()) for (DashboardState dashboardState : tabStateMap.values())
dashboardStates.add(controller.getState()); composerStates.add(dashboardState.composer);
try { try {
File stateFile = new File("Everest/config/state.json"); File stateFile = new File("Everest/config/state.json");
EverestUtilities.jsonMapper.writeValue(stateFile, dashboardStates); EverestUtilities.jsonMapper.writeValue(stateFile, composerStates);
Services.loggingService.logInfo("Application state saved.", LocalDateTime.now()); LoggingService.logInfo("Application state saved.", LocalDateTime.now());
} catch (IOException e) { } catch (IOException e) {
Services.loggingService.logSevere("Failed to save application state.", e, LocalDateTime.now()); LoggingService.logSevere("Failed to save application state.", e, LocalDateTime.now());
} }
} }
@ -193,31 +241,81 @@ public class HomeWindowController implements Initializable {
File stateFile = new File("Everest/config/state.json"); File stateFile = new File("Everest/config/state.json");
if (!stateFile.exists()) { if (!stateFile.exists()) {
Services.loggingService.logInfo("Application state file not found. Loading default state.", LocalDateTime.now()); LoggingService.logInfo("Application state file not found. Loading default state.", LocalDateTime.now());
addTab(); addTab();
return; return;
} }
ArrayList<DashboardState> dashboardStates = EverestUtilities.jsonMapper ArrayList<ComposerState> composerStates = EverestUtilities.jsonMapper
.reader() .reader()
.forType(new TypeReference<ArrayList<DashboardState>>() { .forType(new TypeReference<ArrayList<ComposerState>>() {
}) })
.readValue(stateFile); .readValue(stateFile);
if (dashboardStates.size() > 0) { if (composerStates.size() > 0) {
for (DashboardState dashboardState : dashboardStates) for (ComposerState composerState : composerStates)
addTab(dashboardState); addTab(composerState);
} else { } else {
addTab(); addTab();
} }
} catch (IOException e) { } catch (IOException e) {
Services.loggingService.logWarning("Application state file is possibly corrupted. State recovery failed. Loading default state.", e, LocalDateTime.now()); LoggingService.logWarning("Application state file is either corrupted or outdated. State recovery failed. Loading default state.", e, LocalDateTime.now());
addTab();
} finally { } finally {
Services.loggingService.logInfo("Application loaded.", LocalDateTime.now()); LoggingService.logInfo("Application loaded.", LocalDateTime.now());
} }
} }
public void addHistoryItem(DashboardState state) { public void addHistoryItem(ComposerState state) {
historyPaneController.addHistoryItem(state); historyController.addHistoryItem(state);
}
private void toggleHistoryPane() {
historyController.toggleVisibilityIn(splitPane);
}
private class KeymapHandler {
private KeymapHandler() {
Scene thisScene = homeWindowSP.getScene();
thisScene.setOnKeyPressed(e -> {
if (KeyMap.newTab.match(e)) {
addTab();
} else if (KeyMap.focusAddressBar.match(e)) {
dashboard.addressField.requestFocus();
} else if (KeyMap.focusMethodBox.match(e)) {
dashboard.httpMethodBox.show();
} else if (KeyMap.sendRequest.match(e)) {
dashboard.sendRequest();
} else if (KeyMap.toggleHistory.match(e)) {
toggleHistoryPane();
} else if (KeyMap.closeTab.match(e)) {
Tab activeTab = tabPane.getSelectionModel().getSelectedItem();
tabStateMap.remove(activeTab);
tabPane.getTabs().remove(activeTab);
if (tabPane.getTabs().size() == 0) {
saveState();
Stage thisStage = (Stage) homeWindowSP.getScene().getWindow();
thisStage.close();
}
tabPane.getTabs().remove(activeTab);
} else if (KeyMap.searchHistory.match(e)) {
historyController.focusSearchField();
} else if (KeyMap.focusParams.match(e)) {
dashboard.requestOptionsTab.getSelectionModel().select(dashboard.paramsTab);
} else if (KeyMap.focusAuth.match(e)) {
dashboard.requestOptionsTab.getSelectionModel().select(dashboard.authTab);
} else if (KeyMap.focusHeaders.match(e)) {
dashboard.requestOptionsTab.getSelectionModel().select(dashboard.headersTab);
} else if (KeyMap.focusBody.match(e)) {
String httpMethod = dashboard.httpMethodBox.getValue();
if (!httpMethod.equals(HTTPConstants.GET) && !httpMethod.equals(HTTPConstants.DELETE)) {
dashboard.requestOptionsTab.getSelectionModel().select(dashboard.bodyTab);
}
} else if (KeyMap.refreshTheme.match(e)) {
ThemeManager.refreshTheme();
}
});
}
} }
} }

View file

@ -23,8 +23,15 @@ import javafx.scene.control.ScrollPane;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
class ResponseHeadersViewer extends ScrollPane { import java.util.HashMap;
public class ResponseHeadersViewer extends ScrollPane {
private VBox container; private VBox container;
private HashMap<String, String> map;
private static final String responseHeaderLabel = "response-header-label";
private static final String keyLabelStyleClass = "response-header-key-label";
private static final String valueLabelStyleClass = "response-header-value-label";
ResponseHeadersViewer() { ResponseHeadersViewer() {
this.container = new VBox(); this.container = new VBox();
@ -33,19 +40,38 @@ class ResponseHeadersViewer extends ScrollPane {
this.setFitToHeight(true); this.setFitToHeight(true);
this.setFitToWidth(true); this.setFitToWidth(true);
map = new HashMap<>();
}
void populate(HashMap<String, String> headers) {
map.clear();
headers.forEach((key, value) -> map.put(key, value));
populate();
} }
void populate(EverestResponse response) { void populate(EverestResponse response) {
map.clear();
response.getHeaders().forEach((key, value) -> map.put(key, value.get(0)));
populate();
}
private void populate() {
container.getChildren().clear(); container.getChildren().clear();
response.getHeaders().forEach((key, value) -> { map.forEach((key, value) -> {
Label keyLabel = new Label(key + ": "); Label keyLabel = new Label(key + ": ");
keyLabel.getStyleClass().addAll("visualizerKeyLabel", "visualizerLabel"); keyLabel.getStyleClass().addAll(keyLabelStyleClass, responseHeaderLabel);
Label valueLabel = new Label(value.get(0)); Label valueLabel = new Label(value);
valueLabel.getStyleClass().addAll("visualizerValueLabel", "visualizerLabel"); valueLabel.getStyleClass().addAll(valueLabelStyleClass, responseHeaderLabel);
container.getChildren().add(new HBox(keyLabel, valueLabel)); container.getChildren().add(new HBox(keyLabel, valueLabel));
}); });
}
public HashMap<String, String> getHeaders() {
return new HashMap<>(map);
} }
} }

View file

@ -18,11 +18,15 @@ package com.rohitawate.everest.controllers;
import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXCheckBox; import com.jfoenix.controls.JFXCheckBox;
import com.rohitawate.everest.controllers.state.FieldState; import com.rohitawate.everest.state.FieldState;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.StringProperty;
import javafx.event.EventHandler;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.util.Pair; import javafx.util.Pair;
import java.net.URL; import java.net.URL;
@ -100,4 +104,21 @@ public class StringKeyValueFieldController implements Initializable {
public void setChecked(boolean checked) { public void setChecked(boolean checked) {
checkBox.setSelected(checked); checkBox.setSelected(checked);
} }
public void setKeyHandler(EventHandler<KeyEvent> handler) {
keyField.setOnKeyPressed(handler);
valueField.setOnKeyPressed(handler);
}
public BooleanProperty getSelectedProperty() {
return checkBox.selectedProperty();
}
public StringProperty getKeyProperty() {
return keyField.textProperty();
}
public StringProperty getValueProperty() {
return valueField.textProperty();
}
} }

View file

@ -16,13 +16,12 @@
package com.rohitawate.everest.controllers; package com.rohitawate.everest.controllers;
import com.rohitawate.everest.controllers.state.FieldState; import com.rohitawate.everest.logging.LoggingService;
import com.rohitawate.everest.misc.Services;
import com.rohitawate.everest.misc.ThemeManager; import com.rohitawate.everest.misc.ThemeManager;
import com.rohitawate.everest.state.FieldState;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty; import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleIntegerProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
@ -54,32 +53,36 @@ public class URLTabController implements Initializable {
public void addField(FieldState state) { public void addField(FieldState state) {
addField(state.key, state.value, null, state.checked); addField(state.key, state.value, state.checked);
} }
private void addField() { private void addField() {
addField("", "", null, false); addField("", "", false);
} }
@FXML private void addField(String key, String value, boolean checked) {
private void addField(ActionEvent event) {
addField("", "", event, false);
}
private void addField(String key, String value, ActionEvent event, boolean checked) {
/* /*
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.
A value of null for the 'event' parameter indicates that the method call A value of null for the 'event' parameter indicates that the method call
came from code and not from the user. This call is made while recovering came from code and not from the user. This call is made while recovering
the application state. the application state.
*/ */
if (controllers.size() > 0 && event == null) { if (controllers.size() > 0) {
StringKeyValueFieldController previousController = controllers.get(controllers.size() - 1); StringKeyValueFieldController previousController = controllers.get(controllers.size() - 1);
if (previousController.isKeyFieldEmpty() && if (previousController.isKeyFieldEmpty() &&
previousController.isValueFieldEmpty()) { previousController.isValueFieldEmpty()) {
previousController.setKeyField(key); previousController.setKeyField(key);
previousController.setValueField(value); previousController.setValueField(value);
previousController.setChecked(checked);
/*
For when the last field is loaded from setState.
This makes sure an extra blank field is always present.
*/
if (!key.equals("") && !value.equals(""))
addField();
return; return;
} }
} }
@ -98,11 +101,12 @@ public class URLTabController implements Initializable {
controller.deleteButton.setOnAction(e -> { controller.deleteButton.setOnAction(e -> {
fieldsBox.getChildren().remove(stringField); fieldsBox.getChildren().remove(stringField);
controllers.remove(controller); controllers.remove(controller);
controllersCount.set(controllersCount.get() + 1); controllersCount.set(controllersCount.get() - 1);
}); });
controller.setKeyHandler(keyEvent -> addField());
fieldsBox.getChildren().add(stringField); fieldsBox.getChildren().add(stringField);
} catch (IOException e) { } catch (IOException e) {
Services.loggingService.logSevere("Could not add string field.", e, LocalDateTime.now()); LoggingService.logSevere("Could not add string field.", e, LocalDateTime.now());
} }
} }
@ -129,10 +133,22 @@ public class URLTabController implements Initializable {
public ArrayList<FieldState> getFieldStates() { public ArrayList<FieldState> getFieldStates() {
ArrayList<FieldState> states = new ArrayList<>(); ArrayList<FieldState> states = new ArrayList<>();
for (StringKeyValueFieldController controller : controllers) for (StringKeyValueFieldController controller : controllers) {
if (!controller.isKeyFieldEmpty() && !controller.isValueFieldEmpty()) if (!controller.isKeyFieldEmpty() && !controller.isValueFieldEmpty())
states.add(controller.getState()); states.add(controller.getState());
}
return states; return states;
} }
void clear() {
if (tuples != null)
tuples.clear();
if (controllers != null)
controllers.clear();
fieldsBox.getChildren().clear();
controllersCount.set(0);
addField();
}
} }

View file

@ -1,119 +0,0 @@
/*
* 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.*;
import javafx.scene.layout.HBox;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
class Visualizer extends ScrollPane {
private TreeView<HBox> visualizer;
Visualizer() {
this.visualizer = new TreeView<>();
this.visualizer.setShowRoot(false);
this.setContent(this.visualizer);
this.setFitToHeight(true);
this.setFitToWidth(true);
}
void populate(JsonNode node) {
this.populate(new TreeItem<>(), "root", node);
}
private void populate(TreeItem<HBox> 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));
JsonNode currentNode;
Label valueLabel;
HBox valueContainer;
List<TreeItem<HBox>> items = new LinkedList<>();
Tooltip valueTooltip;
if (root.isArray()) {
Iterator<JsonNode> iterator = root.elements();
int i = 0;
while (iterator.hasNext()) {
currentNode = iterator.next();
if (currentNode.isValueNode()) {
valueLabel = new Label(i++ + ": " + 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<HBox> newRoot = new TreeItem<>();
items.add(newRoot);
populate(newRoot, i++ + ": [Anonymous Object]", currentNode);
}
}
} else {
Iterator<Map.Entry<String, JsonNode>> iterator = root.fields();
Map.Entry<String, JsonNode> 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<HBox> newRoot = new TreeItem<>();
items.add(newRoot);
populate(newRoot, currentEntry.getKey(), currentNode);
}
}
}
rootItem.getChildren().addAll(items);
}
public void clear() {
this.visualizer.setRoot(null);
}
}

View file

@ -17,63 +17,68 @@
package com.rohitawate.everest.controllers.codearea; package com.rohitawate.everest.controllers.codearea;
import com.rohitawate.everest.controllers.codearea.highlighters.Highlighter; import com.rohitawate.everest.controllers.codearea.highlighters.Highlighter;
import com.rohitawate.everest.controllers.codearea.highlighters.JSONHighlighter; import com.rohitawate.everest.format.Formatter;
import com.rohitawate.everest.controllers.codearea.highlighters.PlaintextHighlighter;
import com.rohitawate.everest.controllers.codearea.highlighters.XMLHighlighter;
import com.rohitawate.everest.settings.Settings; import com.rohitawate.everest.settings.Settings;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import org.fxmisc.richtext.CodeArea; import org.fxmisc.richtext.CodeArea;
import java.io.IOException;
import java.time.Duration; import java.time.Duration;
public class EverestCodeArea extends CodeArea { public class EverestCodeArea extends CodeArea {
public enum HighlightMode {
JSON, XML, HTML, PLAIN
}
private Highlighter highlighter; private Highlighter highlighter;
private static JSONHighlighter jsonHighlighter;
private static XMLHighlighter xmlHighlighter;
private static PlaintextHighlighter plaintextHighlighter;
public EverestCodeArea() { public EverestCodeArea() {
this.getStylesheets().add(getClass().getResource("/css/syntax/Moondust.css").toString()); this.getStylesheets().add(getClass().getResource("/css/syntax/Moondust.css").toString());
this.getStyleClass().add("everest-code-area"); this.getStyleClass().add("everest-code-area");
this.setWrapText(Settings.editorWrapText); this.setWrapText(Settings.editorWrapText);
this.setPadding(new Insets(5)); this.setPadding(new Insets(5));
jsonHighlighter = new JSONHighlighter();
xmlHighlighter = new XMLHighlighter();
plaintextHighlighter = new PlaintextHighlighter();
setMode(HighlightMode.PLAIN);
this.multiPlainChanges() this.multiPlainChanges()
.successionEnds(Duration.ofMillis(1)) .successionEnds(Duration.ofMillis(1))
.subscribe(ignore -> this.setStyleSpans(0, highlighter.computeHighlighting(getText()))); .subscribe(ignore -> highlight());
} }
public void setMode(HighlightMode mode) { private void highlight() {
switch (mode) {
case JSON:
highlighter = jsonHighlighter;
break;
case XML:
case HTML:
highlighter = xmlHighlighter;
break;
default:
highlighter = plaintextHighlighter;
}
// Re-computes the highlighting for the new mode
this.setStyleSpans(0, highlighter.computeHighlighting(getText())); this.setStyleSpans(0, highlighter.computeHighlighting(getText()));
} }
public void setText(String text, HighlightMode mode) { public void setHighlighter(Highlighter highlighter) {
this.highlighter = highlighter;
// Re-computes the highlighting using the new Highlighter
this.highlight();
}
/**
* Sets the text and then computes the highlighting.
*/
public void setText(String text, Highlighter highlighter) {
clear(); clear();
appendText(text); appendText(text);
setMode(mode); setHighlighter(highlighter);
}
/**
* Formats the text with the provided Formatter if it is not null,
* sets the text and then computes the highlighting.
*
*/
public void setText(String text, Formatter formatter, Highlighter highlighter) {
clear();
String formattedText = text;
if (formatter != null) {
try {
formattedText = formatter.format(text);
} catch (IOException e) {
clear();
appendText(text);
return;
}
}
appendText(formattedText);
setHighlighter(highlighter);
} }
} }

View file

@ -20,6 +20,15 @@ import org.fxmisc.richtext.model.StyleSpans;
import java.util.Collection; import java.util.Collection;
/**
* Highlights strings in various data formats.
*/
public interface Highlighter { public interface Highlighter {
/**
* Returns the StyleSpans needed for highlighting the given text which
* can then be used by EverestCodeArea.
*
* @param text The string to highlight
*/
StyleSpans<Collection<String>> computeHighlighting(String text); StyleSpans<Collection<String>> computeHighlighting(String text);
} }

View file

@ -0,0 +1,48 @@
package com.rohitawate.everest.controllers.codearea.highlighters;
import com.rohitawate.everest.exceptions.DuplicateException;
import com.rohitawate.everest.models.requests.HTTPConstants;
import java.util.HashMap;
/**
* Provides Highlighters for computing syntax highlighting
* of specific data formats.
* <p>
* Everest, by default, comes with Highlighters for JSON, XML (HTML uses the same) and PLAIN TEXT.
*/
public class HighlighterFactory {
private static HashMap<String, Highlighter> highlighters;
static {
highlighters = new HashMap<>();
highlighters.put(HTTPConstants.JSON, new JSONHighlighter());
XMLHighlighter xmlHighlighter = new XMLHighlighter();
highlighters.put(HTTPConstants.XML, xmlHighlighter);
highlighters.put(HTTPConstants.HTML, xmlHighlighter);
highlighters.put(HTTPConstants.PLAIN_TEXT, new PlaintextHighlighter());
}
public static Highlighter getHighlighter(String name) {
return highlighters.get(name);
}
/**
* Provisional method for the future Extension API which will enable
* developers to write and load custom Highlighters.
*
* @param name The display name for the Highlighter.
* @param highlighter The Highlighter object
* @throws DuplicateException If a Highlighter is already loaded by Everest with the same name.
*/
public static void addHighlighter(String name, Highlighter highlighter)
throws DuplicateException {
if (highlighters.containsKey(name)) {
throw new DuplicateException("Highlighter already exists for the following type: " + name);
}
highlighters.put(name, highlighter);
}
}

View file

@ -17,6 +17,7 @@
package com.rohitawate.everest.controllers.search; package com.rohitawate.everest.controllers.search;
import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXButton;
import com.rohitawate.everest.logging.LoggingService;
import com.rohitawate.everest.misc.Services; import com.rohitawate.everest.misc.Services;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.concurrent.Task; import javafx.concurrent.Task;
@ -116,12 +117,12 @@ public abstract class SearchablePaneController<T> implements Initializable {
for (T state : entries) for (T state : entries)
addHistoryItem(state); addHistoryItem(state);
} catch (InterruptedException | ExecutionException E) { } catch (InterruptedException | ExecutionException E) {
Services.loggingService.logSevere("Task thread interrupted while populating HistoryTab.", E, LoggingService.logSevere("Task thread interrupted while populating HistoryTab.", E,
LocalDateTime.now()); LocalDateTime.now());
} }
}); });
entryLoader.setOnFailed(e -> Services.loggingService.logWarning("Failed to load history.", entryLoader.setOnFailed(e -> LoggingService.logWarning("Failed to load history.",
(Exception) entryLoader.getException(), LocalDateTime.now())); (Exception) entryLoader.getException(), LocalDateTime.now()));
Services.singleExecutor.execute(entryLoader); Services.singleExecutor.execute(entryLoader);
@ -149,7 +150,7 @@ public abstract class SearchablePaneController<T> implements Initializable {
return searchEntry.getSearchable(); return searchEntry.getSearchable();
} catch (IOException e) { } catch (IOException e) {
Services.loggingService.logSevere("Could not append HistoryItem to list.", e, LocalDateTime.now()); LoggingService.logSevere("Could not append HistoryItem to list.", e, LocalDateTime.now());
} }
return null; return null;

View file

@ -1,46 +0,0 @@
/*
* 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.state;
import java.util.ArrayList;
/**
* Convenience class to abstract the state of the application.
*/
public class DashboardState {
public String target;
public String httpMethod;
public ArrayList<FieldState> params;
public ArrayList<FieldState> headers;
// Determined from the active tab within the Body tab
public String contentType;
// Body and content-type of requests with raw bodies
public String rawBody;
public String rawBodyType;
// Tuples of URL-encoded requests
public ArrayList<FieldState> urlStringTuples;
// String and file tuples of multipart-form requests
public ArrayList<FieldState> formStringTuples;
public ArrayList<FieldState> formFileTuples;
// File path of application/octet-stream requests
public String binaryFilePath;
}

View file

@ -0,0 +1,117 @@
/*
* 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.visualizers;
import com.fasterxml.jackson.databind.JsonNode;
import com.rohitawate.everest.misc.EverestUtilities;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class TreeVisualizer extends Visualizer {
private TreeView<String> visualizer;
private ArrayList<TreeItem<String>> recycledNodes;
private int lastNodeIndex;
public TreeVisualizer() {
visualizer = new TreeView<>();
recycledNodes = new ArrayList<>();
visualizer.setShowRoot(false);
visualizer.setCache(true);
setContent(visualizer);
}
public void populate(String body) throws IOException {
JsonNode tree = EverestUtilities.jsonMapper.readTree(body);
this.populate(getTreeNode(null), "root", tree);
}
private void populate(TreeItem<String> rootItem, String rootName, JsonNode root) {
if (rootName.equals("root")) {
visualizer.setRoot(rootItem);
}
rootItem.setValue(rootName);
JsonNode currentNode;
List<TreeItem<String>> items = new ArrayList<>();
if (root.isArray()) {
Iterator<JsonNode> iterator = root.elements();
int i = 0;
while (iterator.hasNext()) {
currentNode = iterator.next();
if (currentNode.isValueNode()) {
items.add(getTreeNode(i++ + ": " + EverestUtilities.trimString(currentNode.toString())));
} else if (currentNode.isObject()) {
TreeItem<String> newRoot = getTreeNode(null);
newRoot.setExpanded(true);
items.add(newRoot);
populate(newRoot, i++ + ": [Anonymous Object]", currentNode);
}
}
} else {
Iterator<Map.Entry<String, JsonNode>> iterator = root.fields();
Map.Entry<String, JsonNode> currentEntry;
while (iterator.hasNext()) {
currentEntry = iterator.next();
currentNode = currentEntry.getValue();
if (currentNode.isValueNode()) {
items.add(getTreeNode(currentEntry.getKey() + ": "
+ EverestUtilities.trimString(currentNode.toString())));
} else if (currentNode.isArray() || currentNode.isObject()) {
TreeItem<String> newRoot = getTreeNode(null);
newRoot.setExpanded(true);
items.add(newRoot);
populate(newRoot, currentEntry.getKey(), currentNode);
}
}
}
rootItem.getChildren().addAll(items);
}
private TreeItem<String> getTreeNode(String value) {
TreeItem<String> node;
if (recycledNodes.size() > 0) {
node = recycledNodes.get(lastNodeIndex);
recycledNodes.remove(lastNodeIndex--);
} else {
node = new TreeItem<>();
}
node.setValue(value);
node.getChildren().clear();
return node;
}
public void clear() {
recycledNodes.addAll(visualizer.getRoot().getChildren());
visualizer.setRoot(null);
System.gc();
}
}

View file

@ -0,0 +1,15 @@
package com.rohitawate.everest.controllers.visualizers;
import javafx.scene.control.ScrollPane;
public abstract class Visualizer extends ScrollPane {
Visualizer() {
setFitToHeight(true);
setFitToWidth(true);
}
public abstract void populate(String body) throws Exception;
public abstract void clear();
}

View file

@ -0,0 +1,7 @@
package com.rohitawate.everest.exceptions;
public class DuplicateException extends Exception {
public DuplicateException(String message) {
super(message);
}
}

View file

@ -21,11 +21,11 @@ package com.rohitawate.everest.exceptions;
* <p> * <p>
* Used by DashboardController to display ErrorLayer. * Used by DashboardController to display ErrorLayer.
*/ */
public class UnreliableResponseException extends Exception { public class NullResponseException extends Exception {
private String exceptionTitle; private String exceptionTitle;
private String exceptionDetails; private String exceptionDetails;
public UnreliableResponseException(String exceptionTitle, String exceptionDetails) { public NullResponseException(String exceptionTitle, String exceptionDetails) {
this.exceptionTitle = exceptionTitle; this.exceptionTitle = exceptionTitle;
this.exceptionDetails = exceptionDetails; this.exceptionDetails = exceptionDetails;
} }

View file

@ -0,0 +1,16 @@
package com.rohitawate.everest.format;
import java.io.IOException;
/**
* Formats strings in various data formats.
*/
public interface Formatter {
/**
* Returns a string formatted appropriate to the data format.
*
* @param unformatted The unformatted string
* @throws IOException If the Formatter fails to format the given string.
*/
String format(String unformatted) throws IOException;
}

View file

@ -0,0 +1,42 @@
package com.rohitawate.everest.format;
import com.rohitawate.everest.exceptions.DuplicateException;
import com.rohitawate.everest.models.requests.HTTPConstants;
import java.util.HashMap;
/**
* Provides Formatters for formatting strings of specific data formats.
* <p>
* Everest, by default, comes with a Formatter for JSON.
*/
public class FormatterFactory {
private static HashMap<String, Formatter> formatters;
static {
formatters = new HashMap<>();
formatters.put(HTTPConstants.JSON, new JSONFormatter());
}
public static Formatter getHighlighter(String type) {
return formatters.get(type);
}
/**
* Provisional method for the future Extension API which will enable
* developers to write and load custom Formatters.
*
* @param name The display name for the Formatter.
* @param formatter The Formatter object.
* @throws DuplicateException If a Formatter is already loaded by Everest with the same name.
*/
public static void addFormatter(String name, Formatter formatter)
throws DuplicateException {
if (formatters.containsKey(name)) {
throw new DuplicateException("Formatter already exists for the following type: " + name);
}
formatters.put(name, formatter);
}
}

View file

@ -0,0 +1,30 @@
package com.rohitawate.everest.format;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.io.IOException;
/**
* Formats JSON strings using Jackson's ObjectMapper.
*/
public class JSONFormatter implements Formatter {
private static ObjectMapper mapper;
JSONFormatter() {
mapper = new ObjectMapper();
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
}
@Override
public String format(String unformatted) throws IOException {
JsonNode tree = mapper.readTree(unformatted);
return mapper.writeValueAsString(tree);
}
@Override
public String toString() {
return this.getClass().getCanonicalName();
}
}

View file

@ -1,443 +0,0 @@
/*
* 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.history;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.rohitawate.everest.controllers.state.DashboardState;
import com.rohitawate.everest.controllers.state.FieldState;
import com.rohitawate.everest.misc.EverestUtilities;
import com.rohitawate.everest.misc.Services;
import com.rohitawate.everest.settings.Settings;
import javax.ws.rs.core.MediaType;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
public class HistoryManager {
private Connection conn;
private JsonNode queries;
private PreparedStatement statement;
private HistorySaver historySaver = new HistorySaver();
public HistoryManager() {
try {
File configFolder = new File("Everest/config/");
if (!configFolder.exists())
configFolder.mkdirs();
conn = DriverManager.getConnection("jdbc:sqlite:Everest/config/history.sqlite");
initDatabase();
} catch (Exception E) {
Services.loggingService.logSevere("Exception while initializing HistoryManager.", E, LocalDateTime.now());
} finally {
System.out.println("Connected to database.");
}
}
/**
* Creates and initializes the database with necessary tables if not already done.
*
* @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
InputStream queriesFile = getClass().getResourceAsStream("/sql/Queries.json");
ObjectMapper mapper = new ObjectMapper();
queries = mapper.readTree(queriesFile);
statement =
conn.prepareStatement(EverestUtilities.trimString(queries.get("createRequestsTable").toString()));
statement.execute();
statement =
conn.prepareStatement(EverestUtilities.trimString(queries.get("createHeadersTable").toString()));
statement.execute();
statement =
conn.prepareStatement(EverestUtilities.trimString(queries.get("createRequestContentMapTable").toString()));
statement.execute();
statement =
conn.prepareStatement(EverestUtilities.trimString(queries.get("createBodiesTable").toString()));
statement.execute();
statement =
conn.prepareStatement(EverestUtilities.trimString(queries.get("createTuplesTable").toString()));
statement.execute();
}
// Method is made synchronized to allow only one database transaction at a time.
/**
* Saves the request to the database if it is not identical to one made exactly before it.
*
* @param state - The state of the Dashboard while making the request.
*/
public synchronized void saveHistory(DashboardState state) {
if (isDuplicate(state))
return;
historySaver.state = state;
Services.singleExecutor.execute(historySaver);
// Appends this history item to the HistoryTab
Services.homeWindowController.addHistoryItem(state);
}
/**
* Returns a list of all the recent requests.
*/
public synchronized List<DashboardState> getHistory() {
List<DashboardState> history = new ArrayList<>();
try {
// Loads the requests from the last x number of days, x being Settings.showHistoryRange
statement = conn.prepareStatement(EverestUtilities.trimString(queries.get("selectRecentRequests").toString()));
String historyStartDate = LocalDate.now().minusDays(Settings.showHistoryRange).toString();
statement.setString(1, historyStartDate);
ResultSet resultSet = statement.executeQuery();
DashboardState state;
while (resultSet.next()) {
state = new DashboardState();
state.target = resultSet.getString("Target");
int requestID = resultSet.getInt("ID");
state.headers = getRequestHeaders(requestID);
state.params = getTuples(requestID, "Param");
state.httpMethod = resultSet.getString("Type");
if (!(state.httpMethod.equals("GET") || state.httpMethod.equals("DELETE"))) {
// Retrieves request body ContentType for querying corresponding table
statement = conn.prepareStatement(EverestUtilities.trimString(queries.get("selectRequestContentType").toString()));
statement.setInt(1, requestID);
ResultSet RS = statement.executeQuery();
String contentType = "";
if (RS.next())
contentType = RS.getString("ContentType");
state.contentType = contentType;
// Retrieves body from corresponding table
switch (contentType) {
case MediaType.TEXT_PLAIN:
case MediaType.APPLICATION_JSON:
case MediaType.APPLICATION_XML:
case MediaType.TEXT_HTML:
case MediaType.APPLICATION_OCTET_STREAM:
statement = conn.prepareStatement(EverestUtilities.trimString(queries.get("selectRequestBody").toString()));
statement.setInt(1, requestID);
RS = statement.executeQuery();
if (RS.next())
state.rawBody = RS.getString("Body");
break;
case MediaType.APPLICATION_FORM_URLENCODED:
state.urlStringTuples = getTuples(requestID, "String");
break;
case MediaType.MULTIPART_FORM_DATA:
state.formStringTuples = getTuples(requestID, "String");
state.formFileTuples = getTuples(requestID, "File");
break;
}
}
history.add(state);
}
} catch (SQLException e) {
Services.loggingService.logWarning("Database error.", e, LocalDateTime.now());
}
return history;
}
private ArrayList<FieldState> getRequestHeaders(int requestID) {
ArrayList<FieldState> headers = new ArrayList<>();
try {
PreparedStatement statement =
conn.prepareStatement(EverestUtilities.trimString(queries.get("selectRequestHeaders").toString()));
statement.setInt(1, requestID);
ResultSet RS = statement.executeQuery();
String key, value;
boolean checked;
while (RS.next()) {
key = RS.getString("Key");
value = RS.getString("Value");
checked = RS.getBoolean("Checked");
headers.add(new FieldState(key, value, checked));
}
} catch (SQLException e) {
Services.loggingService.logWarning("Database error.", e, LocalDateTime.now());
}
return headers;
}
/**
* @param requestID Database ID of the request whose tuples are needed.
* @param type Type of tuples needed ('String', 'File' or 'Param')
* @return tuples - Map of tuples of corresponding type
*/
private ArrayList<FieldState> getTuples(int requestID, String type) {
if (!(type.equals("String") || type.equals("File") || type.equals("Param")))
return null;
ArrayList<FieldState> tuples = new ArrayList<>();
try {
PreparedStatement statement =
conn.prepareStatement(EverestUtilities.trimString(queries.get("selectTuples").toString()));
statement.setInt(1, requestID);
statement.setString(2, type);
ResultSet RS = statement.executeQuery();
String key, value;
boolean checked;
while (RS.next()) {
key = RS.getString("Key");
value = RS.getString("Value");
checked = RS.getBoolean("Checked");
tuples.add(new FieldState(key, value, checked));
}
} catch (SQLException e) {
Services.loggingService.logWarning("Database error.", e, LocalDateTime.now());
}
return tuples;
}
/**
* Performs a comprehensive comparison of the new request with the one added last to the database.
*
* @param newState The new request.
* @return true, if request is same as the last one in the database. false, otherwise.
*/
private boolean isDuplicate(DashboardState newState) {
try {
statement = conn.prepareStatement(EverestUtilities.trimString(queries.get("selectMostRecentRequest").toString()));
ResultSet RS = statement.executeQuery();
int lastRequestID = -1;
if (RS.next()) {
if (!(newState.httpMethod.equals(RS.getString("Type"))) ||
!(newState.target.equals(RS.getString("Target"))) ||
!(LocalDate.now().equals(LocalDate.parse(RS.getString("Date")))))
return false;
else
lastRequestID = RS.getInt("ID");
}
// This condition is observed when the database is empty
if (lastRequestID == -1)
return false;
ArrayList<FieldState> states;
// Checks for new or modified headers
states = getRequestHeaders(lastRequestID);
if (!areListsEqual(states, newState.headers))
return false;
// Checks for new or modified params
states = getTuples(lastRequestID, "Param");
if (!areListsEqual(states, newState.params))
return false;
if (!(newState.httpMethod.equals("GET") || newState.httpMethod.equals("DELETE"))) {
statement = conn.prepareStatement(EverestUtilities.trimString(queries.get("selectRequestContentType").toString()));
statement.setInt(1, lastRequestID);
RS = statement.executeQuery();
String previousContentType = "";
if (RS.next())
previousContentType = RS.getString("ContentType");
if (!newState.contentType.equals(previousContentType))
return false;
switch (newState.contentType) {
case MediaType.TEXT_PLAIN:
case MediaType.APPLICATION_JSON:
case MediaType.APPLICATION_XML:
case MediaType.TEXT_HTML:
case MediaType.APPLICATION_OCTET_STREAM:
statement = conn.prepareStatement(EverestUtilities.trimString(queries.get("selectRequestBody").toString()));
statement.setInt(1, lastRequestID);
RS = statement.executeQuery();
if (RS.next())
if (!RS.getString("Body").equals(newState.rawBody))
return false;
break;
case MediaType.APPLICATION_FORM_URLENCODED:
// Checks for new or modified string tuples
states = getTuples(lastRequestID, "String");
return areListsEqual(states, newState.urlStringTuples);
case MediaType.MULTIPART_FORM_DATA:
// Checks for new or modified string tuples
states = getTuples(lastRequestID, "String");
boolean stringComparison = areListsEqual(states, newState.formStringTuples);
// Checks for new or modified file tuples
states = getTuples(lastRequestID, "File");
boolean fileComparison = areListsEqual(states, newState.formFileTuples);
return stringComparison && fileComparison;
}
}
} catch (SQLException e) {
Services.loggingService.logWarning("Database error.", e, LocalDateTime.now());
} catch (NullPointerException NPE) {
/*
NPE is thrown by containsKey indicating that the key is not present in the database thereby
classifying it as a non-duplicate request.
*/
return false;
}
return true;
}
private static boolean areListsEqual(ArrayList<FieldState> firstList, ArrayList<FieldState> secondList) {
if (firstList == null && secondList == null)
return true;
if ((firstList == null && secondList != null) ||
(firstList != null && secondList == null))
return false;
for (FieldState state : secondList) {
if (!firstList.contains(state))
return false;
}
return true;
}
private class HistorySaver implements Runnable {
private DashboardState state;
@Override
public void run() {
try {
statement =
conn.prepareStatement(EverestUtilities.trimString(queries.get("saveRequest").toString()));
statement.setString(1, state.httpMethod);
statement.setString(2, state.target);
statement.setString(3, LocalDate.now().toString());
statement.executeUpdate();
// Get latest RequestID to insert into Headers table
statement = conn.prepareStatement("SELECT MAX(ID) AS MaxID FROM Requests");
ResultSet RS = statement.executeQuery();
int requestID = -1;
if (RS.next())
requestID = RS.getInt("MaxID");
if (state.headers.size() > 0) {
// Saves request headers
statement = conn.prepareStatement(EverestUtilities.trimString(queries.get("saveHeader").toString()));
for (FieldState fieldState : state.headers) {
statement.setInt(1, requestID);
statement.setString(2, fieldState.key);
statement.setString(3, fieldState.value);
statement.setInt(4, fieldState.checked ? 1 : 0);
statement.executeUpdate();
}
}
// Saves request parameters
saveTuple(state.params, "Param", requestID);
if (!(state.httpMethod.equals("GET") || state.httpMethod.equals("DELETE"))) {
// Maps the request to its ContentType for faster retrieval
statement = conn.prepareStatement(EverestUtilities.trimString(queries.get("saveRequestContentPair").toString()));
statement.setInt(1, requestID);
statement.setString(2, state.contentType);
statement.executeUpdate();
// Determines where to fetch the body from, based on the ContentType
switch (state.contentType) {
case MediaType.TEXT_PLAIN:
case MediaType.APPLICATION_JSON:
case MediaType.APPLICATION_XML:
case MediaType.TEXT_HTML:
case MediaType.APPLICATION_OCTET_STREAM:
// Saves the body in case of raw content, or the file location in case of binary
statement = conn.prepareStatement(EverestUtilities.trimString(queries.get("saveBody").toString()));
statement.setInt(1, requestID);
statement.setString(2, state.rawBody);
statement.executeUpdate();
break;
case MediaType.APPLICATION_FORM_URLENCODED:
saveTuple(state.urlStringTuples, "String", requestID);
break;
case MediaType.MULTIPART_FORM_DATA:
saveTuple(state.formStringTuples, "String", requestID);
saveTuple(state.formFileTuples, "File", requestID);
break;
}
}
} catch (SQLException e) {
Services.loggingService.logWarning("Database error.", e, LocalDateTime.now());
}
}
private void saveTuple(ArrayList<FieldState> tuples, String tupleType, int requestID) {
if (tuples.size() > 0) {
for (FieldState fieldState : tuples) {
// Saves the string tuples
try {
statement = conn.prepareStatement(EverestUtilities.trimString(queries.get("saveTuple").toString()));
statement.setInt(1, requestID);
statement.setString(2, tupleType);
statement.setString(3, fieldState.key);
statement.setString(4, fieldState.value);
statement.setInt(5, fieldState.checked ? 1 : 0);
statement.executeUpdate();
} catch (SQLException e) {
Services.loggingService.logWarning("Database error.", e, LocalDateTime.now());
}
}
}
}
}
}

View file

@ -20,13 +20,15 @@ public enum Level {
SEVERE, WARNING, INFO; SEVERE, WARNING, INFO;
int getValue() { int getValue() {
if (this.equals(SEVERE)) switch (this) {
case SEVERE:
return 3; return 3;
else if (this.equals(WARNING)) case WARNING:
return 2; return 2;
else default:
return 1; return 1;
} }
}
boolean greaterThanEqualTo(Level level) { boolean greaterThanEqualTo(Level level) {
return this.getValue() >= level.getValue(); return this.getValue() >= level.getValue();

View file

@ -79,8 +79,6 @@ class Logger {
} else { } else {
builder.append("Stack trace unavailable."); builder.append("Stack trace unavailable.");
} }
} else {
builder.append("");
} }
logEntry = logEntry.replace("%% StackTrace %%", builder.toString()); logEntry = logEntry.replace("%% StackTrace %%", builder.toString());

View file

@ -22,49 +22,49 @@ import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
public class LoggingService { public class LoggingService {
private Logger logger; private static final Logger logger;
private DateTimeFormatter dateFormat; private static final DateTimeFormatter dateFormat;
private Log log; private static final Log log;
public LoggingService(Level writerLevel) { static {
this.log = new Log(); log = new Log();
this.logger = new Logger(writerLevel); logger = new Logger(Level.INFO);
this.dateFormat = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss"); dateFormat = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss");
} }
public void logSevere(String message, Exception exception, LocalDateTime time) { public static void logSevere(String message, Exception exception, LocalDateTime time) {
setValues(message, exception, time); setValues(message, exception, time);
Services.singleExecutor.execute(severeLogger); Services.singleExecutor.execute(severeLogger);
} }
public void logWarning(String message, Exception exception, LocalDateTime time) { public static void logWarning(String message, Exception exception, LocalDateTime time) {
setValues(message, exception, time); setValues(message, exception, time);
Services.singleExecutor.execute(warningLogger); Services.singleExecutor.execute(warningLogger);
} }
public void logInfo(String message, LocalDateTime time) { public static void logInfo(String message, LocalDateTime time) {
setValues(message, null, time); setValues(message, null, time);
Services.singleExecutor.execute(infoLogger); Services.singleExecutor.execute(infoLogger);
} }
private void setValues(String message, Exception exception, LocalDateTime time) { private static void setValues(String message, Exception exception, LocalDateTime time) {
this.log.message = message; log.message = message;
this.log.exception = exception; log.exception = exception;
this.log.time = dateFormat.format(time); log.time = dateFormat.format(time);
} }
private Runnable severeLogger = () -> { private static Runnable severeLogger = () -> {
this.log.level = Level.SEVERE; log.level = Level.SEVERE;
this.logger.log(this.log); logger.log(log);
}; };
private Runnable warningLogger = () -> { private static Runnable warningLogger = () -> {
this.log.level = Level.WARNING; log.level = Level.WARNING;
this.logger.log(log); logger.log(log);
}; };
private Runnable infoLogger = () -> { private static Runnable infoLogger = () -> {
this.log.level = Level.INFO; log.level = Level.INFO;
this.logger.log(log); logger.log(log);
}; };
} }

View file

@ -18,6 +18,7 @@ package com.rohitawate.everest.misc;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.SerializationFeature;
import com.google.common.net.UrlEscapers;
public class EverestUtilities { public class EverestUtilities {
public static ObjectMapper jsonMapper; public static ObjectMapper jsonMapper;
@ -36,4 +37,8 @@ public class EverestUtilities {
public static String trimString(String input) { public static String trimString(String input) {
return input.replaceAll("\"", ""); return input.replaceAll("\"", "");
} }
public static String encodeURL(String url) {
return UrlEscapers.urlFragmentEscaper().escape(url);
}
} }

View file

@ -17,30 +17,13 @@
package com.rohitawate.everest.misc; package com.rohitawate.everest.misc;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import com.rohitawate.everest.controllers.HomeWindowController;
import com.rohitawate.everest.history.HistoryManager;
import com.rohitawate.everest.logging.Level;
import com.rohitawate.everest.logging.LoggingService;
import com.rohitawate.everest.requestmanager.RequestManagersPool;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
public class Services { public class Services {
public static Thread startServicesThread;
public static HistoryManager historyManager;
public static LoggingService loggingService;
public static HomeWindowController homeWindowController;
public static Executor singleExecutor; public static Executor singleExecutor;
public static RequestManagersPool pool;
public static void start() { static {
startServicesThread = new Thread(() -> {
loggingService = new LoggingService(Level.INFO);
historyManager = new HistoryManager();
singleExecutor = MoreExecutors.directExecutor(); singleExecutor = MoreExecutors.directExecutor();
pool = new RequestManagersPool();
});
startServicesThread.start();
} }
} }

View file

@ -17,6 +17,7 @@
package com.rohitawate.everest.misc; package com.rohitawate.everest.misc;
import com.rohitawate.everest.controllers.codearea.EverestCodeArea; import com.rohitawate.everest.controllers.codearea.EverestCodeArea;
import com.rohitawate.everest.logging.LoggingService;
import com.rohitawate.everest.settings.Settings; import com.rohitawate.everest.settings.Settings;
import javafx.scene.Parent; import javafx.scene.Parent;
@ -45,9 +46,9 @@ public class ThemeManager {
parent.getStylesheets().add(1, themePath); parent.getStylesheets().add(1, themePath);
} }
Services.loggingService.logInfo("Theme changed to " + Settings.theme + ".", LocalDateTime.now()); LoggingService.logInfo("Theme changed to " + Settings.theme + ".", LocalDateTime.now());
} else { } else {
Services.loggingService.logInfo(Settings.theme + ": No such theme file found.", LocalDateTime.now()); LoggingService.logInfo(Settings.theme + ": No such theme file found.", LocalDateTime.now());
} }
} }
} }
@ -58,7 +59,7 @@ public class ThemeManager {
parent.getStylesheets().add(themeFile.toURI().toString()); parent.getStylesheets().add(themeFile.toURI().toString());
parentNodes.add(parent); parentNodes.add(parent);
} else { } else {
Services.loggingService.logInfo(Settings.theme + ": No such theme file found.", LocalDateTime.now()); LoggingService.logInfo(Settings.theme + ": No such theme file found.", LocalDateTime.now());
} }
} }
} }
@ -68,7 +69,7 @@ public class ThemeManager {
if (syntaxThemeFile.exists()) { if (syntaxThemeFile.exists()) {
everestCodeArea.getStylesheets().add(syntaxThemeFile.toURI().toString()); everestCodeArea.getStylesheets().add(syntaxThemeFile.toURI().toString());
} else { } else {
Services.loggingService.logInfo(Settings.syntaxTheme + ": No such theme file found.", LocalDateTime.now()); LoggingService.logInfo(Settings.syntaxTheme + ": No such theme file found.", LocalDateTime.now());
} }
} }
} }

View file

@ -16,18 +16,9 @@
package com.rohitawate.everest.models.requests; package com.rohitawate.everest.models.requests;
import java.net.MalformedURLException; /**
import java.net.URL; * Convenience class to represent an HTTP request which uses the HTTP DELETE method.
*/
public class DELETERequest extends EverestRequest { public class DELETERequest extends EverestRequest {
public DELETERequest() {
}
public DELETERequest(String target) throws MalformedURLException {
super(target);
}
public DELETERequest(URL target) {
super(target);
}
} }

View file

@ -16,26 +16,18 @@
package com.rohitawate.everest.models.requests; package com.rohitawate.everest.models.requests;
import java.io.Serializable;
import java.util.HashMap; import java.util.HashMap;
/** /**
* Represents HTTP requests which contain data viz. POST and PUT. * Represents HTTP requests which use the HTTP POST, PUT and PATCH methods.
*/ */
public class DataDispatchRequest extends EverestRequest implements Serializable { public class DataRequest extends EverestRequest {
private String requestType; private String requestType;
private String body; private String body;
private String contentType; private String contentType;
private HashMap<String, String> stringTuples; private HashMap<String, String> stringTuples;
private HashMap<String, String> fileTuples; private HashMap<String, String> fileTuples;
public DataDispatchRequest() {
}
public DataDispatchRequest(String requestType) {
this.requestType = requestType;
}
public String getBody() { public String getBody() {
return body; return body;
} }

View file

@ -25,17 +25,6 @@ public abstract class EverestRequest implements Serializable {
private URL target; private URL target;
private HashMap<String, String> headers; private HashMap<String, String> headers;
EverestRequest() {
}
EverestRequest(String target) throws MalformedURLException {
this.target = new URL(target);
}
EverestRequest(URL target) {
this.target = target;
}
public void setTarget(String target) throws MalformedURLException { public void setTarget(String target) throws MalformedURLException {
this.target = new URL(target); this.target = new URL(target);
} }
@ -44,10 +33,6 @@ public abstract class EverestRequest implements Serializable {
return this.target; return this.target;
} }
public void addHeader(String key, String value) {
headers.put(key, value);
}
public void setHeaders(HashMap<String, String> headers) { public void setHeaders(HashMap<String, String> headers) {
this.headers = headers; this.headers = headers;
} }

View file

@ -16,18 +16,9 @@
package com.rohitawate.everest.models.requests; package com.rohitawate.everest.models.requests;
import java.net.MalformedURLException; /**
import java.net.URL; * Convenience class to represent an HTTP request which uses the HTTP GET method.
*/
public class GETRequest extends EverestRequest { public class GETRequest extends EverestRequest {
public GETRequest() {
}
public GETRequest(URL target) {
super(target);
}
public GETRequest(String target) throws MalformedURLException {
super(target);
}
} }

View file

@ -0,0 +1,42 @@
package com.rohitawate.everest.models.requests;
import javax.ws.rs.core.MediaType;
public class HTTPConstants {
public static final String GET = "GET";
public static final String POST = "POST";
public static final String PUT = "PUT";
public static final String PATCH = "PATCH";
public static final String DELETE = "DELETE";
public static final String PLAIN_TEXT = "PLAIN TEXT";
public static final String JSON = "JSON";
public static final String XML = "XML";
public static final String HTML = "HTML";
public static String getSimpleContentType(String contentType) {
switch (contentType) {
case MediaType.APPLICATION_JSON:
return JSON;
case MediaType.APPLICATION_XML:
return XML;
case MediaType.TEXT_HTML:
return HTML;
default:
return PLAIN_TEXT;
}
}
public static String getComplexContentType(String contentType) {
switch (contentType) {
case JSON:
return MediaType.APPLICATION_JSON;
case XML:
return MediaType.APPLICATION_XML;
case HTML:
return MediaType.TEXT_HTML;
default:
return MediaType.TEXT_PLAIN;
}
}
}

View file

@ -16,8 +16,11 @@
package com.rohitawate.everest.models.responses; package com.rohitawate.everest.models.responses;
import com.rohitawate.everest.logging.LoggingService;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.MultivaluedMap;
import java.time.LocalDateTime;
public class EverestResponse { public class EverestResponse {
private String body; private String body;
@ -74,4 +77,131 @@ public class EverestResponse {
public void setHeaders(MultivaluedMap<String, String> headers) { public void setHeaders(MultivaluedMap<String, String> headers) {
this.headers = headers; this.headers = headers;
} }
public static String getReasonPhrase(int statusCode) {
switch (statusCode) {
case 100:
return "Continue";
case 101:
return "Switching Protocol";
case 102:
return "Processing";
case 200:
return "OK";
case 201:
return "Created";
case 202:
return "Accepted";
case 203:
return "Non-Authoritative Information";
case 204:
return "No Content";
case 205:
return "Reset Content";
case 206:
return "Partial Content";
case 207:
case 208:
return "Multi-Status";
case 226:
return "IM Used";
case 300:
return "Multiple Choice";
case 301:
return "Moved Permanently";
case 302:
return "Found";
case 303:
return "See Other";
case 304:
return "Not Modified";
case 305:
return "Use Proxy";
case 307:
return "Temporary Redirect";
case 308:
return "Permanent Redirect";
case 400:
return "Bad Request";
case 401:
return "Unauthorized";
case 402:
return "Payment Required";
case 403:
return "Forbidden";
case 404:
return "Not Found";
case 405:
return "Method Not Allowed";
case 406:
return "Not Acceptable";
case 407:
return "Proxy Authentication Required";
case 408:
return "Request Timeout";
case 409:
return "Conflict";
case 410:
return "Gone";
case 411:
return "Length Required";
case 412:
return "Precondition Failed";
case 413:
return "Payload Too Large";
case 414:
return "URI Too Long";
case 415:
return "Unsupported Media Type";
case 416:
return "Requested Range Not Satisfiable";
case 417:
return "Expectation Failed";
case 418:
return "I'm a teapot";
case 421:
return "Misdirected Request";
case 422:
return "Unprocessable Entity";
case 423:
return "Locked";
case 424:
return "Failed Dependency";
case 426:
return "Upgrade Required";
case 428:
return "Precondition Required";
case 429:
return "Too Many Requests";
case 431:
return "Request Header Fields Too Large";
case 451:
return "Unavailable For Legal Reasons";
case 500:
return "Internal Server Error";
case 501:
return "Not Implemented";
case 502:
return "Bad Gateway";
case 503:
return "Service Unavailable";
case 504:
return "Gateway Timeout";
case 505:
return "HTTP Version Not Supported";
case 506:
return "Variant Also Negotiates";
case 507:
return "Insufficient Storage";
case 508:
return "Loop Detected";
case 510:
return "Not Extended";
case 511:
return "Network Authentication Required";
default:
LoggingService.logWarning("No description found for status code: " + statusCode, null, LocalDateTime.now());
return "No Description Found";
}
}
} }

View file

@ -1,48 +0,0 @@
/*
* 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 com.rohitawate.everest.models.responses.EverestResponse;
import javafx.concurrent.Task;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.core.Response;
public class DELETERequestManager extends RequestManager {
DELETERequestManager() {
}
@Override
protected Task<EverestResponse> createTask() {
return new Task<EverestResponse>() {
@Override
protected EverestResponse call() throws Exception {
Invocation invocation = requestBuilder.buildDelete();
initialTime = System.currentTimeMillis();
Response serverResponse = invocation.invoke();
finalTime = System.currentTimeMillis();
processServerResponse(serverResponse);
return response;
}
};
}
}

View file

@ -1,184 +0,0 @@
/*
* 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 com.rohitawate.everest.models.requests.DataDispatchRequest;
import com.rohitawate.everest.models.responses.EverestResponse;
import javafx.concurrent.Task;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.glassfish.jersey.media.multipart.file.FileDataBodyPart;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.core.Form;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.HashMap;
import java.util.Map;
/**
* Processes DataDispatchRequest by automatically determining whether it
* is a POST, PUT or PATCH request.
*/
public class DataDispatchRequestManager extends RequestManager {
private DataDispatchRequest dataDispatchRequest;
private String requestType;
DataDispatchRequestManager() {
}
@Override
protected Task<EverestResponse> createTask() {
return new Task<EverestResponse>() {
@Override
protected EverestResponse call() throws Exception {
dataDispatchRequest = (DataDispatchRequest) request;
requestType = dataDispatchRequest.getRequestType();
Invocation invocation = appendBody();
initialTime = System.currentTimeMillis();
Response serverResponse = invocation.invoke();
finalTime = System.currentTimeMillis();
processServerResponse(serverResponse);
return response;
}
};
}
/**
* Adds the request body based on the content type and generates an invocation.
*
* @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<String, String> mapEntry;
switch (dataDispatchRequest.getContentType()) {
case MediaType.MULTIPART_FORM_DATA:
FormDataMultiPart formData = new FormDataMultiPart();
// Adding the string tuples to the request
HashMap<String, String> pairs = dataDispatchRequest.getStringTuples();
for (Map.Entry entry : pairs.entrySet()) {
mapEntry = (Map.Entry) entry;
formData.field(mapEntry.getKey(), mapEntry.getValue());
}
String filePath;
File file;
boolean fileException = false;
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();
file = new File(filePath);
if (file.exists())
formData.bodyPart(new FileDataBodyPart(mapEntry.getKey(),
file, MediaType.APPLICATION_OCTET_STREAM_TYPE));
else {
fileException = true;
// For pretty-printing FileNotFoundException to the UI
fileExceptionMessage = " - " + filePath + "\n";
}
}
if (fileException) {
throw new FileNotFoundException(fileExceptionMessage);
}
formData.setMediaType(MediaType.MULTIPART_FORM_DATA_TYPE);
if (requestType.equals("POST"))
invocation = requestBuilder.buildPost(Entity.entity(formData, MediaType.MULTIPART_FORM_DATA_TYPE));
else
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);
if (!check.exists()) {
throw new FileNotFoundException(filePath);
}
FileInputStream stream = new FileInputStream(filePath);
if (requestType.equals("POST"))
invocation = requestBuilder.buildPost(Entity.entity(stream, overriddenContentType));
else
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()) {
mapEntry = (Map.Entry) entry;
form.param(mapEntry.getKey(), mapEntry.getValue());
}
if (requestType.equals("POST"))
invocation = requestBuilder.buildPost(Entity.entity(form, overriddenContentType));
else
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(), overriddenContentType));
break;
case "PUT":
invocation = requestBuilder
.buildPut(Entity.entity(dataDispatchRequest.getBody(), overriddenContentType));
break;
case "PATCH":
invocation = requestBuilder
.build("PATCH", Entity.entity(dataDispatchRequest.getBody(), overriddenContentType));
break;
}
}
return invocation;
}
}

View file

@ -1,45 +0,0 @@
/*
* 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 com.rohitawate.everest.models.responses.EverestResponse;
import javafx.concurrent.Task;
import javax.ws.rs.core.Response;
public class GETRequestManager extends RequestManager {
GETRequestManager() {
}
@Override
protected Task<EverestResponse> createTask() {
return new Task<EverestResponse>() {
@Override
protected EverestResponse call() throws Exception {
initialTime = System.currentTimeMillis();
Response serverResponse = requestBuilder.get();
finalTime = System.currentTimeMillis();
processServerResponse(serverResponse);
return response;
}
};
}
}

View file

@ -15,38 +15,56 @@
*/ */
package com.rohitawate.everest.requestmanager; package com.rohitawate.everest.requestmanager;
import com.rohitawate.everest.exceptions.NullResponseException;
import com.rohitawate.everest.exceptions.RedirectException; import com.rohitawate.everest.exceptions.RedirectException;
import com.rohitawate.everest.exceptions.UnreliableResponseException; import com.rohitawate.everest.models.requests.*;
import com.rohitawate.everest.models.requests.EverestRequest;
import com.rohitawate.everest.models.responses.EverestResponse; import com.rohitawate.everest.models.responses.EverestResponse;
import com.rohitawate.everest.settings.Settings; import com.rohitawate.everest.settings.Settings;
import javafx.concurrent.Service; import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.EventHandler;
import org.glassfish.jersey.client.ClientProperties; import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.client.HttpUrlConnectorProvider; import org.glassfish.jersey.client.HttpUrlConnectorProvider;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.glassfish.jersey.media.multipart.MultiPartFeature; import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.media.multipart.file.FileDataBodyPart;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.client.Client; import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.Invocation.Builder; import javax.ws.rs.client.Invocation.Builder;
import javax.ws.rs.core.Form;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
public abstract class RequestManager extends Service<EverestResponse> { /**
private final Client client; * Manages all the requests made through Everest.
long initialTime; * Converts EverestRequests into JAX-RS Invocations, which are then processed by Jersey.
long finalTime; * Also parses the ServerResponse and returns an EverestResponse.
* <p>
* Previously, Everest used separate managers for GET, Data (POST, PUT and PATCH) and DELETE requests.
* However, RequestManager extends JavaFX's Service class, which is expensive to create objects of.
* This made the creation of separate pools for every kind of RequestManager too expensive, memory-wise.
* Thus, now a single class manages all kinds of Requests thereby requiring only a single kind of pool.
* Also, this enables us to re-use inactive RequestManagers for all kinds of requests.
* For example, previously, if a GETRequestManager was requested by Everest, and all GETRequestManagers were running,
* a new one would be created even if a DELETERequestManager was idle.
* <p>
* TLDR: At the cost of some reduced semantic clarity, the old, separate-for-every-type-of-request RequestManagers
* are now replaced by this single works-for-all one to save some serious amount of memory and to facilitate better re-use.
*/
public class RequestManager extends Service<EverestResponse> {
private static final Client client;
EverestRequest request; static {
EverestResponse response;
Builder requestBuilder;
RequestManager() {
this.client = initClient();
}
private Client initClient() {
Client client;
client = ClientBuilder.newBuilder() client = ClientBuilder.newBuilder()
.register(MultiPartFeature.class) .register(MultiPartFeature.class)
.build(); .build();
@ -58,8 +76,47 @@ public abstract class RequestManager extends Service<EverestResponse> {
client.property(ClientProperties.CONNECT_TIMEOUT, Settings.connectionTimeOut); client.property(ClientProperties.CONNECT_TIMEOUT, Settings.connectionTimeOut);
if (Settings.connectionReadTimeOutEnable) if (Settings.connectionReadTimeOutEnable)
client.property(ClientProperties.READ_TIMEOUT, Settings.connectionReadTimeOut); client.property(ClientProperties.READ_TIMEOUT, Settings.connectionReadTimeOut);
}
return client; private long initialTime;
private long finalTime;
private EverestRequest request;
private EverestResponse response;
private Builder requestBuilder;
/**
* Creates a JavaFX Task for processing the required kind of request.
*/
@Override
protected Task<EverestResponse> createTask() throws ProcessingException {
return new Task<EverestResponse>() {
@Override
protected EverestResponse call() throws Exception {
Response serverResponse = null;
if (request.getClass().equals(GETRequest.class)) {
initialTime = System.currentTimeMillis();
serverResponse = requestBuilder.get();
finalTime = System.currentTimeMillis();
} else if (request.getClass().equals(DataRequest.class)) {
DataRequest dataRequest = (DataRequest) request;
Invocation invocation = appendBody(dataRequest);
initialTime = System.currentTimeMillis();
serverResponse = invocation.invoke();
finalTime = System.currentTimeMillis();
} else if (request.getClass().equals(DELETERequest.class)) {
initialTime = System.currentTimeMillis();
serverResponse = requestBuilder.delete();
finalTime = System.currentTimeMillis();
}
processServerResponse(serverResponse);
return response;
}
};
} }
public void setRequest(EverestRequest request) { public void setRequest(EverestRequest request) {
@ -68,24 +125,27 @@ public abstract class RequestManager extends Service<EverestResponse> {
appendHeaders(); appendHeaders();
} }
public EverestRequest getRequest() {
return this.request;
}
private void appendHeaders() { private void appendHeaders() {
HashMap<String, String> headers = request.getHeaders(); request.getHeaders().forEach((key, value) -> requestBuilder.header(key, value));
Map.Entry<String, String> mapEntry; requestBuilder.header("User-Agent", "Everest");
for (Map.Entry entry : headers.entrySet()) {
mapEntry = (Map.Entry) entry;
requestBuilder.header(mapEntry.getKey(), mapEntry.getValue());
}
} }
void processServerResponse(Response serverResponse) /**
throws UnreliableResponseException, RedirectException { * Takes a ServerResponse and extracts all the headers, the body, the response time and other details
if (serverResponse == null) * into a EverestResponse.
throw new UnreliableResponseException("The server did not respond.", */
private void processServerResponse(Response serverResponse)
throws NullResponseException, RedirectException {
if (serverResponse == null) {
throw new NullResponseException("The server did not respond.",
"Like that crush from high school.."); "Like that crush from high school..");
else if (serverResponse.getStatus() == 301) { } else if (serverResponse.getStatus() == 301 || serverResponse.getStatus() == 302) {
String newLocation = serverResponse.getHeaderString("location"); throw new RedirectException(
throw new RedirectException(newLocation); serverResponse.getHeaderString("location"));
} }
String responseBody = serverResponse.readEntity(String.class); String responseBody = serverResponse.readEntity(String.class);
@ -98,4 +158,154 @@ public abstract class RequestManager extends Service<EverestResponse> {
response.setStatusCode(serverResponse.getStatus()); response.setStatusCode(serverResponse.getStatus());
response.setSize(responseBody.length()); response.setSize(responseBody.length());
} }
public void addHandlers(EventHandler<WorkerStateEvent> running,
EventHandler<WorkerStateEvent> succeeded,
EventHandler<WorkerStateEvent> failed,
EventHandler<WorkerStateEvent> cancelled) {
setOnRunning(running);
setOnSucceeded(succeeded);
setOnFailed(failed);
setOnCancelled(cancelled);
}
public void removeHandlers() {
removeEventHandler(WorkerStateEvent.WORKER_STATE_RUNNING, getOnRunning());
removeEventHandler(WorkerStateEvent.WORKER_STATE_SUCCEEDED, getOnSucceeded());
removeEventHandler(WorkerStateEvent.WORKER_STATE_FAILED, getOnFailed());
removeEventHandler(WorkerStateEvent.WORKER_STATE_CANCELLED, getOnCancelled());
}
/**
* Adds the request body based on the content type and generates an invocation.
* Used for DataRequests.
*
* @return invocation object
*/
private Invocation appendBody(DataRequest dataRequest) throws Exception {
/*
Checks if a custom mime-type is mentioned in the headers.
If present, it will override the auto-determined one.
*/
String overriddenContentType = request.getHeaders().get("Content-Type");
Invocation invocation = null;
String requestType = dataRequest.getRequestType();
switch (dataRequest.getContentType()) {
case MediaType.MULTIPART_FORM_DATA:
// This enables Everest to make requests with an empty body.
if (dataRequest.getStringTuples().size() == 0 && dataRequest.getFileTuples().size() == 0) {
invocation = getInvocation(MediaType.MULTIPART_FORM_DATA, requestType, null, requestBuilder);
break;
}
FormDataMultiPart formData = new FormDataMultiPart();
// Adding the string tuples to the request
HashMap<String, String> pairs = dataRequest.getStringTuples();
for (Map.Entry<String, String> entry : pairs.entrySet()) {
formData.field(entry.getKey(), entry.getValue());
}
String filePath;
File file;
boolean fileException = false;
String fileExceptionMessage = null;
pairs = dataRequest.getFileTuples();
// Adding the file tuples to the request
for (Map.Entry<String, String> entry : pairs.entrySet()) {
filePath = entry.getValue();
file = new File(filePath);
if (file.exists())
formData.bodyPart(new FileDataBodyPart(entry.getKey(),
file, MediaType.APPLICATION_OCTET_STREAM_TYPE));
else {
fileException = true;
// For pretty-printing FileNotFoundException to the UI
fileExceptionMessage = " - " + filePath + "\n";
}
}
if (fileException) {
throw new FileNotFoundException(fileExceptionMessage);
}
formData.setMediaType(MediaType.MULTIPART_FORM_DATA_TYPE);
invocation = getInvocation(MediaType.MULTIPART_FORM_DATA, requestType, formData, requestBuilder);
break;
case MediaType.APPLICATION_OCTET_STREAM:
if (overriddenContentType == null)
overriddenContentType = MediaType.APPLICATION_OCTET_STREAM;
filePath = dataRequest.getBody();
// This enables Everest to make requests with empty bodies.
if (filePath.equals("")) {
invocation = getInvocation(overriddenContentType, requestType, null, requestBuilder);
break;
}
File check = new File(filePath);
if (!check.exists()) {
throw new FileNotFoundException(filePath);
}
FileInputStream stream = new FileInputStream(filePath);
invocation = getInvocation(overriddenContentType, requestType, stream, requestBuilder);
break;
case MediaType.APPLICATION_FORM_URLENCODED:
if (overriddenContentType == null)
overriddenContentType = MediaType.APPLICATION_FORM_URLENCODED;
Form form = new Form();
for (Map.Entry<String, String> entry : dataRequest.getStringTuples().entrySet()) {
form.param(entry.getKey(), entry.getValue());
}
invocation = getInvocation(overriddenContentType, requestType, form, requestBuilder);
break;
default:
// Handles raw data types (JSON, Plain text, XML, HTML)
String originalContentType = dataRequest.getContentType();
if (overriddenContentType == null)
overriddenContentType = originalContentType;
switch (requestType) {
case HTTPConstants.POST:
invocation = requestBuilder
.buildPost(Entity.entity(dataRequest.getBody(), overriddenContentType));
break;
case HTTPConstants.PUT:
invocation = requestBuilder
.buildPut(Entity.entity(dataRequest.getBody(), overriddenContentType));
break;
case HTTPConstants.PATCH:
invocation = requestBuilder
.build(HTTPConstants.PATCH, Entity.entity(dataRequest.getBody(), overriddenContentType));
break;
}
}
return invocation;
}
private static Invocation getInvocation(String overriddenContentType, String requestType, Object entity, Builder requestBuilder) {
Invocation invocation;
switch (requestType) {
case HTTPConstants.POST:
invocation = requestBuilder.buildPost(Entity.entity(entity, overriddenContentType));
break;
case HTTPConstants.PUT:
invocation = requestBuilder.buildPut(Entity.entity(entity, overriddenContentType));
break;
default:
invocation = requestBuilder.build(HTTPConstants.PATCH, Entity.entity(entity, overriddenContentType));
break;
}
return invocation;
}
} }

View file

@ -19,70 +19,26 @@ package com.rohitawate.everest.requestmanager;
import java.util.ArrayList; import java.util.ArrayList;
/** /**
* Provides the various RequestManagers employed by Everest. * Provides a dynamically-growing pool RequestManagers used by Everest.
* <p> * <p>
* Pools are created as needed i.e. the first DELETE request * The manager() method when invoked, searches the pool linearly.
* will create the pool of DELETERequestManagers. If a DELETE
* request is never made, the pool won't be created. Same applies
* for all other types of requests.
* <p>
* When demanding a RequestManager, the pool is checked linearly.
* The first RequestManager which is not currently running will be * The first RequestManager which is not currently running will be
* returned to the caller. If all the managers in the pool are running, * returned to the caller. If all the managers in the pool are running,
* a new one is created, added to the pool, and returned. * a new one will be created, added to the pool, and returned.
*/ */
public class RequestManagersPool { public class RequestManagersPool {
private ArrayList<GETRequestManager> getManagers; private static ArrayList<RequestManager> pool = new ArrayList<>();
private ArrayList<DataDispatchRequestManager> dataManagers;
private ArrayList<DELETERequestManager> deleteManagers;
public GETRequestManager get() { public static RequestManager manager() {
if (getManagers == null) for (RequestManager manager: pool) {
getManagers = new ArrayList<>(); if (!manager.isRunning()) {
manager.reset();
for (GETRequestManager getManager : getManagers) { return manager;
if (!getManager.isRunning()) {
getManager.reset();
return getManager;
} }
} }
GETRequestManager newManager = new GETRequestManager(); RequestManager newManager = new RequestManager();
getManagers.add(newManager); pool.add(newManager);
return newManager;
}
public DataDispatchRequestManager data() {
if (dataManagers == null)
dataManagers = new ArrayList<>();
for (DataDispatchRequestManager dataManager : dataManagers) {
if (!dataManager.isRunning()) {
dataManager.reset();
return dataManager;
}
}
DataDispatchRequestManager newManager = new DataDispatchRequestManager();
dataManagers.add(newManager);
return newManager;
}
public DELETERequestManager delete() {
if (deleteManagers == null)
deleteManagers = new ArrayList<>();
for (DELETERequestManager deleteManager : deleteManagers) {
if (!deleteManager.isRunning()) {
deleteManager.reset();
return deleteManager;
}
}
DELETERequestManager newManager = new DELETERequestManager();
deleteManagers.add(newManager);
return newManager; return newManager;
} }

View file

@ -31,5 +31,7 @@ public class Settings {
public static String syntaxTheme = "Moondust"; public static String syntaxTheme = "Moondust";
public static int showHistoryRange = 7; public static int showHistoryRange = 7;
public static boolean editorWrapText = false; public static boolean editorWrapText = true;
public static String fetchSource = "SQLite";
} }

View file

@ -17,8 +17,8 @@
package com.rohitawate.everest.settings; package com.rohitawate.everest.settings;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.rohitawate.everest.logging.LoggingService;
import com.rohitawate.everest.misc.EverestUtilities; import com.rohitawate.everest.misc.EverestUtilities;
import com.rohitawate.everest.misc.Services;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -60,7 +60,7 @@ public class SettingsLoader implements Runnable {
Settings.syntaxTheme = EverestUtilities.trimString(setStringSetting(Settings.syntaxTheme, "syntaxTheme")); Settings.syntaxTheme = EverestUtilities.trimString(setStringSetting(Settings.syntaxTheme, "syntaxTheme"));
Settings.showHistoryRange = setIntegerSetting(Settings.showHistoryRange, "showHistoryRange"); Settings.showHistoryRange = setIntegerSetting(Settings.showHistoryRange, "showHistoryRange");
} catch (IOException IOE) { } catch (IOException IOE) {
Services.loggingService.logInfo("Settings file not found. Using defaults.", LocalDateTime.now()); LoggingService.logInfo("Settings file not found. Using defaults.", LocalDateTime.now());
} }
} }
@ -69,9 +69,9 @@ public class SettingsLoader implements Runnable {
if (value != null) { if (value != null) {
defaultValue = value.toString(); defaultValue = value.toString();
Services.loggingService.logInfo("[" + identifier + "]: Loaded: " + defaultValue, LocalDateTime.now()); LoggingService.logInfo("[" + identifier + "]: Loaded: " + defaultValue, LocalDateTime.now());
} else { } else {
Services.loggingService.logInfo("[" + identifier + "]: Not found. Using default value.", LocalDateTime.now()); LoggingService.logInfo("[" + identifier + "]: Not found. Using default value.", LocalDateTime.now());
} }
return defaultValue; return defaultValue;
@ -82,9 +82,9 @@ public class SettingsLoader implements Runnable {
if (value != null) { if (value != null) {
defaultValue = value.asInt(); defaultValue = value.asInt();
Services.loggingService.logInfo("[" + identifier + "]: Loaded: " + defaultValue, LocalDateTime.now()); LoggingService.logInfo("[" + identifier + "]: Loaded: " + defaultValue, LocalDateTime.now());
} else { } else {
Services.loggingService.logInfo("[" + identifier + "]: Not found. Using default value.", LocalDateTime.now()); LoggingService.logInfo("[" + identifier + "]: Not found. Using default value.", LocalDateTime.now());
} }
return defaultValue; return defaultValue;
@ -95,9 +95,9 @@ public class SettingsLoader implements Runnable {
if (value != null) { if (value != null) {
defaultValue = value.asBoolean(); defaultValue = value.asBoolean();
Services.loggingService.logInfo("[" + identifier + "]: Loaded: " + defaultValue, LocalDateTime.now()); LoggingService.logInfo("[" + identifier + "]: Loaded: " + defaultValue, LocalDateTime.now());
} else { } else {
Services.loggingService.logInfo("[" + identifier + "]: Not found. Using default value.", LocalDateTime.now()); LoggingService.logInfo("[" + identifier + "]: Not found. Using default value.", LocalDateTime.now());
} }
return defaultValue; return defaultValue;

View file

@ -0,0 +1,76 @@
/*
* 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.state;
import com.rohitawate.everest.models.requests.HTTPConstants;
import java.util.List;
/**
* Represents the state of the Composer.
*/
public class ComposerState {
public String target;
public String httpMethod;
public List<FieldState> params;
public List<FieldState> headers;
public String contentType;
// Body and content-type of requests with raw bodies
public String rawBody;
public String rawBodyBoxValue;
// Tuples of URL-encoded requests
public List<FieldState> urlStringTuples;
// String and file tuples of multipart-form requests
public List<FieldState> formStringTuples;
public List<FieldState> formFileTuples;
// File path of application/octet-stream requests
public String binaryFilePath;
public ComposerState() {
this.httpMethod = HTTPConstants.GET;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ComposerState state = (ComposerState) o;
if (!target.equals(state.target)) return false;
if (!httpMethod.equals(state.httpMethod)) return false;
if (!params.equals(state.params)) return false;
if (!headers.equals(state.headers)) return false;
if (state.httpMethod.equals(HTTPConstants.GET)
|| state.httpMethod.equals(HTTPConstants.DELETE)) return true;
if (!contentType.equals(state.contentType)) return false;
if (!rawBody.equals(state.rawBody)) return false;
if (!rawBodyBoxValue.equals(state.rawBodyBoxValue)) return false;
if (!binaryFilePath.equals(state.binaryFilePath)) return false;
if (!urlStringTuples.equals(state.urlStringTuples)) return false;
if (!formStringTuples.equals(state.formStringTuples)) return false;
if (!formFileTuples.equals(state.formFileTuples)) return false;
return true;
}
}

View file

@ -0,0 +1,160 @@
/*
* 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.state;
import com.rohitawate.everest.controllers.DashboardController.ComposerTab;
import com.rohitawate.everest.controllers.DashboardController.ResponseLayer;
import com.rohitawate.everest.controllers.DashboardController.ResponseTab;
import com.rohitawate.everest.exceptions.NullResponseException;
import com.rohitawate.everest.exceptions.RedirectException;
import com.rohitawate.everest.logging.LoggingService;
import com.rohitawate.everest.models.requests.DataRequest;
import com.rohitawate.everest.models.requests.EverestRequest;
import com.rohitawate.everest.models.responses.EverestResponse;
import com.rohitawate.everest.requestmanager.RequestManager;
import javafx.event.Event;
import javax.ws.rs.ProcessingException;
import java.io.FileNotFoundException;
import java.net.MalformedURLException;
import java.time.LocalDateTime;
import java.util.HashMap;
/**
* Represents the state of Everest's Dashboard.
*/
public class DashboardState {
public ComposerState composer;
public ResponseLayer visibleResponseLayer;
public ResponseTab visibleResponseTab;
public ComposerTab visibleComposerTab;
// ResponseLayer parameters
public int statusCode;
public String responseType;
public String responseBody;
public int responseTime;
public int responseSize;
public HashMap<String, String> responseHeaders;
// ErrorLayer parameters
public String errorTitle;
public String errorDetails;
// ResponseLayer parameters
private RequestManager requestManager;
/**
* Accepts a RequestManager from the DashboardController
* which is in the RUNNING state and switches its handlers.
* <p>
* The new handlers make changes to the DashboardState object
* rather than the Dashboard.
* <p>
* If we switch back to the tab with DashboardState while
* the manager is running, it is handed back over to the Dashboard.
*/
public void handOverRequest(RequestManager requestManager) {
this.requestManager = requestManager;
this.requestManager.removeHandlers();
this.requestManager.setOnFailed(this::onRequestFailed);
this.requestManager.setOnSucceeded(this::onRequestSucceeded);
this.requestManager.setOnCancelled(this::onRequestCancelled);
}
private void onRequestCancelled(Event event) {
this.visibleResponseLayer = ResponseLayer.PROMPT;
requestManager.reset();
}
private void onRequestSucceeded(Event event) {
visibleResponseLayer = ResponseLayer.RESPONSE;
EverestResponse response = requestManager.getValue();
statusCode = response.getStatusCode();
if (response.getMediaType() != null)
responseType = response.getMediaType().toString();
else
responseType = "";
responseTime = (int) response.getTime();
responseSize = response.getSize();
responseBody = response.getBody();
if (responseHeaders == null)
responseHeaders = new HashMap<>();
else
responseHeaders.clear();
response.getHeaders().forEach((key, value) -> responseHeaders.put(key, value.get(0)));
}
// TODO: Clean this method
private void onRequestFailed(Event event) {
this.visibleResponseLayer = ResponseLayer.ERROR;
Throwable throwable = requestManager.getException();
Exception exception = (Exception) throwable;
LoggingService.logWarning(this.composer.httpMethod + " request could not be processed.", exception, LocalDateTime.now());
if (throwable.getClass() == NullResponseException.class) {
NullResponseException URE = (NullResponseException) throwable;
errorTitle = URE.getExceptionTitle();
errorDetails = URE.getExceptionDetails();
} else if (throwable.getClass() == ProcessingException.class) {
System.out.println(throwable.getCause().toString());
errorTitle = "Everest couldn't connect.";
errorDetails = "Either you are not connected to the Internet or the server is offline.";
} else if (throwable.getClass() == RedirectException.class) {
RedirectException redirect = (RedirectException) throwable;
this.composer.target = redirect.getNewLocation();
EverestRequest request = requestManager.getRequest();
try {
request.setTarget(redirect.getNewLocation());
requestManager.restart();
return;
} catch (MalformedURLException MURLE) {
LoggingService.logInfo("Invalid URL: " + this.composer.target, LocalDateTime.now());
}
} else {
errorTitle = "Oops... That's embarrassing!";
errorDetails = "Something went wrong. Try to make another request.Restart Everest if that doesn't work.";
}
if (requestManager.getRequest().getClass().equals(DataRequest.class)) {
if (throwable.getCause() != null && throwable.getCause().getClass() == IllegalArgumentException.class) {
errorTitle = "Did you forget something?";
errorDetails = "Please specify a body for your " + this.composer.httpMethod + " request.";
} else if (throwable.getClass() == FileNotFoundException.class) {
errorTitle = "File(s) not found:";
errorDetails = throwable.getMessage();
}
}
requestManager.reset();
}
public RequestManager getRequestManager() {
return this.requestManager;
}
public DashboardState() {
}
public DashboardState(ComposerState composer) {
this.composer = composer;
}
}

View file

@ -14,7 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package com.rohitawate.everest.controllers.state; package com.rohitawate.everest.state;
import com.fasterxml.jackson.annotation.JsonIgnore;
import java.util.Objects; import java.util.Objects;
@ -48,4 +50,9 @@ public class FieldState {
Objects.equals(key, that.key) && Objects.equals(key, that.key) &&
Objects.equals(value, that.value); Objects.equals(value, that.value);
} }
@JsonIgnore
public boolean isEmpty() {
return key.isEmpty() && value.isEmpty();
}
} }

View file

@ -0,0 +1,35 @@
package com.rohitawate.everest.sync;
import com.rohitawate.everest.state.ComposerState;
import java.util.List;
/**
* Manages the history and (in the future) the projects of Everest.
*/
public interface DataManager {
/**
* Saves the state of the Composer when the request was made.
*/
void saveState(ComposerState newState) throws Exception;
/**
* Fetches all the states of the Composer when the previous requests were made.
*
* @return A list of the states.
*/
List<ComposerState> getHistory() throws Exception;
/**
* Returns the state of the Composer when the last request was made.
* If this DataManager is the primary fetching source, SyncManager uses
* calls this method before attempting to save a new state.
*/
ComposerState getLastAdded();
/**
* Returns the identifier for the DataManager. Preferably, use the source as the identifier.
* For example, a DataManager using Google Drive may identify itself as 'Google Drive'.
*/
String getIdentifier();
}

View file

@ -0,0 +1,314 @@
/*
* 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.sync;
import com.rohitawate.everest.logging.LoggingService;
import com.rohitawate.everest.models.requests.HTTPConstants;
import com.rohitawate.everest.settings.Settings;
import com.rohitawate.everest.state.ComposerState;
import com.rohitawate.everest.state.FieldState;
import javafx.util.Pair;
import java.io.File;
import java.sql.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
class SQLiteManager implements DataManager {
private Connection conn;
private PreparedStatement statement;
private static class Queries {
private static final String[] createQueries = {
"CREATE TABLE IF NOT EXISTS Requests(ID INTEGER PRIMARY KEY, Type TEXT NOT NULL, Target TEXT NOT NULL, Date TEXT NOT NULL)",
"CREATE TABLE IF NOT EXISTS RequestContentMap(RequestID INTEGER, ContentType TEXT NOT NULL, FOREIGN KEY(RequestID) REFERENCES Requests(ID))",
"CREATE TABLE IF NOT EXISTS Bodies(RequestID INTEGER, Type TEXT NOT NULL CHECK(Type IN ('application/json', 'application/xml', 'text/html', 'text/plain')), Body TEXT NOT NULL, FOREIGN KEY(RequestID) REFERENCES Requests(ID))",
"CREATE TABLE IF NOT EXISTS FilePaths(RequestID INTEGER, Path TEXT NOT NULL, FOREIGN KEY(RequestID) REFERENCES Requests(ID))",
"CREATE TABLE IF NOT EXISTS Tuples(RequestID INTEGER, Type TEXT NOT NULL CHECK(Type IN ('Header', 'Param', 'URLString', 'FormString', 'File')), Key TEXT NOT NULL, Value TEXT NOT NULL, Checked INTEGER CHECK (Checked IN (0, 1)), FOREIGN KEY(RequestID) REFERENCES Requests(ID))"
};
private static final String saveRequest = "INSERT INTO Requests(Type, Target, Date) VALUES(?, ?, ?)";
private static final String saveRequestContentPair = "INSERT INTO RequestContentMap(RequestID, ContentType) VALUES(?, ?)";
private static final String saveBody = "INSERT INTO Bodies(RequestID, Body, Type) VALUES(?, ?, ?)";
private static final String saveFilePath = "INSERT INTO FilePaths(RequestID, Path) VALUES(?, ?)";
private static final String saveTuple = "INSERT INTO Tuples(RequestID, Type, Key, Value, Checked) VALUES(?, ?, ?, ?, ?)";
private static final String selectRecentRequests = "SELECT * FROM Requests WHERE Requests.Date > ?";
private static final String selectRequestContentType = "SELECT ContentType FROM RequestContentMap WHERE RequestID == ?";
private static final String selectRequestBody = "SELECT Body, Type FROM Bodies WHERE RequestID == ?";
private static final String selectFilePath = "SELECT Path FROM FilePaths WHERE RequestID == ?";
private static final String selectTuplesByType = "SELECT * FROM Tuples WHERE RequestID == ? AND Type == ?";
private static final String selectMostRecentRequest = "SELECT * FROM Requests ORDER BY ID DESC LIMIT 1";
}
public SQLiteManager() {
try {
String configPath = "Everest/config/";
File configFolder = new File(configPath);
if (!configFolder.exists()) {
if (configFolder.mkdirs())
LoggingService.logSevere("Unable to create directory: " + configPath, null, LocalDateTime.now());
}
conn = DriverManager.getConnection("jdbc:sqlite:Everest/config/history.sqlite");
createDatabase();
} catch (Exception E) {
LoggingService.logSevere("Exception while initializing DataManager.", E, LocalDateTime.now());
} finally {
System.out.println("Connected to database.");
}
}
/**
* Creates and initializes the database with necessary tables if not already done.
*/
private void createDatabase() throws SQLException {
for (String query : Queries.createQueries) {
statement = conn.prepareStatement(query);
statement.execute();
}
}
/**
* Saves the request to the database if it is not identical to one made exactly before it.
* Method is synchronized to allow only one database transaction at a time.
*
* @param newState - The state of the Dashboard while making the request.
*/
@Override
public synchronized void saveState(ComposerState newState) throws SQLException {
statement = conn.prepareStatement(Queries.saveRequest);
statement.setString(1, newState.httpMethod);
statement.setString(2, newState.target);
statement.setString(3, LocalDate.now().toString());
statement.executeUpdate();
// Get latest RequestID to insert into Headers table
statement = conn.prepareStatement("SELECT MAX(ID) AS MaxID FROM Requests");
ResultSet RS = statement.executeQuery();
int requestID = -1;
if (RS.next())
requestID = RS.getInt("MaxID");
saveTuple(newState.headers, "Header", requestID);
saveTuple(newState.params, "Param", requestID);
if (!(newState.httpMethod.equals(HTTPConstants.GET) || newState.httpMethod.equals(HTTPConstants.DELETE))) {
// Maps the request to its ContentType for faster retrieval
statement = conn.prepareStatement(Queries.saveRequestContentPair);
statement.setInt(1, requestID);
statement.setString(2, newState.contentType);
statement.executeUpdate();
statement = conn.prepareStatement(Queries.saveBody);
statement.setInt(1, requestID);
statement.setString(2, newState.rawBody);
statement.setString(3, newState.rawBodyBoxValue);
statement.executeUpdate();
statement = conn.prepareStatement(Queries.saveFilePath);
statement.setInt(1, requestID);
statement.setString(2, newState.binaryFilePath);
statement.executeUpdate();
saveTuple(newState.urlStringTuples, "URLString", requestID);
saveTuple(newState.formStringTuples, "FormString", requestID);
saveTuple(newState.formFileTuples, "File", requestID);
}
}
/**
* Returns a list of all the recent requests.
*/
@Override
public synchronized List<ComposerState> getHistory() throws SQLException {
List<ComposerState> history = new ArrayList<>();
// Loads the requests from the last x number of days, x being Settings.showHistoryRange
statement = conn.prepareStatement(Queries.selectRecentRequests);
String historyStartDate = LocalDate.now().minusDays(Settings.showHistoryRange).toString();
statement.setString(1, historyStartDate);
ResultSet resultSet = statement.executeQuery();
ComposerState state;
while (resultSet.next()) {
state = new ComposerState();
state.target = resultSet.getString("Target");
int requestID = resultSet.getInt("ID");
state.headers = getTuples(requestID, "Header");
state.params = getTuples(requestID, "Param");
state.httpMethod = resultSet.getString("Type");
if (!(state.httpMethod.equals(HTTPConstants.GET) || state.httpMethod.equals(HTTPConstants.DELETE))) {
// Retrieves request body ContentType for querying corresponding table
state.contentType = getRequestContentType(requestID);
Pair<String, String> rawBodyAndType = getRequestBody(requestID);
if (rawBodyAndType != null) {
state.rawBody = rawBodyAndType.getKey();
state.rawBodyBoxValue = rawBodyAndType.getValue();
}
state.binaryFilePath = getFilePath(requestID);
state.urlStringTuples = getTuples(requestID, "URLString");
state.formStringTuples = getTuples(requestID, "FormString");
state.formFileTuples = getTuples(requestID, "File");
}
history.add(state);
}
return history;
}
private String getRequestContentType(int requestID) throws SQLException {
String contentType = null;
statement = conn.prepareStatement(Queries.selectRequestContentType);
statement.setInt(1, requestID);
ResultSet RS = statement.executeQuery();
if (RS.next())
contentType = RS.getString("ContentType");
return contentType;
}
/**
* @param requestID Database ID of the request whose tuples are needed.
* @param type Type of tuples needed ('URLString', 'FormString', 'File', 'Header' or 'Param')
* @return fieldStates - List of FieldStates for the tuples
*/
private List<FieldState> getTuples(int requestID, String type) throws SQLException {
if (!(type.equals("FormString") || type.equals("URLString") ||
type.equals("File") || type.equals("Param") || type.equals("Header")))
return null;
ArrayList<FieldState> fieldStates = new ArrayList<>();
PreparedStatement statement = conn.prepareStatement(Queries.selectTuplesByType);
statement.setInt(1, requestID);
statement.setString(2, type);
ResultSet RS = statement.executeQuery();
String key, value;
boolean checked;
while (RS.next()) {
key = RS.getString("Key");
value = RS.getString("Value");
checked = RS.getBoolean("Checked");
fieldStates.add(new FieldState(key, value, checked));
}
return fieldStates;
}
@Override
public ComposerState getLastAdded() {
ComposerState lastRequest = new ComposerState();
try {
statement = conn.prepareStatement(Queries.selectMostRecentRequest);
ResultSet RS = statement.executeQuery();
int requestID = -1;
if (RS.next()) {
requestID = RS.getInt("ID");
lastRequest.target = RS.getString("Target");
lastRequest.httpMethod = RS.getString("Type");
}
lastRequest.headers = getTuples(requestID, "Header");
lastRequest.params = getTuples(requestID, "Param");
lastRequest.urlStringTuples = getTuples(requestID, "URLString");
lastRequest.formStringTuples = getTuples(requestID, "FormString");
lastRequest.formFileTuples = getTuples(requestID, "File");
lastRequest.contentType = getRequestContentType(requestID);
lastRequest.binaryFilePath = getFilePath(requestID);
Pair<String, String> rawBodyAndType = getRequestBody(requestID);
if (rawBodyAndType != null) {
lastRequest.rawBody = rawBodyAndType.getKey();
lastRequest.rawBodyBoxValue = rawBodyAndType.getValue();
}
} catch (SQLException e) {
e.printStackTrace();
}
return lastRequest;
}
private Pair<String, String> getRequestBody(int requestID) throws SQLException {
statement = conn.prepareStatement(Queries.selectRequestBody);
statement.setInt(1, requestID);
ResultSet RS = statement.executeQuery();
if (RS.next()) {
return new Pair<>(RS.getString("Body"), RS.getString("Type"));
} else {
return null;
}
}
private String getFilePath(int requestID) throws SQLException {
statement = conn.prepareStatement(Queries.selectFilePath);
statement.setInt(1, requestID);
ResultSet RS = statement.executeQuery();
if (RS.next())
return RS.getString("Path");
else
return null;
}
private void saveTuple(List<FieldState> tuples, String tupleType, int requestID) {
if (tuples.size() > 0) {
try {
for (FieldState fieldState : tuples) {
statement = conn.prepareStatement(Queries.saveTuple);
statement.setInt(1, requestID);
statement.setString(2, tupleType);
statement.setString(3, fieldState.key);
statement.setString(4, fieldState.value);
statement.setInt(5, fieldState.checked ? 1 : 0);
statement.addBatch();
}
statement.executeBatch();
} catch (SQLException e) {
LoggingService.logSevere("Database error.", e, LocalDateTime.now());
}
}
}
@Override
public String getIdentifier() {
return "SQLite";
}
}

View file

@ -0,0 +1,100 @@
package com.rohitawate.everest.sync;
import com.google.common.util.concurrent.MoreExecutors;
import com.rohitawate.everest.controllers.HomeWindowController;
import com.rohitawate.everest.exceptions.DuplicateException;
import com.rohitawate.everest.logging.LoggingService;
import com.rohitawate.everest.settings.Settings;
import com.rohitawate.everest.state.ComposerState;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Executor;
/**
* Manages all the DataManagers of Everest and registers new ones.
* Registers Everest's default SQLiteManager automatically.
*/
public class SyncManager {
private static HashMap<String, DataManager> managers;
private static HomeWindowController homeWindowController;
private static Executor executor = MoreExecutors.directExecutor();
private static HistorySaver historySaver;
/**
* @param homeWindowController - to add a HistoryItem by invoking addHistoryItem()
*/
public SyncManager(HomeWindowController homeWindowController) {
SyncManager.homeWindowController = homeWindowController;
managers = new HashMap<>();
historySaver = new HistorySaver();
// Registering the default
try {
registerManager(new SQLiteManager());
} catch (DuplicateException e) {
System.out.println("SQLite Manager already exists: Nope, will never happen.");
}
}
/**
* Asynchronously saves the new state by invoking all the registered DataManagers.
*/
public void saveState(ComposerState newState) {
if (newState.equals(managers.get(Settings.fetchSource).getLastAdded()))
return;
historySaver.newState = newState;
executor.execute(historySaver);
homeWindowController.addHistoryItem(newState);
}
/**
* Retrieves the history from the configured source.
*
* @return A list of all the requests
*/
public List<ComposerState> getHistory() {
List<ComposerState> history = null;
try {
if (managers.get(Settings.fetchSource) == null) {
LoggingService.logSevere("No such source found: " + Settings.fetchSource, null, LocalDateTime.now());
history = managers.get("SQLite").getHistory();
} else {
history = managers.get(Settings.fetchSource).getHistory();
}
} catch (Exception e) {
LoggingService.logSevere("History could not be fetched.", e, LocalDateTime.now());
}
return history;
}
/**
* Registers a new DataManager to be used for syncing Everest's data
* at various sources.
*/
public void registerManager(DataManager newManager) throws DuplicateException {
if (managers.containsKey(newManager.getIdentifier()))
throw new DuplicateException(
"Duplicate DataManager: Manager with identifier \'" + newManager.getIdentifier() + "\' already exists.");
else
managers.put(newManager.getIdentifier(), newManager);
}
private static class HistorySaver implements Runnable {
private ComposerState newState;
@Override
public void run() {
try {
for (DataManager manager : managers.values())
manager.saveState(newState);
} catch (Exception e) {
LoggingService.logSevere("Could not save history.", e, LocalDateTime.now());
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 755 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

View file

@ -23,7 +23,7 @@
} }
#addressField { #addressField {
-fx-background-color: #6e6e6e; -fx-background-color: #707070;
-fx-text-fill: white; -fx-text-fill: white;
} }
@ -34,8 +34,8 @@
.combo-box .list-cell { .combo-box .list-cell {
-fx-background-color: #282828; -fx-background-color: #282828;
-fx-pref-height: 40px; -fx-pref-height: 35px;
-fx-font-size: 15px; -fx-font-size: 14px;
-fx-text-fill: #d4d4d4; -fx-text-fill: #d4d4d4;
-fx-padding: 0 0 0 10px; -fx-padding: 0 0 0 10px;
} }
@ -63,8 +63,9 @@
-fx-background-color: transparent; -fx-background-color: transparent;
} }
#sendButton { #sendButton,
-fx-background-color: #c45015; #sendButtonPane {
-fx-background-color: #282828;
} }
#responseDetails { #responseDetails {
@ -96,15 +97,15 @@
} }
/* Home window tab */ /* Home window tab */
#homeWindowTabPane:top .tab-header-area .headers-region .tab:top .tab-container .tab-close-button { #tabPane:top .tab-header-area .headers-region .tab:top .tab-container .tab-close-button {
-fx-background-color: white; -fx-background-color: white;
} }
#homeWindowTabPane:top .tab-header-area .headers-region .tab:hover:top { #tabPane:top .tab-header-area .headers-region .tab:hover:top {
-fx-background-color: orangered; -fx-background-color: orangered;
} }
#homeWindowTabPane:top .tab-header-area .headers-region .tab:selected:top { #tabPane:top .tab-header-area .headers-region .tab:selected:top {
-fx-background-color: #505050; -fx-background-color: #505050;
} }
@ -142,6 +143,14 @@
-fx-faint-focus-color: transparent; -fx-faint-focus-color: transparent;
} }
#newTabButton {
-fx-background-color: #6a6a6a;
}
#newTabButton:hover {
-fx-background-color: cornflowerblue;
}
.tab-pane:top .tab-header-area .headers-region .tab:top { .tab-pane:top .tab-header-area .headers-region .tab:top {
-fx-background-color: #6a6a6a; -fx-background-color: #6a6a6a;
} }
@ -151,6 +160,10 @@
-fx-fill: white; -fx-fill: white;
} }
.tab:selected .tab-close-button:hover {
-fx-background-color: #282828;
}
/* Request options (Headers, Authorization, etc) tab */ /* Request options (Headers, Authorization, etc) tab */
#requestOptionsTab .tab-header-area .headers-region .tab:hover:top { #requestOptionsTab .tab-header-area .headers-region .tab:hover:top {
-fx-background-color: #55a15c; -fx-background-color: #55a15c;
@ -197,7 +210,14 @@
-fx-padding: 0px; -fx-padding: 0px;
} }
#keyField, #valueField, #filePathField { #keyField, #valueField {
-fx-prompt-text-fill: #919191;
-fx-background-color: #303030;
-fx-text-fill: white;
}
/* Binary tab */
#filePathBox, #filePathField {
-fx-prompt-text-fill: #919191; -fx-prompt-text-fill: #919191;
-fx-background-color: #303030; -fx-background-color: #303030;
-fx-text-fill: white; -fx-text-fill: white;
@ -226,7 +246,7 @@
} }
#browseButton { #browseButton {
-fx-background-color: #a2a2a2; -fx-background-color: #bababa;
} }
#browseButton:hover { #browseButton:hover {
@ -270,6 +290,12 @@
} }
/* History item */ /* History item */
#methodLabel {
-fx-font-family: sans-serif;
-fx-font-weight: bold;
-fx-font-size: 13px;
}
#historyItemBox { #historyItemBox {
-fx-background-color: #353535; -fx-background-color: #353535;
} }
@ -282,7 +308,7 @@
-fx-font-size: 15px; -fx-font-size: 15px;
} }
/* Response Visualizer */ /* ---------------------------- TreeVisualizer ------------------------- */
#responseTabPane:bottom .tab-header-area .tab-header-background, #responseTabPane:bottom .tab-header-area .tab-header-background,
#responseTabPane:bottom .tab-header-area .headers-region .tab:bottom { #responseTabPane:bottom .tab-header-area .headers-region .tab:bottom {
-fx-background-color: #282828; -fx-background-color: #282828;
@ -305,28 +331,40 @@
-fx-background-color: #2a2a2a; -fx-background-color: #2a2a2a;
} }
.visualizerLabel { #rawInputTypeBox,
#responseTypeBox {
-fx-background-color: #505050;
}
#rawInputTypeBox .list-cell,
#responseTypeBox .list-cell {
-fx-text-fill: azure;
-fx-font-size: 13px;
-fx-background-color: #505050;
}
#rawInputTypeBox .list-cell:hover,
#responseTypeBox .list-cell:hover {
-fx-background-color: cornflowerblue;
}
/* Response Headers Viewer */
.response-header-label {
-fx-font-family: "Liberation Mono", "Consolas", "Courier New", "Monaco", "DejaVu Sans Mono", monospace; -fx-font-family: "Liberation Mono", "Consolas", "Courier New", "Monaco", "DejaVu Sans Mono", monospace;
} }
.visualizerRootLabel { .response-header-key-label {
-fx-font-size: 17px; -fx-font-size: 16px;
-fx-text-fill: #dedede;
-fx-font-weight: bold;
}
.visualizerKeyLabel {
-fx-font-size: 17px;
-fx-text-fill: #bababa; -fx-text-fill: #bababa;
-fx-font-weight: bold; -fx-font-weight: bold;
} }
.visualizerValueLabel { .response-header-value-label {
-fx-font-size: 16px; -fx-font-size: 16px;
-fx-text-fill: #959595; -fx-text-fill: #959595;
} }
/* Visualizer tree */ /* Tree Visualizer */
.tree-view { .tree-view {
-fx-background-color: #353535; -fx-background-color: #353535;
} }
@ -342,6 +380,9 @@
.tree-cell { .tree-cell {
-fx-background-color: #282828; -fx-background-color: #282828;
-fx-text-fill: azure;
-fx-font-size: 14px;
-fx-font-family: "Liberation Mono", "Consolas", "Courier New", "Monaco", "DejaVu Sans Mono", monospace;
-fx-border-width: 0px; -fx-border-width: 0px;
} }
@ -351,7 +392,7 @@
.tree-cell:selected, .tree-cell:selected,
.tree-cell:focused { .tree-cell:focused {
-fx-background-color: cornflowerblue; -fx-background-color: darkred;
} }
.tree-cell:selected .label { .tree-cell:selected .label {
@ -365,7 +406,7 @@
-fx-padding: 0; -fx-padding: 0;
} }
/* SnackBar */ /* Snackbar */
.jfx-snackbar-content { .jfx-snackbar-content {
-fx-background-color: black; -fx-background-color: black;
} }

View file

@ -23,7 +23,7 @@
.everest-code-area .text { .everest-code-area .text {
-fx-fill: #ababab; -fx-fill: #ababab;
-fx-font-family: "Liberation Mono", "Consolas", "Courier New", "Monaco", "DejaVu Sans Mono", monospace; -fx-font-family: "Liberation Mono", "Consolas", "Courier New", "Monaco", "DejaVu Sans Mono", monospace;
-fx-font-size: 17px; -fx-font-size: 15px;
} }
.everest-code-area .caret { .everest-code-area .caret {

View file

@ -19,10 +19,13 @@
<?import com.jfoenix.controls.JFXButton?> <?import com.jfoenix.controls.JFXButton?>
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?> <?import javafx.scene.control.*?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<TabPane fx:id="bodyTabPane" prefHeight="100.0" prefWidth="1280.0" stylesheets="@../../css/Adreana.css" <?import javafx.scene.text.Font?>
tabClosingPolicy="UNAVAILABLE" tabMinWidth="150.0" xmlns="http://javafx.com/javafx/8.0.141" <TabPane fx:id="bodyTabPane" stylesheets="@../../css/Adreana.css" tabClosingPolicy="UNAVAILABLE" tabMinWidth="150.0"
xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.rohitawate.everest.controllers.BodyTabController"> xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.rohitawate.everest.controllers.BodyTabController">
<tabs> <tabs>
<Tab fx:id="formTab" text="FORM"/> <Tab fx:id="formTab" text="FORM"/>
<Tab fx:id="urlTab" text="URL ENCODED"/> <Tab fx:id="urlTab" text="URL ENCODED"/>
@ -30,7 +33,8 @@
<content> <content>
<VBox fx:id="rawVBox"> <VBox fx:id="rawVBox">
<children> <children>
<ComboBox fx:id="rawInputTypeBox" maxHeight="30.0" visibleRowCount="5" VBox.vgrow="SOMETIMES"> <ComboBox fx:id="rawInputTypeBox" maxHeight="25.0" minHeight="25.0" visibleRowCount="5"
VBox.vgrow="SOMETIMES">
<VBox.margin> <VBox.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0"/> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0"/>
</VBox.margin> </VBox.margin>
@ -41,13 +45,55 @@
</Tab> </Tab>
<Tab fx:id="binaryTab" text="BINARY"> <Tab fx:id="binaryTab" text="BINARY">
<content> <content>
<HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0" spacing="20.0"> <VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0" spacing="15.0">
<children> <children>
<TextField fx:id="filePathField" maxWidth="700.0" promptText="FILE PATH" HBox.hgrow="ALWAYS"/> <Label text="Select a file to add to the request" textFill="#9a9a9a">
<JFXButton fx:id="browseButton" buttonType="RAISED" onAction="#browseFile" ripplerFill="WHITE" <font>
text="BROWSE" HBox.hgrow="ALWAYS"/> <Font size="14.0"/>
</font>
</Label>
<HBox alignment="CENTER" maxWidth="700.0" spacing="20.0">
<children>
<HBox fx:id="filePathBox" alignment="CENTER" maxHeight="35.0" minHeight="30.0"
HBox.hgrow="ALWAYS">
<children>
<TextField fx:id="filePathField" maxWidth="700.0" promptText="FILE PATH"
HBox.hgrow="ALWAYS"/>
<JFXButton fx:id="clearFilePathButton" contentDisplay="CENTER"
graphicTextGap="0.0" maxHeight="35.0" onAction="#clearFilePath"
ripplerFill="#ffffff00" textFill="WHITE">
<graphic>
<ImageView fitHeight="25.0" fitWidth="25.0" pickOnBounds="true"
preserveRatio="true">
<image>
<Image url="@../../assets/BackspaceArrow.png"/>
</image>
</ImageView>
</graphic>
</JFXButton>
</children>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0"/>
</padding>
</HBox>
<JFXButton fx:id="browseButton" buttonType="RAISED" graphicTextGap="10.0"
minWidth="80.0" onAction="#browseFile" ripplerFill="BLACK" text="BROWSE">
<graphic>
<ImageView fitHeight="18.0" fitWidth="18.0" pickOnBounds="true"
preserveRatio="true">
<image>
<Image url="@../../assets/File.png"/>
</image>
</ImageView>
</graphic>
<font>
<Font size="12.0"/>
</font>
</JFXButton>
</children> </children>
</HBox> </HBox>
</children>
</VBox>
</content> </content>
</Tab> </Tab>
</tabs> </tabs>

View file

@ -22,202 +22,215 @@
<?import javafx.scene.image.*?> <?import javafx.scene.image.*?>
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?> <?import javafx.scene.text.*?>
<StackPane fx:id="dashboard" stylesheets="@../../css/Adreana.css" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.rohitawate.everest.controllers.DashboardController"> <VBox fx:id="dashboard" alignment="CENTER" stylesheets="@../../css/Adreana.css" xmlns="http://javafx.com/javafx/8.0.141"
xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.rohitawate.everest.controllers.DashboardController">
<children> <children>
<VBox> <HBox alignment="CENTER" maxHeight="70.0" minHeight="70.0" spacing="20.0" VBox.vgrow="ALWAYS">
<children> <children>
<HBox alignment="CENTER" maxHeight="100.0" minHeight="100.0" spacing="20.0" VBox.vgrow="ALWAYS"> <ImageView fitHeight="40.0" fitWidth="40.0" pickOnBounds="true" preserveRatio="true"
<children> HBox.hgrow="ALWAYS">
<ImageView fitHeight="50.0" fitWidth="50.0" pickOnBounds="true" preserveRatio="true" HBox.hgrow="ALWAYS">
<image> <image>
<Image url="@../../assets/Logo.png" /> <Image url="@../../assets/Logo.png"/>
</image> </image>
</ImageView> </ImageView>
<HBox fx:id="addressSection" alignment="CENTER" maxHeight="40.0" HBox.hgrow="ALWAYS"> <HBox fx:id="addressSection" alignment="CENTER" maxHeight="30.0" maxWidth="800.0" HBox.hgrow="ALWAYS">
<children> <children>
<StackPane fx:id="comboContainer" minHeight="40.0" minWidth="130.0"> <StackPane fx:id="comboContainer" maxHeight="35.0" minHeight="35.0" minWidth="100.0">
<children> <children>
<ComboBox fx:id="httpMethodBox" minHeight="40.0" minWidth="130.0" StackPane.alignment="CENTER" /> <ComboBox fx:id="httpMethodBox" minHeight="35.0" minWidth="100.0"
StackPane.alignment="CENTER"/>
</children> </children>
</StackPane> </StackPane>
<TextField fx:id="addressField" promptText="URL" HBox.hgrow="ALWAYS"> <TextField fx:id="addressField" maxHeight="35.0" minHeight="35.0" promptText="URL"
HBox.hgrow="ALWAYS">
<font> <font>
<Font size="18.0" /> <Font size="15.0"/>
</font> </font>
</TextField> </TextField>
</children> <StackPane fx:id="sendButtonPane" maxHeight="35.0" HBox.hgrow="SOMETIMES">
</HBox> <HBox.margin>
<JFXButton fx:id="sendButton" buttonType="RAISED" defaultButton="true" minWidth="110.0" onAction="#sendRequest" prefHeight="39.0" ripplerFill="WHITE" text=" SEND" textAlignment="CENTER" textFill="WHITE" HBox.hgrow="ALWAYS"> <Insets/>
</HBox.margin>
<children>
<JFXButton fx:id="sendButton" defaultButton="true" graphicTextGap="10.0" minWidth="80.0"
onAction="#sendRequest" prefHeight="30.0" ripplerFill="WHITE" text="SEND"
textAlignment="CENTER" textFill="WHITE">
<padding> <padding>
<Insets bottom="5.0" left="15.0" right="15.0" top="5.0" /> <Insets bottom="5.0" left="15.0" right="15.0" top="5.0"/>
</padding> </padding>
<font> <font>
<Font size="18.0" /> <Font size="15.0"/>
</font> </font>
<HBox.margin>
<Insets />
</HBox.margin>
<graphic> <graphic>
<ImageView fitHeight="15.0" fitWidth="15.0" pickOnBounds="true" preserveRatio="true"> <ImageView fitHeight="15.0" fitWidth="15.0" pickOnBounds="true"
preserveRatio="true">
<image> <image>
<Image url="@../../assets/Send.png" /> <Image url="@../../assets/Send.png"/>
</image> </image>
</ImageView> </ImageView>
</graphic> </graphic>
<StackPane.margin>
<Insets bottom="1.0" left="1.0" right="1.0" top="1.0"/>
</StackPane.margin>
</JFXButton> </JFXButton>
</children> </children>
</StackPane>
</children>
</HBox>
</children>
<padding> <padding>
<Insets left="20.0" right="20.0" /> <Insets left="20.0" right="20.0"/>
</padding> </padding>
</HBox> </HBox>
<SplitPane dividerPositions="0.1" orientation="VERTICAL" VBox.vgrow="ALWAYS"> <SplitPane dividerPositions="0.1" orientation="VERTICAL" VBox.vgrow="ALWAYS">
<items> <items>
<AnchorPane maxHeight="300.0"> <AnchorPane maxHeight="300.0">
<children> <children>
<TabPane fx:id="requestOptionsTab" minHeight="190.0" tabClosingPolicy="UNAVAILABLE" tabMinWidth="150.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> <TabPane fx:id="requestOptionsTab" minHeight="190.0" tabClosingPolicy="UNAVAILABLE"
tabMinWidth="150.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<tabs> <tabs>
<Tab fx:id="paramsTab" text="PARAMS"> <Tab fx:id="paramsTab" text="PARAMS">
<content> <content>
<VBox> <VBox>
<children> <children>
<HBox alignment="CENTER" maxHeight="0.0" spacing="20.0" VBox.vgrow="ALWAYS"> <ScrollPane fitToHeight="true" fitToWidth="true" hbarPolicy="NEVER"
<VBox.margin> VBox.vgrow="ALWAYS">
<Insets />
</VBox.margin>
<children>
<JFXButton fx:id="newParamButton" onAction="#addParamField" text=" NEW PARAM" textFill="WHITE" HBox.hgrow="ALWAYS">
<graphic>
<ImageView fitHeight="15.0" fitWidth="15.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../../assets/Plus.png" />
</image>
</ImageView>
</graphic>
<HBox.margin>
<Insets />
</HBox.margin>
</JFXButton>
<JFXButton fx:id="appendParamsButton" onAction="#appendParams" ripplerFill="WHITE" text=" APPEND PARAMS" textFill="WHITE" HBox.hgrow="ALWAYS">
<graphic>
<ImageView fitHeight="15.0" fitWidth="15.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../../assets/CheckMark.png" />
</image>
</ImageView>
</graphic>
<HBox.margin>
<Insets />
</HBox.margin>
</JFXButton>
</children>
<padding>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
</padding>
</HBox>
<ScrollPane fitToHeight="true" fitToWidth="true" hbarPolicy="NEVER" VBox.vgrow="ALWAYS">
<content> <content>
<VBox fx:id="paramsBox" alignment="TOP_CENTER" /> <VBox fx:id="paramsBox" alignment="TOP_CENTER">
<padding>
<Insets bottom="10.0" top="10.0"/>
</padding>
</VBox>
</content> </content>
</ScrollPane> </ScrollPane>
</children> </children>
</VBox> </VBox>
</content> </content>
</Tab> </Tab>
<Tab fx:id="authTab" text="AUTHORIZATION" /> <Tab fx:id="authTab" text="AUTHORIZATION"/>
<Tab fx:id="headersTab" text="HEADERS" /> <Tab fx:id="headersTab" text="HEADERS"/>
<Tab fx:id="bodyTab" text="BODY" /> <Tab fx:id="bodyTab" text="BODY"/>
</tabs> </tabs>
</TabPane> </TabPane>
</children> </children>
</AnchorPane> </AnchorPane>
<AnchorPane> <AnchorPane>
<children> <children>
<VBox fx:id="responseBox" alignment="CENTER" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> <VBox fx:id="responseBox" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children> <children>
<HBox fx:id="responseDetails" alignment="CENTER_RIGHT" maxHeight="50.0" minHeight="50.0" spacing="30.0" VBox.vgrow="ALWAYS"> <StackPane VBox.vgrow="ALWAYS">
<children>
<VBox fx:id="responseLayer">
<children>
<HBox fx:id="responseDetails" alignment="CENTER_RIGHT" maxHeight="40.0"
minHeight="40.0" spacing="30.0" VBox.vgrow="ALWAYS">
<children> <children>
<HBox alignment="CENTER_LEFT" HBox.hgrow="ALWAYS"> <HBox alignment="CENTER_LEFT" HBox.hgrow="ALWAYS">
<children> <children>
<Label fx:id="statusCode" text="404" textFill="WHITE" HBox.hgrow="ALWAYS"> <Label fx:id="statusCode" text="404" textFill="WHITE"
HBox.hgrow="ALWAYS">
<font> <font>
<Font name="System Bold" size="35.0" /> <Font name="System Bold" size="22.0"/>
</font> </font>
<HBox.margin> <HBox.margin>
<Insets right="10.0" /> <Insets right="10.0"/>
</HBox.margin> </HBox.margin>
</Label> </Label>
<Label fx:id="statusCodeDescription" text="Not Found" textFill="WHITE" HBox.hgrow="ALWAYS"> <Label fx:id="statusCodeDescription" text="Not Found"
textFill="WHITE" HBox.hgrow="ALWAYS">
<font> <font>
<Font size="30.0" /> <Font size="18.0"/>
</font> </font>
</Label> </Label>
</children> </children>
</HBox> </HBox>
<ComboBox fx:id="responseTypeBox" minHeight="30.0" prefWidth="100.0"/> <ComboBox fx:id="responseTypeBox" maxHeight="25.0"
<Label fx:id="responseTime" text="151 ms" textFill="WHITE" HBox.hgrow="ALWAYS"> minHeight="25.0" prefWidth="120.0"
styleClass="content-type-combo-box"/>
<Label fx:id="responseTime" text="151 ms" textFill="WHITE"
HBox.hgrow="ALWAYS">
<HBox.margin> <HBox.margin>
<Insets /> <Insets/>
</HBox.margin> </HBox.margin>
<font> <font>
<Font name="Liberation Mono" size="17.0" /> <Font name="Liberation Mono" size="15.0"/>
</font> </font>
</Label> </Label>
<Label fx:id="responseSize" layoutX="1187.0" layoutY="23.0" text="1998 B" textFill="WHITE" HBox.hgrow="ALWAYS"> <Label fx:id="responseSize" layoutX="1187.0" layoutY="23.0"
text="1998 B" textFill="WHITE" HBox.hgrow="ALWAYS">
<font> <font>
<Font name="Liberation Mono" size="17.0" /> <Font name="Liberation Mono" size="15.0"/>
</font> </font>
<HBox.margin> <HBox.margin>
<Insets /> <Insets/>
</HBox.margin> </HBox.margin>
</Label> </Label>
<JFXButton fx:id="copyBodyButton" textFill="WHITE"> <JFXButton fx:id="copyBodyButton" textFill="WHITE"
HBox.hgrow="ALWAYS">
<graphic> <graphic>
<ImageView fitHeight="20.0" fitWidth="20.0" pickOnBounds="true" preserveRatio="true"> <ImageView fitHeight="20.0" fitWidth="20.0"
pickOnBounds="true" preserveRatio="true">
<image> <image>
<Image url="@../../assets/Copy.png" /> <Image url="@../../assets/Copy.png"/>
</image> </image>
</ImageView> </ImageView>
</graphic> </graphic>
<font>
<Font size="12.0"/>
</font>
</JFXButton> </JFXButton>
<JFXButton fx:id="clearResponseAreaButton" buttonType="RAISED" onAction="#clearResponseArea" ripplerFill="WHITE" text=" CLEAR" textFill="WHITE" HBox.hgrow="ALWAYS"> <JFXButton fx:id="clearResponseAreaButton" buttonType="RAISED"
maxHeight="20.0" onAction="#clearResponseArea"
ripplerFill="WHITE" text=" CLEAR" textFill="WHITE"
HBox.hgrow="ALWAYS">
<graphic> <graphic>
<ImageView fitHeight="15.0" fitWidth="15.0" pickOnBounds="true" preserveRatio="true"> <ImageView fitHeight="15.0" fitWidth="15.0"
pickOnBounds="true" preserveRatio="true">
<image> <image>
<Image url="@../../assets/CrossMark.png" /> <Image url="@../../assets/CrossMark.png"/>
</image> </image>
</ImageView> </ImageView>
</graphic> </graphic>
<tooltip> <tooltip>
<Tooltip autoHide="true" text="Clears this bar and the response body below." /> <Tooltip autoHide="true"
text="Clears this bar and the response body below."/>
</tooltip> </tooltip>
<font>
<Font size="12.0"/>
</font>
</JFXButton> </JFXButton>
</children> </children>
<padding> <padding>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" /> <Insets bottom="15.0" left="15.0" right="15.0" top="15.0"/>
</padding> </padding>
</HBox> </HBox>
<StackPane VBox.vgrow="SOMETIMES"> <TabPane fx:id="responseTabPane" side="BOTTOM" tabMinWidth="100.0"
<children> VBox.vgrow="ALWAYS">
<TabPane fx:id="responseTabPane" side="BOTTOM" tabMinWidth="100.0">
<tabs> <tabs>
<Tab fx:id="responseBodyTab" closable="false" text="BODY"/> <Tab fx:id="responseBodyTab" closable="false" text="BODY"/>
<Tab fx:id="visualizerTab" closable="false" text="VISUALIZER" /> <Tab fx:id="visualizerTab" closable="false" text="VISUALIZER"/>
<Tab fx:id="responseHeadersTab" closable="false" text="HEADERS" /> <Tab fx:id="responseHeadersTab" closable="false"
text="HEADERS"/>
</tabs> </tabs>
</TabPane> </TabPane>
<VBox fx:id="loadingLayer" alignment="CENTER" spacing="10.0"> </children>
</VBox>
<VBox fx:id="loadingLayer" alignment="CENTER" spacing="10.0" visible="false">
<children> <children>
<Label text="LOADING" textFill="WHITE"> <Label text="LOADING" textFill="WHITE">
<font> <font>
<Font size="70.0" /> <Font size="70.0"/>
</font> </font>
</Label> </Label>
<JFXProgressBar fx:id="progressBar" VBox.vgrow="ALWAYS" /> <JFXProgressBar fx:id="progressBar" VBox.vgrow="ALWAYS"/>
<JFXButton fx:id="cancelButton" text=" CANCEL" textFill="WHITE"> <JFXButton fx:id="cancelButton" text=" CANCEL" textFill="WHITE">
<graphic> <graphic>
<ImageView fitHeight="15.0" fitWidth="15.0" pickOnBounds="true" preserveRatio="true"> <ImageView fitHeight="15.0" fitWidth="15.0" pickOnBounds="true"
preserveRatio="true">
<image> <image>
<Image url="@../../assets/CrossMark.png" /> <Image url="@../../assets/CrossMark.png"/>
</image> </image>
</ImageView> </ImageView>
</graphic> </graphic>
@ -226,28 +239,34 @@
</VBox> </VBox>
<VBox fx:id="promptLayer" alignment="CENTER" visible="false"> <VBox fx:id="promptLayer" alignment="CENTER" visible="false">
<children> <children>
<Label text="Enter an address, select a method and hit send." textFill="WHITE"> <Label text="Enter an address, select a method and hit send."
textFill="WHITE">
<font> <font>
<Font size="32.0" /> <Font size="32.0"/>
</font> </font>
</Label> </Label>
</children> </children>
</VBox> </VBox>
<VBox fx:id="errorLayer" alignment="CENTER" layoutX="10.0" layoutY="10.0" visible="false"> <VBox fx:id="errorLayer" alignment="CENTER" layoutX="10.0" layoutY="10.0"
visible="false">
<children> <children>
<ImageView fitHeight="100.0" fitWidth="100.0" opacity="0.75" pickOnBounds="true" preserveRatio="true"> <ImageView fitHeight="100.0" fitWidth="100.0" opacity="0.75"
pickOnBounds="true" preserveRatio="true">
<image> <image>
<Image url="@../../assets/Explosion.png" /> <Image url="@../../assets/Explosion.png"/>
</image> </image>
</ImageView> </ImageView>
<Label fx:id="errorTitle" text="Error title" textFill="WHITE"> <Label fx:id="errorTitle" text="Oops... That's embarrassing!"
textFill="WHITE">
<font> <font>
<Font name="System Bold" size="32.0" /> <Font name="System Bold" size="32.0"/>
</font> </font>
</Label> </Label>
<Label fx:id="errorDetails" text="Error details" textAlignment="CENTER" textFill="#c3c3c3"> <Label fx:id="errorDetails"
text="Something went wrong. Try to make another request.Restart Everest if that doesn't work."
textAlignment="CENTER" textFill="#c3c3c3">
<font> <font>
<Font size="22.0" /> <Font size="22.0"/>
</font> </font>
</Label> </Label>
</children> </children>
@ -262,8 +281,6 @@
</SplitPane> </SplitPane>
</children> </children>
<padding> <padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" /> <Insets bottom="10.0" left="10.0" right="10.0"/>
</padding> </padding>
</VBox> </VBox>
</children>
</StackPane>

View file

@ -16,58 +16,20 @@
~ limitations under the License. ~ limitations under the License.
--> -->
<?import com.jfoenix.controls.JFXButton?> <?import javafx.geometry.Insets?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.ScrollPane?> <?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.image.Image?> <?import javafx.scene.layout.VBox?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.*?>
<VBox fx:id="headerTabContent" alignment="CENTER" stylesheets="@../../css/Adreana.css" <VBox fx:id="headerTabContent" alignment="CENTER" stylesheets="@../../css/Adreana.css"
xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.rohitawate.everest.controllers.FormDataTabController"> fx:controller="com.rohitawate.everest.controllers.FormDataTabController">
<children> <children>
<HBox alignment="CENTER" VBox.vgrow="NEVER">
<VBox.margin>
<Insets/>
</VBox.margin>
<children>
<JFXButton fx:id="newStringKVButton" buttonType="RAISED" minWidth="100.0" onAction="#addStringField"
ripplerFill="WHITE" text=" STRING" textFill="WHITE" HBox.hgrow="ALWAYS">
<padding>
<Insets bottom="5.0" left="10.0" right="10.0" top="5.0"/>
</padding>
<graphic>
<ImageView fitHeight="15.0" fitWidth="15.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../../assets/Plus.png"/>
</image>
</ImageView>
</graphic>
<HBox.margin>
<Insets right="20.0"/>
</HBox.margin>
</JFXButton>
<JFXButton fx:id="newFileKVButton" buttonType="RAISED" layoutX="35.0" layoutY="25.0" minWidth="100.0"
onAction="#addFileField" ripplerFill="WHITE" text=" FILE" textFill="WHITE">
<padding>
<Insets bottom="5.0" left="10.0" right="10.0" top="5.0"/>
</padding>
<graphic>
<ImageView fitHeight="15.0" fitWidth="15.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../../assets/Plus.png"/>
</image>
</ImageView>
</graphic>
</JFXButton>
</children>
<padding>
<Insets bottom="10.0" left="25.0" right="10.0" top="15.0"/>
</padding>
</HBox>
<ScrollPane fitToHeight="true" fitToWidth="true" hbarPolicy="NEVER" VBox.vgrow="ALWAYS"> <ScrollPane fitToHeight="true" fitToWidth="true" hbarPolicy="NEVER" VBox.vgrow="ALWAYS">
<content> <content>
<VBox fx:id="fieldsBox" alignment="TOP_CENTER"/> <VBox fx:id="fieldsBox" alignment="TOP_CENTER">
<padding>
<Insets bottom="10.0" top="10.0"/>
</padding>
</VBox>
</content> </content>
</ScrollPane> </ScrollPane>
</children> </children>

View file

@ -16,42 +16,20 @@
~ limitations under the License. ~ limitations under the License.
--> -->
<?import com.jfoenix.controls.JFXButton?>
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import javafx.scene.control.ScrollPane?> <?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.image.Image?> <?import javafx.scene.layout.VBox?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.*?>
<VBox fx:id="headerTabContent" alignment="CENTER" stylesheets="@../../css/Adreana.css" <VBox fx:id="headerTabContent" alignment="CENTER" stylesheets="@../../css/Adreana.css"
xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.rohitawate.everest.controllers.HeaderTabController"> fx:controller="com.rohitawate.everest.controllers.HeaderTabController">
<children> <children>
<HBox alignment="CENTER" VBox.vgrow="NEVER">
<VBox.margin>
<Insets/>
</VBox.margin>
<children>
<JFXButton fx:id="addHeaderButton" buttonType="RAISED" onAction="#addHeader" text=" NEW HEADER"
textFill="WHITE" HBox.hgrow="ALWAYS">
<padding>
<Insets bottom="5.0" left="10.0" right="10.0" top="5.0"/>
</padding>
<graphic>
<ImageView fitHeight="15.0" fitWidth="15.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../../assets/Plus.png"/>
</image>
</ImageView>
</graphic>
</JFXButton>
</children>
<padding>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0"/>
</padding>
</HBox>
<ScrollPane fitToHeight="true" fitToWidth="true" hbarPolicy="NEVER" VBox.vgrow="ALWAYS"> <ScrollPane fitToHeight="true" fitToWidth="true" hbarPolicy="NEVER" VBox.vgrow="ALWAYS">
<content> <content>
<VBox fx:id="headersBox" alignment="TOP_CENTER"/> <VBox fx:id="headersBox" alignment="TOP_CENTER">
<padding>
<Insets bottom="10.0" top="10.0"/>
</padding>
</VBox>
</content> </content>
</ScrollPane> </ScrollPane>
</children> </children>

View file

@ -20,24 +20,16 @@
<?import javafx.scene.control.Label?> <?import javafx.scene.control.Label?>
<?import javafx.scene.control.Tooltip?> <?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.text.Font?> <?import javafx.scene.text.Font?>
<HBox fx:id="historyItemBox" alignment="CENTER_LEFT" maxHeight="40.0" minHeight="40.0" minWidth="300.0" spacing="20.0" <HBox fx:id="historyItemBox" alignment="CENTER_LEFT" maxHeight="35.0" maxWidth="300.0" minHeight="35.0" minWidth="250.0"
stylesheets="@../../css/Adreana.css" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" spacing="10.0" stylesheets="@../../css/Adreana.css" xmlns="http://javafx.com/javafx/8.0.111"
fx:controller="com.rohitawate.everest.controllers.HistoryItemController"> xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.rohitawate.everest.controllers.HistoryItemController">
<children> <children>
<StackPane maxWidth="80.0" minWidth="80.0" HBox.hgrow="ALWAYS"> <Label fx:id="methodLabel" minWidth="50.0">
<children>
<Label fx:id="requestType" textFill="ORANGERED" textOverrun="WORD_ELLIPSIS">
<font>
<Font name="Liberation Mono Bold" size="18.0"/>
</font>
</Label> </Label>
</children> <Label fx:id="address" textFill="#bababa" textOverrun="WORD_ELLIPSIS" HBox.hgrow="ALWAYS">
</StackPane>
<Label fx:id="address" textFill="WHITE" HBox.hgrow="ALWAYS">
<font> <font>
<Font size="15.0" /> <Font size="13.0"/>
</font> </font>
<tooltip> <tooltip>
<Tooltip fx:id="tooltip" text="tooltip"/> <Tooltip fx:id="tooltip" text="tooltip"/>
@ -45,6 +37,6 @@
</Label> </Label>
</children> </children>
<padding> <padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" /> <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</padding> </padding>
</HBox> </HBox>

View file

@ -22,34 +22,33 @@
<?import javafx.scene.image.*?> <?import javafx.scene.image.*?>
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<?import javafx.scene.text.Font?> <?import javafx.scene.text.Font?>
<VBox fx:id="searchPane" alignment="TOP_CENTER" maxWidth="310.0" minWidth="310.0" stylesheets="@../../css/Adreana.css"
<VBox fx:id="searchPane" xmlns="http://javafx.com/javafx/8.0.141" SplitPane.resizableWithParent="false" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1"
xmlns:fx="http://javafx.com/fxml/1" alignment="TOP_CENTER" maxWidth="450.0" fx:controller="com.rohitawate.everest.controllers.HistoryPaneController">
minWidth="400.0" SplitPane.resizableWithParent="false" fx:controller="com.rohitawate.everest.controllers.HistoryPaneController">
<children> <children>
<HBox fx:id="historySearchFieldBox" alignment="CENTER" <HBox fx:id="historySearchFieldBox" alignment="CENTER" fillHeight="false">
fillHeight="false">
<VBox.margin> <VBox.margin>
<Insets /> <Insets />
</VBox.margin> </VBox.margin>
<children> <children>
<ImageView fitHeight="20.0" fitWidth="20.0" <ImageView fitHeight="15.0" fitWidth="15.0" pickOnBounds="true" preserveRatio="true"
pickOnBounds="true" preserveRatio="true" HBox.hgrow="ALWAYS"> HBox.hgrow="ALWAYS">
<image> <image>
<Image url="@../../assets/Search.png" /> <Image url="@../../assets/Search.png" />
</image> </image>
</ImageView> </ImageView>
<TextField fx:id="searchTextField" styleClass="searchTextField" promptText="SEARCH HISTORY" <TextField fx:id="searchTextField" promptText="SEARCH HISTORY" styleClass="searchTextField"
HBox.hgrow="ALWAYS"> HBox.hgrow="ALWAYS">
<padding> <padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" /> <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding> </padding>
<font>
<Font size="13.0"/>
</font>
</TextField> </TextField>
<JFXButton fx:id="clearSearchFieldButton" <JFXButton fx:id="clearSearchFieldButton" ripplerFill="WHITE" HBox.hgrow="ALWAYS">
ripplerFill="WHITE" HBox.hgrow="ALWAYS">
<graphic> <graphic>
<ImageView fitHeight="20.0" fitWidth="20.0" <ImageView fitHeight="20.0" fitWidth="20.0" pickOnBounds="true" preserveRatio="true">
pickOnBounds="true" preserveRatio="true">
<image> <image>
<Image url="@../../assets/BackspaceArrow.png" /> <Image url="@../../assets/BackspaceArrow.png" />
</image> </image>
@ -58,29 +57,26 @@
</JFXButton> </JFXButton>
</children> </children>
<padding> <padding>
<Insets bottom="10.0" left="20.0" right="20.0" top="10.0" /> <Insets bottom="5.0" left="20.0" right="10.0" top="5.0"/>
</padding> </padding>
</HBox> </HBox>
<StackPane VBox.vgrow="ALWAYS"> <StackPane VBox.vgrow="ALWAYS">
<children> <children>
<StackPane> <StackPane>
<children> <children>
<ScrollPane fx:id="historyScrollPane" <ScrollPane fx:id="historyScrollPane" fitToHeight="true" fitToWidth="true" hbarPolicy="NEVER">
fitToHeight="true" fitToWidth="true" hbarPolicy="NEVER">
<content> <content>
<VBox fx:id="searchTab" alignment="TOP_CENTER" <VBox fx:id="searchTab" alignment="TOP_CENTER" spacing="5.0">
spacing="5.0">
<padding> <padding>
<Insets bottom="20.0" left="20.0" right="20.0" <Insets bottom="20.0" left="20.0" right="20.0" top="20.0"/>
top="20.0" />
</padding> </padding>
</VBox> </VBox>
</content> </content>
</ScrollPane> </ScrollPane>
<StackPane fx:id="searchPromptLayer"> <StackPane fx:id="searchPromptLayer">
<children> <children>
<Label text="YOUR REQUESTS HISTORY WILL APPEAR HERE" <Label text="YOUR REQUESTS HISTORY WILL APPEAR HERE" textAlignment="CENTER"
textAlignment="CENTER" textFill="#575757" wrapText="true"> textFill="#575757" wrapText="true">
<font> <font>
<Font size="25.0" /> <Font size="25.0" />
</font> </font>
@ -96,42 +92,38 @@
<children> <children>
<VBox alignment="TOP_CENTER"> <VBox alignment="TOP_CENTER">
<children> <children>
<Label graphicTextGap="10.0" text="SEARCH RESULTS" <Label graphicTextGap="10.0" text="SEARCH RESULTS" textFill="#4ab98e">
textFill="#199F6F">
<VBox.margin> <VBox.margin>
<Insets bottom="10.0" left="10.0" right="10.0" <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
top="10.0" />
</VBox.margin> </VBox.margin>
<font> <font>
<Font size="19.0" /> <Font size="19.0" />
</font> </font>
</Label> </Label>
<ScrollPane fx:id="searchScrollPane" <ScrollPane fx:id="searchScrollPane" fitToHeight="true" fitToWidth="true"
fitToHeight="true" fitToWidth="true" hbarPolicy="NEVER"> hbarPolicy="NEVER">
<content> <content>
<VBox fx:id="searchBox" alignment="TOP_CENTER" <VBox fx:id="searchBox" alignment="TOP_CENTER" spacing="5.0">
spacing="5.0">
<padding> <padding>
<Insets bottom="20.0" left="20.0" right="20.0" <Insets bottom="20.0" left="20.0" right="20.0" top="20.0"/>
top="20.0" />
</padding> </padding>
</VBox> </VBox>
</content> </content>
</ScrollPane> </ScrollPane>
</children> </children>
</VBox> </VBox>
<StackPane fx:id="searchFailedLayer"> <StackPane fx:id="searchFailedLayer" visible="false">
<children> <children>
<VBox alignment="CENTER"> <VBox alignment="CENTER">
<children> <children>
<ImageView fitHeight="100.0" fitWidth="100.0" <ImageView fitHeight="100.0" fitWidth="100.0" opacity="0.51" pickOnBounds="true"
opacity="0.51" pickOnBounds="true" preserveRatio="true"> preserveRatio="true">
<image> <image>
<Image url="@../../assets/Explosion.png" /> <Image url="@../../assets/Explosion.png" />
</image> </image>
</ImageView> </ImageView>
<Label text="NO RESULTS" textAlignment="CENTER" <Label text="NO RESULTS" textAlignment="CENTER" textFill="#bfbfbf"
textFill="#bfbfbf" wrapText="true" VBox.vgrow="ALWAYS"> wrapText="true" VBox.vgrow="ALWAYS">
<font> <font>
<Font size="25.0" /> <Font size="25.0" />
</font> </font>

View file

@ -16,23 +16,40 @@
~ limitations under the License. ~ limitations under the License.
--> -->
<?import com.jfoenix.controls.JFXButton?>
<?import javafx.scene.control.SplitPane?> <?import javafx.scene.control.SplitPane?>
<?import javafx.scene.control.TabPane?> <?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.StackPane?> <?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<StackPane fx:id="homeWindowSP" <?import javafx.scene.layout.*?>
stylesheets="@../../css/Adreana.css" <StackPane fx:id="homeWindowSP" stylesheets="@../../css/Adreana.css" xmlns="http://javafx.com/javafx/8.0.141"
xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.rohitawate.everest.controllers.HomeWindowController">
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.rohitawate.everest.controllers.HomeWindowController">
<children> <children>
<SplitPane fx:id="splitPane" dividerPositions="0.3"> <SplitPane fx:id="splitPane">
<items> <items>
<fx:include fx:id="historyPane" source="HistoryPane.fxml" /> <VBox fx:id="tabDashboardBox" alignment="TOP_CENTER" SplitPane.resizableWithParent="false">
<TabPane fx:id="homeWindowTabPane" <children>
tabClosingPolicy="ALL_TABS" tabMaxHeight="30.0" tabMaxWidth="200.0" <HBox VBox.vgrow="NEVER">
tabMinHeight="30.0" tabMinWidth="200.0" <children>
SplitPane.resizableWithParent="false" /> <JFXButton fx:id="newTabButton" contentDisplay="CENTER" minHeight="35.0" minWidth="40.0"
onAction="#addTab" ripplerFill="WHITE" HBox.hgrow="ALWAYS">
<graphic>
<ImageView fitHeight="15.0" fitWidth="15.0" pickOnBounds="true"
preserveRatio="true">
<image>
<Image url="@../../assets/NewTabPlus.png"/>
</image>
</ImageView>
</graphic>
</JFXButton>
<TabPane fx:id="tabPane" maxHeight="35.0" minHeight="35.0" minWidth="220.0"
tabClosingPolicy="ALL_TABS" tabMaxHeight="35.0" tabMaxWidth="200.0"
tabMinHeight="35.0" tabMinWidth="200.0" HBox.hgrow="ALWAYS"/>
</children>
</HBox>
<StackPane fx:id="dashboardContainer" VBox.vgrow="ALWAYS"/>
</children>
</VBox>
</items> </items>
</SplitPane> </SplitPane>
</children> </children>

View file

@ -16,41 +16,19 @@
~ limitations under the License. ~ limitations under the License.
--> -->
<?import com.jfoenix.controls.JFXButton?>
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import javafx.scene.control.ScrollPane?> <?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.image.Image?> <?import javafx.scene.layout.VBox?>
<?import javafx.scene.image.ImageView?> <VBox stylesheets="@../../css/Adreana.css" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1"
<?import javafx.scene.layout.*?>
<VBox stylesheets="@../../css/Adreana.css" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.rohitawate.everest.controllers.URLTabController"> fx:controller="com.rohitawate.everest.controllers.URLTabController">
<children> <children>
<HBox alignment="CENTER" VBox.vgrow="NEVER">
<VBox.margin>
<Insets/>
</VBox.margin>
<children>
<JFXButton fx:id="newStringKVButton" onAction="#addField" text=" NEW FIELD" textFill="WHITE"
HBox.hgrow="ALWAYS">
<graphic>
<ImageView fitHeight="15.0" fitWidth="15.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../../assets/Plus.png"/>
</image>
</ImageView>
</graphic>
<HBox.margin>
<Insets/>
</HBox.margin>
</JFXButton>
</children>
<padding>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0"/>
</padding>
</HBox>
<ScrollPane fitToHeight="true" fitToWidth="true" hbarPolicy="NEVER" VBox.vgrow="ALWAYS"> <ScrollPane fitToHeight="true" fitToWidth="true" hbarPolicy="NEVER" VBox.vgrow="ALWAYS">
<content> <content>
<VBox fx:id="fieldsBox" alignment="TOP_CENTER"/> <VBox fx:id="fieldsBox" alignment="TOP_CENTER">
<padding>
<Insets bottom="10.0" top="10.0"/>
</padding>
</VBox>
</content> </content>
</ScrollPane> </ScrollPane>
</children> </children>

View file

@ -1,18 +0,0 @@
{
"createRequestsTable": "CREATE TABLE IF NOT EXISTS Requests(ID INTEGER PRIMARY KEY, Type TEXT NOT NULL, Target TEXT NOT NULL, Date TEXT NOT NULL)",
"createHeadersTable": "CREATE TABLE IF NOT EXISTS Headers(RequestID INTEGER, Key TEXT NOT NULL, Value TEXT NOT NULL, Checked INTEGER CHECK (Checked IN (0, 1)), FOREIGN KEY(RequestID) REFERENCES Requests(ID))",
"createRequestContentMapTable": "CREATE TABLE IF NOT EXISTS RequestContentMap(RequestID INTEGER, ContentType TEXT NOT NULL, FOREIGN KEY(RequestID) REFERENCES Requests(ID))",
"createBodiesTable": "CREATE TABLE IF NOT EXISTS Bodies(RequestID INTEGER, Body TEXT NOT NULL, FOREIGN KEY(RequestID) REFERENCES Requests(ID))",
"createTuplesTable": "CREATE TABLE IF NOT EXISTS Tuples(RequestID INTEGER, TupleType TEXT NOT NULL, Key TEXT NOT NULL, Value TEXT NOT NULL, Checked INTEGER CHECK (Checked IN (0, 1)), FOREIGN KEY(RequestID) REFERENCES Requests(ID))",
"saveRequest": "INSERT INTO Requests(Type, Target, Date) VALUES(?, ?, ?)",
"saveHeader": "INSERT INTO Headers(RequestID, Key, Value, Checked) VALUES(?, ?, ?, ?)",
"saveRequestContentPair": "INSERT INTO RequestContentMap(RequestID, ContentType) VALUES(?, ?)",
"saveBody": "INSERT INTO Bodies(RequestID, Body) VALUES(?, ?)",
"saveTuple": "INSERT INTO Tuples(RequestID, TupleType, Key, Value, Checked) VALUES(?, ?, ?, ?, ?)",
"selectRecentRequests": "SELECT * FROM Requests WHERE Requests.Date > ?",
"selectRequestHeaders": "SELECT * FROM Headers WHERE RequestID == ?",
"selectRequestContentType": "SELECT ContentType FROM RequestContentMap WHERE RequestID == ?",
"selectRequestBody": "SELECT Body FROM Bodies WHERE RequestID == ?",
"selectTuples": "SELECT * FROM Tuples WHERE RequestID == ? AND TupleType == ?",
"selectMostRecentRequest": "SELECT * FROM Requests ORDER BY ID DESC LIMIT 1"
}

View file

@ -54,4 +54,4 @@
</style> </style>
</head> </head>
<body> <body>
<h1>Everest Log: %% Date %%</h1> <h1>Everest Logs: %% Date %%</h1>