forked from Qortal/qortal
initial work on adding bundled node-management UI
This commit is contained in:
parent
99024ee2ef
commit
f4022dd243
@ -27,7 +27,7 @@ import org.qora.data.block.BlockData;
|
||||
import org.qora.data.network.BlockSummaryData;
|
||||
import org.qora.data.network.PeerData;
|
||||
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.Peer;
|
||||
import org.qora.network.message.BlockMessage;
|
||||
@ -50,6 +50,7 @@ import org.qora.repository.hsqldb.HSQLDBRepositoryFactory;
|
||||
import org.qora.settings.Settings;
|
||||
import org.qora.transaction.Transaction;
|
||||
import org.qora.transaction.Transaction.ValidationResult;
|
||||
import org.qora.ui.UiService;
|
||||
import org.qora.utils.Base58;
|
||||
import org.qora.utils.NTP;
|
||||
|
||||
@ -159,7 +160,7 @@ public class Controller extends Thread {
|
||||
LOGGER.info("Starting up...");
|
||||
|
||||
// Potential GUI startup with splash screen, etc.
|
||||
GUI.getInstance();
|
||||
Gui.getInstance();
|
||||
|
||||
Security.insertProviderAt(new BouncyCastleProvider(), 0);
|
||||
Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);
|
||||
@ -223,8 +224,17 @@ public class Controller extends Thread {
|
||||
LOGGER.info("Starting auto-update");
|
||||
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
|
||||
GUI.getInstance().notifyRunning();
|
||||
Gui.getInstance().notifyRunning();
|
||||
}
|
||||
|
||||
// Main thread
|
||||
@ -319,6 +329,9 @@ public class Controller extends Thread {
|
||||
if (!isStopping) {
|
||||
isStopping = true;
|
||||
|
||||
LOGGER.info("Shutting down bundled UI");
|
||||
UiService.getInstance().stop();
|
||||
|
||||
LOGGER.info("Shutting down auto-update");
|
||||
AutoUpdate.getInstance().shutdown();
|
||||
|
||||
|
@ -6,24 +6,34 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.UIManager;
|
||||
import javax.swing.UnsupportedLookAndFeelException;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
public class GUI {
|
||||
public class Gui {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(GUI.class);
|
||||
private static GUI instance;
|
||||
private static final Logger LOGGER = LogManager.getLogger(Gui.class);
|
||||
private static Gui instance;
|
||||
|
||||
private boolean isHeadless;
|
||||
private SplashFrame splash = null;
|
||||
private SysTray sysTray = null;
|
||||
|
||||
private GUI() {
|
||||
private Gui() {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
private void showSplash() {
|
||||
@ -40,9 +50,9 @@ public class GUI {
|
||||
}
|
||||
}
|
||||
|
||||
public static GUI getInstance() {
|
||||
public static Gui getInstance() {
|
||||
if (instance == null)
|
||||
instance = new GUI();
|
||||
instance = new Gui();
|
||||
|
||||
return instance;
|
||||
}
|
@ -26,7 +26,7 @@ public class SplashFrame {
|
||||
private BufferedImage image;
|
||||
|
||||
public SplashPanel() {
|
||||
image = GUI.loadImage("splash.png");
|
||||
image = Gui.loadImage("splash.png");
|
||||
this.setPreferredSize(new Dimension(image.getWidth(), image.getHeight()));
|
||||
this.setLayout(new BorderLayout());
|
||||
}
|
||||
@ -42,10 +42,10 @@ public class SplashFrame {
|
||||
this.splashDialog = new JDialog();
|
||||
|
||||
List<Image> icons = new ArrayList<Image>();
|
||||
icons.add(GUI.loadImage("icons/icon16.png"));
|
||||
icons.add(GUI.loadImage("icons/icon32.png"));
|
||||
icons.add(GUI.loadImage("icons/icon64.png"));
|
||||
icons.add(GUI.loadImage("icons/icon128.png"));
|
||||
icons.add(Gui.loadImage("icons/icon16.png"));
|
||||
icons.add(Gui.loadImage("icons/icon32.png"));
|
||||
icons.add(Gui.loadImage("icons/icon64.png"));
|
||||
icons.add(Gui.loadImage("icons/icon128.png"));
|
||||
this.splashDialog.setIconImages(icons);
|
||||
|
||||
this.splashDialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
|
||||
|
@ -1,26 +1,22 @@
|
||||
package org.qora.gui;
|
||||
|
||||
import java.awt.AWTException;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.MenuItem;
|
||||
import java.awt.PopupMenu;
|
||||
import java.awt.SystemTray;
|
||||
import java.awt.TrayIcon;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.SwingWorker;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.qora.controller.Controller;
|
||||
import org.qora.settings.Settings;
|
||||
import org.qora.utils.URLViewer;
|
||||
|
||||
public class SysTray {
|
||||
|
||||
@ -30,34 +26,13 @@ public class SysTray {
|
||||
private TrayIcon trayIcon = 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() {
|
||||
if (!SystemTray.isSupported())
|
||||
return;
|
||||
|
||||
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);
|
||||
|
||||
@ -84,6 +59,18 @@ public class SysTray {
|
||||
private PopupMenu createPopupMenu() {
|
||||
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");
|
||||
exit.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
|
@ -36,6 +36,13 @@ public class Settings {
|
||||
// Settings, and other config files
|
||||
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
|
||||
private boolean apiEnabled = true;
|
||||
private int apiPort = 9085;
|
||||
@ -182,6 +189,18 @@ public class Settings {
|
||||
return this.userPath;
|
||||
}
|
||||
|
||||
public boolean isUiEnabled() {
|
||||
return this.uiEnabled;
|
||||
}
|
||||
|
||||
public int getUiPort() {
|
||||
return this.uiPort;
|
||||
}
|
||||
|
||||
public String[] getUiWhitelist() {
|
||||
return this.uiWhitelist;
|
||||
}
|
||||
|
||||
public boolean isApiEnabled() {
|
||||
return this.apiEnabled;
|
||||
}
|
||||
|
85
src/main/java/org/qora/ui/UiService.java
Normal file
85
src/main/java/org/qora/ui/UiService.java
Normal file
@ -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
Normal file
35
src/main/java/org/qora/utils/URLViewer.java
Normal file
@ -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
Symbolic link
1
src/main/resources/bundled-ui
Symbolic link
@ -0,0 +1 @@
|
||||
../../../../node-UI
|
Loading…
x
Reference in New Issue
Block a user