mirror of https://github.com/i2p-zero/i2p-zero
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
396 lines
16 KiB
396 lines
16 KiB
package org.getmonero.i2p.zero.gui;
|
|
|
|
import javafx.application.Platform;
|
|
import javafx.beans.binding.DoubleBinding;
|
|
import javafx.collections.FXCollections;
|
|
import javafx.collections.ObservableList;
|
|
import javafx.event.ActionEvent;
|
|
import javafx.event.Event;
|
|
import javafx.event.EventHandler;
|
|
import javafx.fxml.FXML;
|
|
import javafx.fxml.FXMLLoader;
|
|
import javafx.scene.Scene;
|
|
import javafx.scene.control.*;
|
|
import javafx.scene.control.Button;
|
|
import javafx.scene.control.Label;
|
|
import javafx.scene.control.TextArea;
|
|
import javafx.scene.control.TextField;
|
|
import javafx.scene.control.cell.PropertyValueFactory;
|
|
import javafx.scene.image.Image;
|
|
import javafx.scene.image.ImageView;
|
|
import javafx.scene.layout.AnchorPane;
|
|
import javafx.scene.layout.BorderPane;
|
|
import javafx.scene.layout.FlowPane;
|
|
import javafx.scene.text.Text;
|
|
import javafx.scene.text.TextFlow;
|
|
import javafx.stage.DirectoryChooser;
|
|
import javafx.stage.Modality;
|
|
import javafx.stage.Stage;
|
|
import org.getmonero.i2p.zero.RouterWrapper;
|
|
import org.getmonero.i2p.zero.TunnelControl;
|
|
import org.getmonero.i2p.zero.UpdateCheck;
|
|
|
|
import static org.getmonero.i2p.zero.TunnelControl.*;
|
|
|
|
import java.awt.*;
|
|
import java.io.File;
|
|
import java.net.URI;
|
|
import java.text.DecimalFormat;
|
|
import java.util.Optional;
|
|
import java.util.Properties;
|
|
import java.util.stream.Stream;
|
|
|
|
public class Controller {
|
|
|
|
private RouterWrapper routerWrapper;
|
|
|
|
@FXML private BorderPane rootBorderPane;
|
|
@FXML private Slider bandwidthSlider;
|
|
@FXML private Label maxBandwidthLabel;
|
|
@FXML private ImageView masterToggle;
|
|
@FXML private AnchorPane bandwidthDisabledOverlay;
|
|
@FXML private Tab bandwidthTab;
|
|
@FXML private Tab tunnelsTab;
|
|
@FXML private Tab eepSiteTab;
|
|
@FXML private Tab helpTab;
|
|
@FXML private Label statusLabel;
|
|
@FXML private Button tunnelAddButton;
|
|
@FXML private Button tunnelRemoveButton;
|
|
@FXML private TableView<Tunnel> tunnelsTableView;
|
|
@FXML private TableColumn typeCol;
|
|
@FXML private TableColumn stateCol;
|
|
@FXML private TableColumn hostCol;
|
|
@FXML private TableColumn portCol;
|
|
@FXML private TableColumn i2PCol;
|
|
@FXML private Label bandwidthIn1s;
|
|
@FXML private Label bandwidthIn5m;
|
|
@FXML private Label bandwidthInAll;
|
|
@FXML private Label totalTransferredIn;
|
|
@FXML private Label bandwidthOut1s;
|
|
@FXML private Label bandwidthOut5m;
|
|
@FXML private Label bandwidthOutAll;
|
|
@FXML private Label totalTransferredOut;
|
|
@FXML private CheckBox eepSiteEnableCheckbox;
|
|
@FXML private TextField eepSiteAddrField;
|
|
@FXML private TextField eepSiteSecretKeyField;
|
|
@FXML private Button eepSiteGenButton;
|
|
@FXML private TextField eepSiteContentDirField;
|
|
@FXML private Button eepSiteContentDirChooseButton;
|
|
@FXML private TextField eepSiteLogsDirField;
|
|
@FXML private Button eepSiteLogsDirChooseButton;
|
|
@FXML private CheckBox eepSiteEnableLogsCheckbox;
|
|
@FXML private CheckBox eepSiteAllowDirBrowsingCheckbox;
|
|
@FXML private TextField eepSiteLocalPortField;
|
|
@FXML private TextArea helpTextArea;
|
|
|
|
|
|
DecimalFormat format2dp = new DecimalFormat("0.00");
|
|
private boolean masterState = true;
|
|
private boolean shuttingDown = false;
|
|
private final ObservableList<Tunnel> tunnelTableList = FXCollections.observableArrayList();
|
|
private boolean eepSiteTabInitialized = false;
|
|
|
|
private Stage getStage() {
|
|
return (Stage) rootBorderPane.getScene().getWindow();
|
|
}
|
|
|
|
@FXML private void initialize() {
|
|
|
|
DirectoryChooser directoryChooser = new DirectoryChooser();
|
|
|
|
typeCol.setCellValueFactory(new PropertyValueFactory<Tunnel,String>("type"));
|
|
stateCol.setCellValueFactory(new PropertyValueFactory<Tunnel,String>("state"));
|
|
hostCol.setCellValueFactory(new PropertyValueFactory<Tunnel,String>("host"));
|
|
portCol.setCellValueFactory(new PropertyValueFactory<Tunnel,String>("port"));
|
|
i2PCol.setCellValueFactory(new PropertyValueFactory<Tunnel,String>("I2P"));
|
|
|
|
DoubleBinding usedWidth = typeCol.widthProperty().add(stateCol.widthProperty()).add(hostCol.widthProperty()).add(portCol.widthProperty());
|
|
i2PCol.prefWidthProperty().bind(tunnelsTableView.widthProperty().subtract(usedWidth).subtract(2));
|
|
|
|
tunnelsTableView.setItems(tunnelTableList);
|
|
|
|
tunnelsTableView.getSelectionModel().selectedItemProperty().addListener((observableValue, oldSelection, newSelection) -> {
|
|
if (newSelection == null) {
|
|
tunnelRemoveButton.setDisable(true);
|
|
}
|
|
else {
|
|
tunnelRemoveButton.setDisable(false);
|
|
}
|
|
});
|
|
|
|
tunnelRemoveButton.setOnAction(e->{
|
|
Tunnel t = tunnelsTableView.getSelectionModel().getSelectedItem();
|
|
getRouterWrapper().getTunnelControl().getTunnelList().removeTunnel(t);
|
|
t.destroy(false);
|
|
tunnelRemoveButton.setDisable(true);
|
|
});
|
|
|
|
tunnelAddButton.setOnAction(event->{
|
|
try {
|
|
Stage dialogStage = new Stage();
|
|
dialogStage.initModality(Modality.WINDOW_MODAL);
|
|
dialogStage.initOwner(getStage());
|
|
dialogStage.setResizable(false);
|
|
dialogStage.setTitle("New tunnel");
|
|
Scene dialogScene = new Scene(FXMLLoader.load(getClass().getResource("addTunnel.fxml")));
|
|
dialogScene.getStylesheets().add("org/getmonero/i2p/zero/gui/gui.css");
|
|
dialogStage.setScene(dialogScene);
|
|
dialogStage.show();
|
|
} catch (Exception e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
});
|
|
|
|
EventHandler<Event> tabSelectionEventHandler = e->{
|
|
Stage stage = getStage();
|
|
if(bandwidthTab.isSelected()) {
|
|
stage.setWidth(360);
|
|
stage.setHeight(370);
|
|
}
|
|
else if(tunnelsTab.isSelected()) {
|
|
stage.setWidth(830);
|
|
}
|
|
else if(eepSiteTab.isSelected()) {
|
|
stage.setWidth(830);
|
|
stage.setHeight(500);
|
|
|
|
if(!eepSiteTabInitialized) {
|
|
EepSiteTunnel eepSiteTunnel = getEepSiteTunnel();
|
|
eepSiteSecretKeyField.setText(eepSiteTunnel.keyPair.toString());
|
|
eepSiteAddrField.setText("http://" + eepSiteTunnel.keyPair.b32Dest);
|
|
eepSiteContentDirField.setText(eepSiteTunnel.contentDir);
|
|
eepSiteLogsDirField.setText(eepSiteTunnel.logsDir);
|
|
eepSiteEnableCheckbox.setSelected(eepSiteTunnel.enabled);
|
|
eepSiteEnableLogsCheckbox.setSelected(eepSiteTunnel.enableLogs);
|
|
eepSiteAllowDirBrowsingCheckbox.setSelected(eepSiteTunnel.allowDirectoryBrowsing);
|
|
eepSiteLocalPortField.setText(eepSiteTunnel.port + "");
|
|
eepSiteTabInitialized = true;
|
|
|
|
// modifying eepSiteSecretKeyField/eepSiteLocalPortField will require the eepsite tunnel to be destroyed
|
|
// and later recreated whenever the user is finished editing settings and ticks the enabled box again
|
|
eepSiteSecretKeyField.textProperty().addListener((observable, oldValue, newValue) -> {
|
|
eepSiteEnableCheckbox.setSelected(false);
|
|
eepSiteAddrField.setText("");
|
|
String key = newValue;
|
|
if(key!=null && !key.isEmpty()) {
|
|
try {
|
|
KeyPair keyPair = new TunnelControl.KeyPair(key);
|
|
eepSiteAddrField.setText("http://" + keyPair.b32Dest);
|
|
getEepSiteTunnel().dest = keyPair.b32Dest;
|
|
getEepSiteTunnel().keyPair = keyPair;
|
|
save();
|
|
}
|
|
catch (Exception e2) {
|
|
// ignore exception. user may be part way through entering string
|
|
}
|
|
}
|
|
});
|
|
eepSiteGenButton.setOnAction(ev->{
|
|
getEepSiteTunnel().keyPair = KeyPair.gen();
|
|
save();
|
|
eepSiteSecretKeyField.setText(getEepSiteTunnel().keyPair.toString());
|
|
});
|
|
eepSiteLocalPortField.textProperty().addListener((ov, oldValue, newValue)->{
|
|
eepSiteEnableCheckbox.setSelected(false);
|
|
getEepSiteTunnel().port = Integer.parseInt(newValue);
|
|
save();
|
|
});
|
|
|
|
eepSiteEnableCheckbox.selectedProperty().addListener((ov, oldValue, newValue)->{
|
|
getEepSiteTunnel().enabled = newValue;
|
|
if(oldValue!=newValue) {
|
|
save();
|
|
getRouterWrapper().getTunnelControl().getTunnelList().fireChangeEvent();
|
|
if(getEepSiteTunnel().enabled) {
|
|
getEepSiteTunnel().start();
|
|
}
|
|
else {
|
|
getEepSiteTunnel().destroy(false);
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
// changing eepSiteContentDirChooseButton/eepSiteLogsDirChooseButton/eepSiteEnableLogsCheckbox/eepSiteAllowDirBrowsingCheckbox only requires a jetty restart
|
|
eepSiteContentDirChooseButton.setOnAction(ev->{
|
|
File selectedDirectory = directoryChooser.showDialog(getStage());
|
|
if (selectedDirectory!=null) {
|
|
eepSiteContentDirField.setText(selectedDirectory.getAbsolutePath());
|
|
getEepSiteTunnel().contentDir = selectedDirectory.getAbsolutePath();
|
|
save();
|
|
if(getEepSiteTunnel().enabled) restartJetty();
|
|
}
|
|
});
|
|
eepSiteLogsDirChooseButton.setOnAction(ev->{
|
|
File selectedDirectory = directoryChooser.showDialog(getStage());
|
|
if (selectedDirectory!=null) {
|
|
eepSiteLogsDirField.setText(selectedDirectory.getAbsolutePath());
|
|
getEepSiteTunnel().logsDir = selectedDirectory.getAbsolutePath();
|
|
save();
|
|
if(getEepSiteTunnel().enabled) restartJetty();
|
|
}
|
|
});
|
|
eepSiteEnableLogsCheckbox.selectedProperty().addListener((ov, oldValue, newValue)->{
|
|
getEepSiteTunnel().enableLogs = newValue;
|
|
save();
|
|
if(getEepSiteTunnel().enabled) restartJetty();
|
|
});
|
|
eepSiteAllowDirBrowsingCheckbox.selectedProperty().addListener((ov, oldValue, newValue)->{
|
|
getEepSiteTunnel().allowDirectoryBrowsing = newValue;
|
|
save();
|
|
if(getEepSiteTunnel().enabled) restartJetty();
|
|
});
|
|
|
|
}
|
|
}
|
|
};
|
|
Stream.of(bandwidthTab, tunnelsTab, eepSiteTab, helpTab).forEach(tab->tab.setOnSelectionChanged(tabSelectionEventHandler));
|
|
|
|
bandwidthSlider.valueProperty().addListener((observableValue, oldValue, newValue)-> {
|
|
maxBandwidthLabel.setText(String.format("%.1f", newValue.floatValue()) + " Mbps");
|
|
routerWrapper.debouncedUpdateBandwidthLimitKBPerSec((int) Math.round(1024d*newValue.doubleValue()/8d));
|
|
});
|
|
|
|
masterToggle.setOnMouseClicked(e->{
|
|
if(shuttingDown) return;
|
|
masterState = !masterState;
|
|
bandwidthDisabledOverlay.setVisible(!masterState);
|
|
if(masterState) {
|
|
masterToggle.setImage(new Image("org/getmonero/i2p/zero/gui/toggle-on.png"));
|
|
statusLabel.setVisible(true);
|
|
routerWrapper.start(()->{});
|
|
listenForTunnelChanges();
|
|
tunnelAddButton.setDisable(false);
|
|
}
|
|
else {
|
|
shuttingDown = true;
|
|
masterToggle.setImage(new Image("org/getmonero/i2p/zero/gui/toggle-off.png"));
|
|
tunnelTableList.clear();
|
|
tunnelAddButton.setDisable(true);
|
|
statusLabel.setText("Shutting down...");
|
|
routerWrapper.stop(true);
|
|
}
|
|
});
|
|
|
|
startRouter();
|
|
|
|
bandwidthSlider.setValue(getRouterWrapper().loadBandwidthLimitKBps()*8d/1024d);
|
|
|
|
listenForTunnelChanges();
|
|
|
|
var bandwidthUpdateThread = new Thread(()->{
|
|
while(!Gui.instance.isStopping()) {
|
|
if (routerWrapper.isStarted()) {
|
|
Platform.runLater(() -> {
|
|
bandwidthIn1s.setText(format2dp.format(routerWrapper.get1sRateInKBps()) + " KBps");
|
|
bandwidthIn5m.setText(format2dp.format(routerWrapper.get5mRateInKBps()) + " KBps");
|
|
bandwidthInAll.setText(format2dp.format(routerWrapper.getAvgRateInKBps()) + " KBps");
|
|
totalTransferredIn.setText(formatTransferAmount(routerWrapper.getTotalInMB())+" ");
|
|
bandwidthOut1s.setText(format2dp.format(routerWrapper.get1sRateOutKBps()) + " KBps");
|
|
bandwidthOut5m.setText(format2dp.format(routerWrapper.get5mRateOutKBps()) + " KBps");
|
|
bandwidthOutAll.setText(format2dp.format(routerWrapper.getAvgRateOutKBps()) + " KBps");
|
|
totalTransferredOut.setText(formatTransferAmount(routerWrapper.getTotalOutMB())+" ");
|
|
|
|
statusLabel.setText("Status: " + routerWrapper.getReachability().getMessage());
|
|
|
|
tunnelsTableView.refresh();
|
|
|
|
});
|
|
}
|
|
try { Thread.sleep(1000); } catch (InterruptedException e) {}
|
|
}
|
|
});
|
|
bandwidthUpdateThread.start();
|
|
|
|
}
|
|
|
|
private String formatTransferAmount(double amt) {
|
|
String unit = "MB";
|
|
if(amt>=1000) { amt/= 1000d; unit = "GB"; }
|
|
if(amt>=1000) { amt/= 1000d; unit = "TB"; }
|
|
if(amt>=1000) { amt/= 1000d; unit = "PB"; }
|
|
return format2dp.format(amt) + " " + unit;
|
|
}
|
|
|
|
private void listenForTunnelChanges() {
|
|
new Thread(()->{
|
|
while(getRouterWrapper()==null || getRouterWrapper().getTunnelControl()==null) {
|
|
try { Thread.sleep(100); } catch (InterruptedException e) {}
|
|
}
|
|
var tunnelList = getRouterWrapper().getTunnelControl().getTunnelList();
|
|
tunnelList.addChangeListener(tunnels->{
|
|
tunnelTableList.clear();
|
|
tunnels.stream().filter(Tunnel::getEnabled).forEach(tunnelTableList::add);
|
|
});
|
|
getRouterWrapper().getTunnelControl().getTunnelList().fireChangeEvent();
|
|
}).start();
|
|
}
|
|
|
|
public RouterWrapper getRouterWrapper() {
|
|
return routerWrapper;
|
|
}
|
|
|
|
private int getBandwidthLimitKBPerSec() {
|
|
return (int)Math.round(bandwidthSlider.getValue()*1024d/8d);
|
|
}
|
|
|
|
private void startRouter() {
|
|
|
|
var params = Gui.instance.getParameters().getNamed();
|
|
|
|
Properties routerProperties = new Properties();
|
|
routerProperties.put("router.sharePercentage", 80);
|
|
params.entrySet().stream().forEach(e->routerProperties.put(e.getKey(), e.getValue()));
|
|
routerWrapper = new RouterWrapper(routerProperties, ()-> {
|
|
Platform.runLater(()->{
|
|
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
|
alert.setTitle("Update available");
|
|
alert.setHeaderText(null);
|
|
|
|
FlowPane fp = new FlowPane();
|
|
String updateUrl = "https://github.com/i2p-zero/i2p-zero";
|
|
|
|
var link = new Hyperlink(updateUrl);
|
|
TextFlow textFlow = new TextFlow(
|
|
new Text("A new version of I2P-zero is available at"),
|
|
link,
|
|
new Text("\nPlease keep your software up-to-date, as it will enhance your privacy and keep you safe from vulnerabilities")
|
|
);
|
|
textFlow.setPrefWidth(300);
|
|
link.setOnAction((ActionEvent event) -> {
|
|
try {
|
|
Desktop.getDesktop().browse(new URI(updateUrl));
|
|
}
|
|
catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
});
|
|
fp.getChildren().addAll(textFlow);
|
|
alert.getDialogPane().contentProperty().set(fp);
|
|
alert.show();
|
|
});
|
|
});
|
|
routerWrapper.start(()->{
|
|
helpTextArea.setText("You are running I2P-zero version " + UpdateCheck.currentVersion + "\n\n"
|
|
+ "For best performance, please open port " + routerWrapper.routerExternalPort + " on your firewall for incoming UDP and TCP connections. This port has been randomly assigned to you. For privacy reasons, please do not share this port with others.\n\n"
|
|
+ helpTextArea.getText());
|
|
});
|
|
|
|
}
|
|
|
|
private void restartJetty() {
|
|
Optional<Tunnel> eepSiteTunnelOptional = getRouterWrapper().getTunnelControl().getTunnelList().getTunnelsCopyStream().filter(t->t.getType().equals("eepsite")).findFirst();
|
|
EepSiteTunnel eepSiteTunnel = (EepSiteTunnel) eepSiteTunnelOptional.get();
|
|
eepSiteTunnel.stopJetty();
|
|
new Thread(()->eepSiteTunnel.startJetty()).start();
|
|
}
|
|
private void save() {
|
|
getRouterWrapper().getTunnelControl().getTunnelList().save();
|
|
}
|
|
private EepSiteTunnel getEepSiteTunnel() {
|
|
return getRouterWrapper().getTunnelControl().getEepSiteTunnel();
|
|
}
|
|
|
|
|
|
}
|