forked from Qortal/qortal
Remove node UI and have tray icon open local/remove UI server
No more "node UI". UI provided by 3rd party. "Open UI" tray icon menu item now attempts to open UI at various local servers (see Settings.uilocalServers) or some random remote server (Settings.uiRemoteServers). Default UI port now 12388 (Settings.uiPort).
This commit is contained in:
parent
a3c44428d3
commit
5bfc17bd64
@ -77,7 +77,6 @@ import org.qortal.transaction.ArbitraryTransaction;
|
||||
import org.qortal.transaction.Transaction;
|
||||
import org.qortal.transaction.Transaction.TransactionType;
|
||||
import org.qortal.transaction.Transaction.ValidationResult;
|
||||
import org.qortal.ui.UiService;
|
||||
import org.qortal.utils.Base58;
|
||||
import org.qortal.utils.ByteArray;
|
||||
import org.qortal.utils.NTP;
|
||||
@ -348,16 +347,6 @@ public class Controller extends Thread {
|
||||
return; // Not System.exit() so that GUI can display error
|
||||
}
|
||||
|
||||
LOGGER.info(String.format("Starting node management UI on port %d", Settings.getInstance().getUiPort()));
|
||||
try {
|
||||
UiService uiService = UiService.getInstance();
|
||||
uiService.start();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Unable to start node management UI", e);
|
||||
Gui.getInstance().fatalError("Node management UI failure", e);
|
||||
return; // Not System.exit() so that GUI can display error
|
||||
}
|
||||
|
||||
// If GUI is enabled, we're no longer starting up but actually running now
|
||||
Gui.getInstance().notifyRunning();
|
||||
}
|
||||
@ -638,9 +627,6 @@ public class Controller extends Thread {
|
||||
if (!isStopping) {
|
||||
isStopping = true;
|
||||
|
||||
LOGGER.info("Shutting down node management UI");
|
||||
UiService.getInstance().stop();
|
||||
|
||||
LOGGER.info("Shutting down API");
|
||||
ApiService.getInstance().stop();
|
||||
|
||||
|
@ -10,11 +10,14 @@ import java.awt.event.WindowEvent;
|
||||
import java.awt.event.WindowFocusListener;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URL;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@ -30,7 +33,7 @@ import org.apache.logging.log4j.Logger;
|
||||
import org.qortal.controller.Controller;
|
||||
import org.qortal.globalization.Translator;
|
||||
import org.qortal.settings.Settings;
|
||||
import org.qortal.ui.UiService;
|
||||
import org.qortal.utils.RandomizeList;
|
||||
import org.qortal.utils.URLViewer;
|
||||
|
||||
public class SysTray {
|
||||
@ -144,15 +147,11 @@ public class SysTray {
|
||||
}
|
||||
});
|
||||
|
||||
JMenuItem openUi = new JMenuItem(Translator.INSTANCE.translate("SysTray", "OPEN_NODE_UI"));
|
||||
JMenuItem openUi = new JMenuItem(Translator.INSTANCE.translate("SysTray", "OPEN_UI"));
|
||||
openUi.addActionListener(actionEvent -> {
|
||||
destroyHiddenDialog();
|
||||
|
||||
try {
|
||||
URLViewer.openWebpage(new URL("http://localhost:" + Settings.getInstance().getUiPort()));
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Unable to open node UI in browser");
|
||||
}
|
||||
new OpenUiWorker().execute();
|
||||
});
|
||||
menu.add(openUi);
|
||||
|
||||
@ -174,7 +173,7 @@ public class SysTray {
|
||||
syncTime.addActionListener(actionEvent -> {
|
||||
destroyHiddenDialog();
|
||||
|
||||
new SynchronizeWorker().execute();
|
||||
new SynchronizeClockWorker().execute();
|
||||
});
|
||||
menu.add(syncTime);
|
||||
}
|
||||
@ -190,11 +189,53 @@ public class SysTray {
|
||||
return menu;
|
||||
}
|
||||
|
||||
class SynchronizeWorker extends SwingWorker<Void, Void> {
|
||||
static class OpenUiWorker extends SwingWorker<Void, Void> {
|
||||
@Override
|
||||
protected Void doInBackground() {
|
||||
List<String> uiServers = new ArrayList<>();
|
||||
|
||||
String[] remoteUiServers = Settings.getInstance().getRemoteUiServers();
|
||||
uiServers.addAll(Arrays.asList(remoteUiServers));
|
||||
// Randomize remote servers
|
||||
uiServers = RandomizeList.randomize(uiServers);
|
||||
|
||||
// Prepend local servers
|
||||
String[] localUiServers = Settings.getInstance().getLocalUiServers();
|
||||
uiServers.addAll(0, Arrays.asList(localUiServers));
|
||||
|
||||
// Check each server in turn before opening browser tab
|
||||
int uiPort = Settings.getInstance().getUiServerPort();
|
||||
for (String uiServer : uiServers) {
|
||||
InetSocketAddress socketAddress = new InetSocketAddress(uiServer, uiPort);
|
||||
|
||||
// If we couldn't resolve try next
|
||||
if (socketAddress.isUnresolved())
|
||||
continue;
|
||||
|
||||
try (SocketChannel socketChannel = SocketChannel.open()) {
|
||||
socketChannel.socket().connect(socketAddress, 100);
|
||||
|
||||
// If we reach here, then socket connected to UI server!
|
||||
URLViewer.openWebpage(new URL(String.format("http://%s:%d", uiServer, uiPort)));
|
||||
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
// try next server
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Unable to open UI website in browser");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static class SynchronizeClockWorker extends SwingWorker<Void, Void> {
|
||||
@Override
|
||||
protected Void doInBackground() {
|
||||
// Extract reconfiguration script from resources
|
||||
String resourceName = "/" + UiService.DOWNLOADS_RESOURCE_PATH + "/" + NTP_SCRIPT;
|
||||
String resourceName = "/node-management/" + NTP_SCRIPT;
|
||||
Path scriptPath = Paths.get(NTP_SCRIPT);
|
||||
|
||||
try (InputStream in = SysTray.class.getResourceAsStream(resourceName)) {
|
||||
@ -218,7 +259,7 @@ public class SysTray {
|
||||
}
|
||||
}
|
||||
|
||||
class ClosingWorker extends SwingWorker<Void, Void> {
|
||||
static class ClosingWorker extends SwingWorker<Void, Void> {
|
||||
@Override
|
||||
protected Void doInBackground() {
|
||||
Controller.getInstance().shutdown();
|
||||
|
@ -31,9 +31,6 @@ public class Settings {
|
||||
private static final int MAINNET_API_PORT = 12391;
|
||||
private static final int TESTNET_API_PORT = 62391;
|
||||
|
||||
private static final int MAINNET_UI_PORT = 12390;
|
||||
private static final int TESTNET_UI_PORT = 62390;
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(Settings.class);
|
||||
private static final String SETTINGS_FILENAME = "settings.json";
|
||||
|
||||
@ -43,14 +40,17 @@ public class Settings {
|
||||
// Settings, and other config files
|
||||
private String userPath;
|
||||
|
||||
// Common to all networking (UI/API/P2P)
|
||||
// Common to all networking (API/P2P)
|
||||
private String bindAddress = "::"; // Use IPv6 wildcard to listen on all local addresses
|
||||
|
||||
// Node management UI
|
||||
private boolean uiEnabled = true;
|
||||
private Integer uiPort;
|
||||
private String[] uiWhitelist = new String[] {
|
||||
"::1", "127.0.0.1"
|
||||
// UI servers
|
||||
private int uiPort = 12388;
|
||||
private String[] uiLocalServers = new String[] {
|
||||
"localhost", "172.24.1.1", "qor.tal"
|
||||
};
|
||||
private String[] uiRemoteServers = new String[] {
|
||||
"node1.qortal.org", "node2.qortal.org", "node3.qortal.org", "node4.qortal.org", "node5.qortal.org",
|
||||
"node6.qortal.org", "node7.qortal.org", "node8.qortal.org", "node9.qortal.org", "node10.qortal.org"
|
||||
};
|
||||
|
||||
// API-related
|
||||
@ -244,19 +244,16 @@ public class Settings {
|
||||
return this.userPath;
|
||||
}
|
||||
|
||||
public boolean isUiEnabled() {
|
||||
return this.uiEnabled;
|
||||
}
|
||||
|
||||
public int getUiPort() {
|
||||
if (this.uiPort != null)
|
||||
public int getUiServerPort() {
|
||||
return this.uiPort;
|
||||
|
||||
return this.isTestNet ? TESTNET_UI_PORT : MAINNET_UI_PORT;
|
||||
}
|
||||
|
||||
public String[] getUiWhitelist() {
|
||||
return this.uiWhitelist;
|
||||
public String[] getLocalUiServers() {
|
||||
return this.uiLocalServers;
|
||||
}
|
||||
|
||||
public String[] getRemoteUiServers() {
|
||||
return this.uiRemoteServers;
|
||||
}
|
||||
|
||||
public boolean isApiEnabled() {
|
||||
|
@ -1,46 +0,0 @@
|
||||
package org.qortal.ui;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Enumeration;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.HttpContent;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.server.ResourceService;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
|
||||
/**
|
||||
* Replace ResourceService that delivers content as "attachments", typically forcing download instead of rendering.
|
||||
* <p>
|
||||
* Sets <tt>Content-Type</tt> header to <tt>application/octet-stream</tt><br>
|
||||
* Sets <tt>Content-Disposition</tt> header to <tt>attachment; filename="<i>basename</i>"</tt><br>
|
||||
* where <i>basename</i> is that last component of requested URI path.
|
||||
* <p>
|
||||
* Example usage:<br>
|
||||
* <br>
|
||||
* <tt>... = new ServletHolder("servlet-name", new DefaultServlet(new DownloadResourceService()));</tt>
|
||||
*/
|
||||
public class DownloadResourceService extends ResourceService {
|
||||
|
||||
@Override
|
||||
protected boolean sendData(HttpServletRequest request, HttpServletResponse response, boolean include, final HttpContent content, Enumeration<String> reqRanges) throws IOException {
|
||||
final boolean _pathInfoOnly = super.isPathInfoOnly();
|
||||
String servletPath = _pathInfoOnly ? "/" : request.getServletPath();
|
||||
String pathInfo = request.getPathInfo();
|
||||
String pathInContext = URIUtil.addPaths(servletPath,pathInfo);
|
||||
|
||||
// Find basename of requested content
|
||||
final int slashIndex = pathInContext.lastIndexOf(URIUtil.SLASH);
|
||||
if (slashIndex != -1)
|
||||
pathInContext = pathInContext.substring(slashIndex + 1);
|
||||
|
||||
// Add appropriate headers
|
||||
response.setHeader(HttpHeader.CONTENT_TYPE.asString(), "application/octet-stream");
|
||||
response.setHeader("Content-Disposition", "attachment; filename=\"" + pathInContext + "\"");
|
||||
|
||||
return super.sendData(request, response, include, content, reqRanges);
|
||||
}
|
||||
|
||||
}
|
@ -1,101 +0,0 @@
|
||||
package org.qortal.ui;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
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.qortal.settings.Settings;
|
||||
|
||||
public class UiService {
|
||||
|
||||
public static final String DOWNLOADS_RESOURCE_PATH = "node-ui-downloads";
|
||||
private static UiService instance;
|
||||
|
||||
private Server server;
|
||||
|
||||
private UiService() {
|
||||
}
|
||||
|
||||
public static UiService getInstance() {
|
||||
if (instance == null)
|
||||
instance = new UiService();
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
try {
|
||||
// Create node management UI server
|
||||
InetAddress bindAddr = InetAddress.getByName(Settings.getInstance().getBindAddress());
|
||||
InetSocketAddress endpoint = new InetSocketAddress(bindAddr, Settings.getInstance().getUiPort());
|
||||
this.server = new Server(endpoint);
|
||||
|
||||
// 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);
|
||||
|
||||
ClassLoader loader = this.getClass().getClassLoader();
|
||||
|
||||
// Node management UI download servlet
|
||||
ServletHolder uiDownloadServlet = new ServletHolder("node-ui-download", new DefaultServlet(new DownloadResourceService()));
|
||||
uiDownloadServlet.setInitParameter("resourceBase", loader.getResource(DOWNLOADS_RESOURCE_PATH + "/").toString());
|
||||
uiDownloadServlet.setInitParameter("dirAllowed", "true");
|
||||
uiDownloadServlet.setInitParameter("pathInfoOnly", "true");
|
||||
context.addServlet(uiDownloadServlet, "/downloads/*");
|
||||
|
||||
// Node management UI static content servlet
|
||||
ServletHolder uiServlet = new ServletHolder("node-management-ui", DefaultServlet.class);
|
||||
uiServlet.setInitParameter("resourceBase", loader.getResource("node-management-ui/").toString());
|
||||
uiServlet.setInitParameter("dirAllowed", "true");
|
||||
uiServlet.setInitParameter("pathInfoOnly", "true");
|
||||
context.addServlet(uiServlet, "/*");
|
||||
|
||||
rewriteHandler.addRule(new RedirectPatternRule("", "/index.html")); // node management UI start page
|
||||
|
||||
// Start server
|
||||
this.server.start();
|
||||
} catch (Exception e) {
|
||||
// Failed to start
|
||||
throw new RuntimeException("Failed to start node management UI", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
try {
|
||||
// Stop server
|
||||
this.server.stop();
|
||||
} catch (Exception e) {
|
||||
// Failed to stop
|
||||
}
|
||||
|
||||
this.server = null;
|
||||
}
|
||||
|
||||
}
|
29
src/main/java/org/qortal/utils/RandomizeList.java
Normal file
29
src/main/java/org/qortal/utils/RandomizeList.java
Normal file
@ -0,0 +1,29 @@
|
||||
package org.qortal.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class RandomizeList {
|
||||
private static final Random random = new Random();
|
||||
|
||||
public static <T> List<T> randomize(List<T> inputList) {
|
||||
List<T> outputList = new ArrayList<T>();
|
||||
|
||||
Iterator<T> inputIterator = inputList.iterator();
|
||||
while (inputIterator.hasNext()) {
|
||||
T element = inputIterator.next();
|
||||
|
||||
if (outputList.isEmpty()) {
|
||||
outputList.add(element);
|
||||
} else {
|
||||
int outputIndex = random.nextInt(outputList.size() + 1);
|
||||
outputList.add(outputIndex, element);
|
||||
}
|
||||
}
|
||||
|
||||
return outputList;
|
||||
}
|
||||
|
||||
}
|
@ -22,7 +22,7 @@ NTP_NAG_TEXT_UNIX = Install NTP service to get an accurate clock.
|
||||
|
||||
NTP_NAG_TEXT_WINDOWS = Select "Synchronize clock" from menu to fix.
|
||||
|
||||
OPEN_NODE_UI = Open Node UI
|
||||
OPEN_UI = Open UI
|
||||
|
||||
SYNCHRONIZE_CLOCK = Synchronize clock
|
||||
|
||||
|
@ -22,7 +22,7 @@ NTP_NAG_TEXT_UNIX = \u5B89\u88C5NTP\u670D\u52A1\u4EE5\u83B7\u5F97\u51C6\u786E\u7
|
||||
|
||||
NTP_NAG_TEXT_WINDOWS = \u4ECE\u83DC\u5355\u4E2D\u9009\u62E9\u201C\u540C\u6B65\u65F6\u949F\u201D\u8FDB\u884C\u4FEE\u590D\u3002
|
||||
|
||||
OPEN_NODE_UI = \u5F00\u542F\u754C\u9762
|
||||
OPEN_UI = \u5F00\u542F\u754C\u9762
|
||||
|
||||
SYNCHRONIZE_CLOCK = \u540C\u6B65\u65F6\u949F
|
||||
|
||||
|
@ -1 +0,0 @@
|
||||
Node UI goes here!
|
@ -14,7 +14,7 @@ public class CheckTranslations {
|
||||
|
||||
private static final String[] SUPPORTED_LANGS = new String[] { "en", "de", "zh", "ru" };
|
||||
private static final Set<String> SYSTRAY_KEYS = Set.of("BLOCK_HEIGHT", "CHECK_TIME_ACCURACY", "CONNECTION", "CONNECTIONS",
|
||||
"EXIT", "MINTING_DISABLED", "MINTING_ENABLED", "OPEN_NODE_UI", "SYNCHRONIZE_CLOCK", "SYNCHRONIZING_CLOCK");
|
||||
"EXIT", "MINTING_DISABLED", "MINTING_ENABLED", "OPEN_UI", "SYNCHRONIZE_CLOCK", "SYNCHRONIZING_CLOCK");
|
||||
|
||||
private static String failurePrefix;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user