Browse Source

initial work on adding bundled node-management UI

pull/67/head
catbref 5 years ago
parent
commit
f4022dd243
  1. 19
      src/main/java/org/qora/controller/Controller.java
  2. 24
      src/main/java/org/qora/gui/Gui.java
  3. 10
      src/main/java/org/qora/gui/SplashFrame.java
  4. 49
      src/main/java/org/qora/gui/SysTray.java
  5. 19
      src/main/java/org/qora/settings/Settings.java
  6. 85
      src/main/java/org/qora/ui/UiService.java
  7. 35
      src/main/java/org/qora/utils/URLViewer.java
  8. 1
      src/main/resources/bundled-ui

19
src/main/java/org/qora/controller/Controller.java

@ -27,7 +27,7 @@ import org.qora.data.block.BlockData;
import org.qora.data.network.BlockSummaryData; import org.qora.data.network.BlockSummaryData;
import org.qora.data.network.PeerData; import org.qora.data.network.PeerData;
import org.qora.data.transaction.TransactionData; import org.qora.data.transaction.TransactionData;
import org.qora.gui.GUI; import org.qora.gui.Gui;
import org.qora.network.Network; import org.qora.network.Network;
import org.qora.network.Peer; import org.qora.network.Peer;
import org.qora.network.message.BlockMessage; import org.qora.network.message.BlockMessage;
@ -50,6 +50,7 @@ import org.qora.repository.hsqldb.HSQLDBRepositoryFactory;
import org.qora.settings.Settings; import org.qora.settings.Settings;
import org.qora.transaction.Transaction; import org.qora.transaction.Transaction;
import org.qora.transaction.Transaction.ValidationResult; import org.qora.transaction.Transaction.ValidationResult;
import org.qora.ui.UiService;
import org.qora.utils.Base58; import org.qora.utils.Base58;
import org.qora.utils.NTP; import org.qora.utils.NTP;
@ -159,7 +160,7 @@ public class Controller extends Thread {
LOGGER.info("Starting up..."); LOGGER.info("Starting up...");
// Potential GUI startup with splash screen, etc. // Potential GUI startup with splash screen, etc.
GUI.getInstance(); Gui.getInstance();
Security.insertProviderAt(new BouncyCastleProvider(), 0); Security.insertProviderAt(new BouncyCastleProvider(), 0);
Security.insertProviderAt(new BouncyCastleJsseProvider(), 1); Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);
@ -223,8 +224,17 @@ public class Controller extends Thread {
LOGGER.info("Starting auto-update"); LOGGER.info("Starting auto-update");
AutoUpdate.getInstance().start(); AutoUpdate.getInstance().start();
LOGGER.info("Starting bundled UI on port " + Settings.getInstance().getUiPort());
try {
UiService uiService = UiService.getInstance();
uiService.start();
} catch (Exception e) {
LOGGER.error("Unable to start bundled UI", e);
System.exit(1);
}
// If GUI is enabled, we're no longer starting up but actually running now // If GUI is enabled, we're no longer starting up but actually running now
GUI.getInstance().notifyRunning(); Gui.getInstance().notifyRunning();
} }
// Main thread // Main thread
@ -319,6 +329,9 @@ public class Controller extends Thread {
if (!isStopping) { if (!isStopping) {
isStopping = true; isStopping = true;
LOGGER.info("Shutting down bundled UI");
UiService.getInstance().stop();
LOGGER.info("Shutting down auto-update"); LOGGER.info("Shutting down auto-update");
AutoUpdate.getInstance().shutdown(); AutoUpdate.getInstance().shutdown();

24
src/main/java/org/qora/gui/GUI.java → src/main/java/org/qora/gui/Gui.java

@ -6,24 +6,34 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
public class GUI { public class Gui {
private static final Logger LOGGER = LogManager.getLogger(GUI.class); private static final Logger LOGGER = LogManager.getLogger(Gui.class);
private static GUI instance; private static Gui instance;
private boolean isHeadless; private boolean isHeadless;
private SplashFrame splash = null; private SplashFrame splash = null;
private SysTray sysTray = null; private SysTray sysTray = null;
private GUI() { private Gui() {
this.isHeadless = GraphicsEnvironment.isHeadless(); this.isHeadless = GraphicsEnvironment.isHeadless();
if (!this.isHeadless) if (!this.isHeadless) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException
| UnsupportedLookAndFeelException e) {
// Use whatever look-and-feel comes by default then
}
showSplash(); showSplash();
}
} }
private void showSplash() { private void showSplash() {
@ -40,9 +50,9 @@ public class GUI {
} }
} }
public static GUI getInstance() { public static Gui getInstance() {
if (instance == null) if (instance == null)
instance = new GUI(); instance = new Gui();
return instance; return instance;
} }

10
src/main/java/org/qora/gui/SplashFrame.java

@ -26,7 +26,7 @@ public class SplashFrame {
private BufferedImage image; private BufferedImage image;
public SplashPanel() { public SplashPanel() {
image = GUI.loadImage("splash.png"); image = Gui.loadImage("splash.png");
this.setPreferredSize(new Dimension(image.getWidth(), image.getHeight())); this.setPreferredSize(new Dimension(image.getWidth(), image.getHeight()));
this.setLayout(new BorderLayout()); this.setLayout(new BorderLayout());
} }
@ -42,10 +42,10 @@ public class SplashFrame {
this.splashDialog = new JDialog(); this.splashDialog = new JDialog();
List<Image> icons = new ArrayList<Image>(); List<Image> icons = new ArrayList<Image>();
icons.add(GUI.loadImage("icons/icon16.png")); icons.add(Gui.loadImage("icons/icon16.png"));
icons.add(GUI.loadImage("icons/icon32.png")); icons.add(Gui.loadImage("icons/icon32.png"));
icons.add(GUI.loadImage("icons/icon64.png")); icons.add(Gui.loadImage("icons/icon64.png"));
icons.add(GUI.loadImage("icons/icon128.png")); icons.add(Gui.loadImage("icons/icon128.png"));
this.splashDialog.setIconImages(icons); this.splashDialog.setIconImages(icons);
this.splashDialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); this.splashDialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);

49
src/main/java/org/qora/gui/SysTray.java

@ -1,26 +1,22 @@
package org.qora.gui; package org.qora.gui;
import java.awt.AWTException; import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.MenuItem; import java.awt.MenuItem;
import java.awt.PopupMenu; import java.awt.PopupMenu;
import java.awt.SystemTray; import java.awt.SystemTray;
import java.awt.TrayIcon; import java.awt.TrayIcon;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.awt.Dimension; import java.net.MalformedURLException;
import java.awt.Graphics; import java.net.URL;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import javax.imageio.ImageIO;
import javax.swing.JPanel;
import javax.swing.SwingWorker; import javax.swing.SwingWorker;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.qora.controller.Controller; import org.qora.controller.Controller;
import org.qora.settings.Settings;
import org.qora.utils.URLViewer;
public class SysTray { public class SysTray {
@ -30,34 +26,13 @@ public class SysTray {
private TrayIcon trayIcon = null; private TrayIcon trayIcon = null;
private PopupMenu popupMenu = null; private PopupMenu popupMenu = null;
@SuppressWarnings("serial")
public static class SplashPanel extends JPanel {
private BufferedImage image;
public SplashPanel() {
try (InputStream in = ClassLoader.getSystemResourceAsStream("images/splash.png")) {
image = ImageIO.read(in);
this.setPreferredSize(new Dimension(image.getWidth(), image.getHeight()));
this.setLayout(new BorderLayout());
} catch (IOException ex) {
LOGGER.error(ex);
}
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, null);
}
}
private SysTray() { private SysTray() {
if (!SystemTray.isSupported()) if (!SystemTray.isSupported())
return; return;
this.popupMenu = createPopupMenu(); this.popupMenu = createPopupMenu();
this.trayIcon = new TrayIcon(GUI.loadImage("icons/icon32.png"), "qora-core", popupMenu); this.trayIcon = new TrayIcon(Gui.loadImage("icons/icon32.png"), "qora-core", popupMenu);
this.trayIcon.setImageAutoSize(true); this.trayIcon.setImageAutoSize(true);
@ -84,6 +59,18 @@ public class SysTray {
private PopupMenu createPopupMenu() { private PopupMenu createPopupMenu() {
PopupMenu menu = new PopupMenu(); PopupMenu menu = new PopupMenu();
MenuItem openUi = new MenuItem("Open UI");
openUi.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
URLViewer.openWebpage(new URL("http://localhost:" + Settings.getInstance().getUiPort()));
} catch (MalformedURLException e1) {
LOGGER.error(e1.getMessage(),e1);
}
}
});
menu.add(openUi);
MenuItem exit = new MenuItem("Exit"); MenuItem exit = new MenuItem("Exit");
exit.addActionListener(new ActionListener() { exit.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {

19
src/main/java/org/qora/settings/Settings.java

@ -36,6 +36,13 @@ public class Settings {
// Settings, and other config files // Settings, and other config files
private String userPath; private String userPath;
// Bundled UI related
private boolean uiEnabled = true;
private int uiPort = 9080;
private String[] uiWhitelist = new String[] {
"::1", "127.0.0.1"
};
// API-related // API-related
private boolean apiEnabled = true; private boolean apiEnabled = true;
private int apiPort = 9085; private int apiPort = 9085;
@ -182,6 +189,18 @@ public class Settings {
return this.userPath; return this.userPath;
} }
public boolean isUiEnabled() {
return this.uiEnabled;
}
public int getUiPort() {
return this.uiPort;
}
public String[] getUiWhitelist() {
return this.uiWhitelist;
}
public boolean isApiEnabled() { public boolean isApiEnabled() {
return this.apiEnabled; return this.apiEnabled;
} }

85
src/main/java/org/qora/ui/UiService.java

@ -0,0 +1,85 @@
package org.qora.ui;
import org.eclipse.jetty.rewrite.handler.RedirectPatternRule;
import org.eclipse.jetty.rewrite.handler.RewriteHandler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.InetAccessHandler;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlets.CrossOriginFilter;
import org.qora.settings.Settings;
public class UiService {
private final Server server;
public UiService() {
// Create bundled UI server
this.server = new Server(Settings.getInstance().getUiPort());
// IP address based access control
InetAccessHandler accessHandler = new InetAccessHandler();
for (String pattern : Settings.getInstance().getUiWhitelist()) {
accessHandler.include(pattern);
}
this.server.setHandler(accessHandler);
// URL rewriting
RewriteHandler rewriteHandler = new RewriteHandler();
accessHandler.setHandler(rewriteHandler);
// Context
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
context.setContextPath("/");
rewriteHandler.setHandler(context);
// Cross-origin resource sharing
FilterHolder corsFilterHolder = new FilterHolder(CrossOriginFilter.class);
corsFilterHolder.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, "*");
corsFilterHolder.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM, "GET, POST, DELETE");
corsFilterHolder.setInitParameter(CrossOriginFilter.CHAIN_PREFLIGHT_PARAM, "false");
context.addFilter(corsFilterHolder, "/*", null);
// Bundled-UI static content servlet
ServletHolder uiServlet = new ServletHolder("bundled-ui", DefaultServlet.class);
ClassLoader loader = this.getClass().getClassLoader();
uiServlet.setInitParameter("resourceBase", loader.getResource("bundled-ui/").toString());
uiServlet.setInitParameter("dirAllowed", "true");
uiServlet.setInitParameter("pathInfoOnly", "true");
context.addServlet(uiServlet, "/*");
rewriteHandler.addRule(new RedirectPatternRule("", "/home.html")); // redirect to bundled UI start page
}
private static UiService instance;
public static UiService getInstance() {
if (instance == null) {
instance = new UiService();
}
return instance;
}
public void start() {
try {
// Start server
server.start();
} catch (Exception e) {
// Failed to start
throw new RuntimeException("Failed to start bundled UI", e);
}
}
public void stop() {
try {
// Stop server
server.stop();
} catch (Exception e) {
// Failed to stop
}
}
}

35
src/main/java/org/qora/utils/URLViewer.java

@ -0,0 +1,35 @@
package org.qora.utils;
import java.awt.Desktop;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class URLViewer {
private static final Logger LOGGER = LogManager.getLogger(URLViewer.class);
public static void openWebpage(URI uri) {
Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null;
if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) {
try {
desktop.browse(uri);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
}
}
public static void openWebpage(URL url) {
try {
openWebpage(url.toURI());
} catch (URISyntaxException e) {
LOGGER.error(e.getMessage(), e);
}
}
}

1
src/main/resources/bundled-ui

@ -0,0 +1 @@
../../../../node-UI
Loading…
Cancel
Save