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.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();
|
||||||
|
|
||||||
|
@ -6,25 +6,35 @@ 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() {
|
||||||
LOGGER.trace("Splash");
|
LOGGER.trace("Splash");
|
||||||
@ -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;
|
||||||
}
|
}
|
@ -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);
|
||||||
|
@ -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) {
|
||||||
|
@ -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
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