diff --git a/SubServers.Web/Avenir-Book.ttf b/SubServers.Web/Avenir-Book.ttf
new file mode 100644
index 00000000..858df677
Binary files /dev/null and b/SubServers.Web/Avenir-Book.ttf differ
diff --git a/SubServers.Web/pom.xml b/SubServers.Web/pom.xml
new file mode 100644
index 00000000..9541da89
--- /dev/null
+++ b/SubServers.Web/pom.xml
@@ -0,0 +1,134 @@
+
+
+ 4.0.0
+
+ net.ME1312.SubServers
+ SubServers.Web
+ -PLACEHOLDER
+ jar
+
+
+
+ me1312-repo
+ https://dev.me1312.net/maven
+
+
+
+
+
+ org.fusesource.jansi
+ jansi
+ 1.18
+ compile
+
+
+ net.md_5
+ bungeecord-internal
+ 1.8-SNAPSHOT
+ provided
+
+
+ net.ME1312.SubServers
+ SubServers.Bungee.Common
+ ${project.version}
+ provided
+
+
+ net.ME1312.SubServers
+ SubServers.Bungee
+ ${project.version}
+ provided
+
+
+ org.eclipse.jetty
+ jetty-server
+ 9.4.3.v20170317
+
+
+ org.eclipse.jetty
+ jetty-servlet
+ 9.4.3.v20170317
+
+
+
+
+ ../out/compile/target/SubServers.Web
+ src
+
+
+ src
+
+ **/*.java
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.7.0
+
+
+ 1.8
+
+
+
+ org.codehaus.mojo
+ ideauidesigner-maven-plugin
+ 1.0-beta-1
+
+
+
+ javac2
+
+
+
+
+ true
+ true
+ true
+
+
+
+ org.apache.maven.plugins
+ maven-antrun-plugin
+
+
+ process-resources
+
+ run
+
+
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 2.2-beta-5
+
+ SubServers.Console
+ ../Artifacts/Plugins
+
+ jar-with-dependencies
+
+ false
+
+
+
+ make-assembly
+ package
+
+ single
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SubServers.Web/src/bungee.yml b/SubServers.Web/src/bungee.yml
new file mode 100644
index 00000000..e96e6e75
--- /dev/null
+++ b/SubServers.Web/src/bungee.yml
@@ -0,0 +1,4 @@
+name: SubServers-Web
+main: net.ME1312.SubServers.Web.ConsolePlugin
+version: 2.18a
+author: ME1312
\ No newline at end of file
diff --git a/SubServers.Web/src/net/ME1312/SubServers/Web/ConsoleFont.ttf b/SubServers.Web/src/net/ME1312/SubServers/Web/ConsoleFont.ttf
new file mode 100644
index 00000000..41413c15
Binary files /dev/null and b/SubServers.Web/src/net/ME1312/SubServers/Web/ConsoleFont.ttf differ
diff --git a/SubServers.Web/src/net/ME1312/SubServers/Web/ConsolePlugin.java b/SubServers.Web/src/net/ME1312/SubServers/Web/ConsolePlugin.java
new file mode 100644
index 00000000..64512e2c
--- /dev/null
+++ b/SubServers.Web/src/net/ME1312/SubServers/Web/ConsolePlugin.java
@@ -0,0 +1,86 @@
+package net.ME1312.SubServers.Web;
+
+import net.ME1312.Galaxi.Library.Config.YAMLConfig;
+import net.ME1312.SubServers.Bungee.Event.SubCreateEvent;
+import net.ME1312.SubServers.Bungee.Event.SubSendCommandEvent;
+import net.ME1312.SubServers.Bungee.Event.SubStartEvent;
+import net.ME1312.SubServers.Bungee.Host.Host;
+import net.ME1312.SubServers.Bungee.Host.RemotePlayer;
+import net.ME1312.SubServers.Bungee.Host.SubCreator;
+import net.ME1312.SubServers.Bungee.Host.SubServer;
+import net.ME1312.SubServers.Bungee.Library.Metrics;
+import net.ME1312.SubServers.Bungee.SubAPI;
+import net.ME1312.SubServers.Bungee.SubProxy;
+
+import net.md_5.bungee.api.plugin.Listener;
+import net.md_5.bungee.api.plugin.Plugin;
+import net.md_5.bungee.event.EventHandler;
+import net.md_5.bungee.event.EventPriority;
+
+import javax.swing.*;
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.logging.Level;
+
+public final class ConsolePlugin extends Plugin implements Listener {
+ public YAMLConfig config;
+ private JettyServer jettyServer;
+
+ @Override
+ public void onEnable() {
+ reload();
+
+ new Metrics(this, 3853).addPlatformCharts();
+
+ jettyServer = new JettyServer();
+ try {
+ jettyServer.start();
+ } catch (Exception e) {
+ getLogger().log(Level.SEVERE, "An error occurred when enabling the webserver, Plugin disabling...", e);
+ this.onDisable();
+ getProxy().getPluginManager().unregisterListeners(this);
+ getProxy().getPluginManager().unregisterCommands(this);
+ }
+
+ SubAPI.getInstance().addListener(new Runnable() {
+ @Override
+ public void run() {
+ reload();
+ }
+ });
+ }
+
+ private void reload() {
+
+ }
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void onServerCreate(SubCreateEvent event) {
+ if (!event.isCancelled()) {
+
+ }
+ }
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void onServerStart(SubStartEvent event) {
+ if (!event.isCancelled()) {
+
+ }
+ }
+
+ @Override
+ public void onDisable() {
+ try {
+ jettyServer.server.stop();
+ } catch (Exception e) {
+ getLogger().log(Level.SEVERE, "An error occurred when disabling the plugin", e);
+ }
+ }
+
+ @Override
+ public SubProxy getProxy() {
+ return (SubProxy) super.getProxy();
+ }
+}
diff --git a/SubServers.Web/src/net/ME1312/SubServers/Web/JettyServer.java b/SubServers.Web/src/net/ME1312/SubServers/Web/JettyServer.java
new file mode 100644
index 00000000..c213c035
--- /dev/null
+++ b/SubServers.Web/src/net/ME1312/SubServers/Web/JettyServer.java
@@ -0,0 +1,23 @@
+package net.ME1312.SubServers.Web;
+
+import net.ME1312.SubServers.Web.endpoints.BlockingServlet;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.ServletHandler;
+
+public class JettyServer {
+ public Server server;
+
+ public void start() throws Exception {
+ server = new Server();
+ ServerConnector connector = new ServerConnector(server);
+ connector.setPort(8090);
+ server.setConnectors(new Connector[] {connector});
+
+ ServletHandler handler = new ServletHandler();
+ server.setHandler(handler);
+ handler.addServletWithMapping(BlockingServlet.class, "/status");
+ server.start();
+ }
+}
\ No newline at end of file
diff --git a/SubServers.Web/src/net/ME1312/SubServers/Web/Library/HTMLogger.java b/SubServers.Web/src/net/ME1312/SubServers/Web/Library/HTMLogger.java
new file mode 100644
index 00000000..bcdbaf5d
--- /dev/null
+++ b/SubServers.Web/src/net/ME1312/SubServers/Web/Library/HTMLogger.java
@@ -0,0 +1,336 @@
+package net.ME1312.SubServers.Web.Library;
+
+import net.ME1312.Galaxi.Library.Container.Container;
+
+import org.fusesource.jansi.AnsiOutputStream;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.LinkedList;
+import java.util.Locale;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+public class HTMLogger extends AnsiOutputStream {
+ private static final String[] ANSI_COLOR_MAP = new String[]{"000000", "cd0000", "25bc24", "d7d700", "0000c3", "be00be", "00a5dc", "cccccc"};
+ private static final String[] ANSI_BRIGHT_COLOR_MAP = new String[]{"808080", "ff0000", "31e722", "ffff00", "0000ff", "ff00ff", "00c8ff", "ffffff"};
+ private static final byte[] BYTES_NBSP = "\u00A0".getBytes(UTF_8);
+ private static final byte[] BYTES_AMP = "&".getBytes(UTF_8);
+ private static final byte[] BYTES_LT = "<".getBytes(UTF_8);
+ private LinkedList currentAttributes = new LinkedList();
+ private LinkedList queue = new LinkedList();
+ private OutputStream raw;
+ protected boolean ansi = true;
+ protected boolean nbsp = false;
+ private boolean underline = false;
+ private boolean strikethrough = false;
+
+ public static HTMLogger wrap(OutputStream raw) {
+ return wrap(raw, new HTMConstructor() {
+ @Override
+ public HTMLogger construct(OutputStream raw, OutputStream wrapped) {
+ return new HTMLogger(raw, wrapped);
+ }
+ });
+ }
+
+ public static T wrap(final OutputStream raw, HTMConstructor constructor) {
+ final Container html = new Container(null);
+ html.value(constructor.construct(raw, new OutputStream() {
+ private boolean nbsp = false;
+
+ @Override
+ public void write(int data) throws IOException {
+ HTMLogger htm = html.value();
+ if (htm.queue.size() > 0) {
+ LinkedList queue = htm.queue;
+ htm.queue = new LinkedList<>();
+ for (String attr : queue) {
+ htm.write('<' + attr + '>');
+ htm.currentAttributes.addFirst(attr);
+ }
+ }
+
+ if (data == 32) {
+ if (htm.nbsp) {
+ if (nbsp) raw.write(BYTES_NBSP);
+ else raw.write(data);
+ nbsp = !nbsp;
+ } else raw.write(data);
+ } else {
+ nbsp = false;
+ switch(data) {
+ case '&':
+ raw.write(BYTES_AMP);
+ break;
+ case '<':
+ raw.write(BYTES_LT);
+ break;
+ case '\n':
+ htm.closeAttributes();
+ default:
+ raw.write(data);
+ }
+ }
+ }
+ }));
+ return html.value();
+ }
+ public HTMLogger(final OutputStream raw, OutputStream wrapped) {
+ super(wrapped);
+ this.raw = raw;
+ }
+ public interface HTMConstructor {
+ T construct(OutputStream raw, OutputStream wrapped);
+ }
+
+ private void write(String s) throws IOException {
+ raw.write(s.getBytes(UTF_8));
+ }
+
+ private void writeAttribute(String attr) throws IOException {
+ queue.add(attr);
+ }
+
+ public void closeAttribute(String s) throws IOException {
+
+ // Try to remove a tag that doesn't exist yet first
+ String[] queue = this.queue.toArray(new String[0]);
+ for (int i = queue.length; i > 0;) {
+ String attr = queue[--i];
+ if (attr.toLowerCase().startsWith(s.toLowerCase())) {
+ this.queue.removeLastOccurrence(attr);
+ return;
+ }
+ }
+
+ // Close a tag that we've already written
+ LinkedList closedAttributes = new LinkedList();
+ LinkedList currentAttributes = new LinkedList(this.currentAttributes);
+ LinkedList unclosedAttributes = new LinkedList();
+
+ for (String attr : currentAttributes) {
+ if (attr.toLowerCase().startsWith(s.toLowerCase())) {
+ for (String a : unclosedAttributes) {
+ closedAttributes.add(a);
+ this.currentAttributes.removeFirst();
+ write("" + a.split(" ", 2)[0] + '>');
+ }
+ unclosedAttributes.clear();
+ this.currentAttributes.removeFirst();
+ write("" + attr.split(" ", 2)[0] + '>');
+ break;
+ } else {
+ unclosedAttributes.add(attr);
+ }
+ }
+
+ // Queue unrelated tags to be re-opened
+ for (String attr : closedAttributes) {
+ this.queue.addFirst(attr);
+ }
+ }
+
+ public void closeAttributes() throws IOException {
+ queue.clear();
+
+ for (String attr : currentAttributes) {
+ write("" + attr.split(" ", 2)[0] + ">");
+ }
+
+ underline = false;
+ strikethrough = false;
+ currentAttributes.clear();
+ }
+
+ @Override
+ protected void processDeleteLine(int amount) throws IOException {
+ super.processDeleteLine(amount);
+ }
+
+ private void renderTextDecoration() throws IOException {
+ String dec = "";
+ if (underline) dec += " underline";
+ if (strikethrough) dec += " line-through";
+
+ closeAttribute("span style=\"text-decoration:");
+ if (dec.length() != 0) writeAttribute("span style=\"text-decoration:" + dec.substring(1) + "\"");
+ }
+
+ @Override
+ protected void processSetAttribute(int attribute) throws IOException {
+ if (ansi) switch(attribute) {
+ case 1:
+ closeAttribute("b");
+ writeAttribute("b");
+ break;
+ case 3:
+ closeAttribute("i");
+ writeAttribute("i");
+ break;
+ case 4:
+ underline = true;
+ renderTextDecoration();
+ break;
+ case 9:
+ strikethrough = true;
+ renderTextDecoration();
+ break;
+ case 22:
+ closeAttribute("b");
+ break;
+ case 23:
+ closeAttribute("i");
+ break;
+ case 24:
+ underline = false;
+ renderTextDecoration();
+ break;
+ case 29:
+ strikethrough = false;
+ renderTextDecoration();
+ break;
+ case 73:
+ closeAttribute("su");
+ writeAttribute("sup");
+ break;
+ case 74:
+ closeAttribute("su");
+ writeAttribute("sub");
+ break;
+ case 75:
+ closeAttribute("su");
+ break;
+ }
+ }
+
+ @Override
+ protected void processUnknownOperatingSystemCommand(int label, String arg) {
+ try {
+ if (ansi && label == 8) {
+ closeAttribute("a");
+ String[] args = arg.split(";", 3);
+ if (args.length > 1 && args[1].length() > 0 && allowHyperlink(args[1])) {
+ writeAttribute("a href=\"" + args[1].replace("&", "&").replace("<", "<").replace("\"", """) + "\" target=\"_blank\"");
+ }
+ }
+ } catch (Exception e) {}
+ }
+
+ protected boolean allowHyperlink(String link) {
+ if (link.toLowerCase(Locale.ENGLISH).startsWith("mailto:execute@galaxi.engine")) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ @Override
+ protected void processAttributeRest() throws IOException {
+ closeAttributes();
+ }
+
+ private String parse256(int color) throws IOException {
+ if (color < 8) {
+ return ANSI_COLOR_MAP[color];
+ } else if (color < 16) {
+ return ANSI_BRIGHT_COLOR_MAP[color - 8];
+ } else if (color < 232) {
+ float x = color - 16;
+ int r = (int) ((x / 36f) * 51);
+ x = (x % 36f) ;
+ int g = (int) ((x / 6f) * 51);
+ int b = (int) ((x % 6f) * 51);
+ return ((r >= 16)?"":"0") + Integer.toString(r, 16) + ((g >= 16)?"":"0") + Integer.toString(g, 16) + ((b >= 16)?"":"0") + Integer.toString(b, 16);
+ } else if (color < 256) {
+ int gray = (int) (10.2f * (color - 231));
+ return ((gray >= 16)?"":"0") + Integer.toString(gray, 16) + ((gray >= 16)?"":"0") + Integer.toString(gray, 16) + ((gray >= 16)?"":"0") + Integer.toString(gray, 16);
+ } else {
+ throw new IOException("Invalid 8 Bit Color: " + color);
+ }
+ }
+
+ @Override
+ protected void processDefaultTextColor() throws IOException {
+ closeAttribute("span style=\"color:");
+ }
+
+ @Override
+ protected void processSetForegroundColor(int color) throws IOException {
+ processSetForegroundColor(color, false);
+ }
+
+ @Override
+ protected void processSetForegroundColor(int color, boolean bright) throws IOException {
+ if (ansi) {
+ processDefaultTextColor();
+ writeAttribute("span style=\"color:#" + ((!bright)?ANSI_COLOR_MAP:ANSI_BRIGHT_COLOR_MAP)[color] + "\"");
+ renderTextDecoration();
+ }
+ }
+
+ @Override
+ protected void processSetForegroundColorExt(int index) throws IOException {
+ if (ansi) {
+ processDefaultTextColor();
+ writeAttribute("span style=\"color:#" + parse256(index) + "\"");
+ renderTextDecoration();
+ }
+ }
+
+ @Override
+ protected void processSetForegroundColorExt(int r, int g, int b) throws IOException {
+ if (ansi) {
+ processDefaultTextColor();
+ writeAttribute("span style=\"color:#" + ((r >= 16)?"":"0") + Integer.toString(r, 16) + ((g >= 16)?"":"0") + Integer.toString(g, 16) + ((b >= 16)?"":"0") + Integer.toString(b, 16) + "\"");
+ renderTextDecoration();
+ }
+ }
+
+ @Override
+ protected void processDefaultBackgroundColor() throws IOException {
+ closeAttribute("span style=\"background-color:");
+ }
+
+ @Override
+ protected void processSetBackgroundColor(int color) throws IOException {
+ processSetBackgroundColor(color, false);
+ }
+
+ @Override
+ protected void processSetBackgroundColor(int color, boolean bright) throws IOException {
+ if (ansi) {
+ processDefaultBackgroundColor();
+ writeAttribute("span style=\"background-color:#" + ((!bright)?ANSI_COLOR_MAP:ANSI_BRIGHT_COLOR_MAP)[color] + "\"");
+ }
+ }
+
+ @Override
+ protected void processSetBackgroundColorExt(int index) throws IOException {
+ if (ansi) {
+ processDefaultBackgroundColor();
+ writeAttribute("span style=\"background-color:#" + parse256(index) + "\"");
+ }
+ }
+
+ @Override
+ protected void processSetBackgroundColorExt(int r, int g, int b) throws IOException {
+ if (ansi) {
+ processDefaultBackgroundColor();
+ writeAttribute("span style=\"background-color:#" + ((r >= 16)?"":"0") + Integer.toString(r, 16) + ((g >= 16)?"":"0") + Integer.toString(g, 16) + ((b >= 16)?"":"0") + Integer.toString(b, 16) + "\"");
+ }
+ }
+
+ @Override
+ public void flush() throws IOException {
+ super.flush();
+ raw.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ closeAttributes();
+ super.close();
+ raw.close();
+ }
+}
diff --git a/SubServers.Web/src/net/ME1312/SubServers/Web/endpoints/BlockingServlet.java b/SubServers.Web/src/net/ME1312/SubServers/Web/endpoints/BlockingServlet.java
new file mode 100644
index 00000000..54694311
--- /dev/null
+++ b/SubServers.Web/src/net/ME1312/SubServers/Web/endpoints/BlockingServlet.java
@@ -0,0 +1,16 @@
+package net.ME1312.SubServers.Web.endpoints;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+public class BlockingServlet extends HttpServlet {
+
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ response.setContentType("application/json");
+ response.setStatus(HttpServletResponse.SC_OK);
+ response.getWriter().println("{ \"status\": \"ok\"}");
+ }
+}
diff --git a/pom.xml b/pom.xml
index 3fa6be37..85b8ef75 100644
--- a/pom.xml
+++ b/pom.xml
@@ -16,5 +16,6 @@
SubServers.Host
SubServers.Sync
SubServers.Sync/velocity
+ SubServers.Web
\ No newline at end of file