diff --git a/src/main/java/mdnet/base/MangaDexClient.java b/src/main/java/mdnet/base/MangaDexClient.java index b02ba49..e818258 100644 --- a/src/main/java/mdnet/base/MangaDexClient.java +++ b/src/main/java/mdnet/base/MangaDexClient.java @@ -1,15 +1,15 @@ package mdnet.base; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import mdnet.base.settings.ClientSettings; import mdnet.cache.DiskLruCache; +import mdnet.webui.WebConsole; import org.http4k.server.Http4kServer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; +import java.io.*; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -171,36 +171,63 @@ public class MangaDexClient { + ") initializing\n"); System.out.println("Copyright (c) 2020, MangaDex Network"); + String file = "settings.json"; + if (args.length == 1) { + file = args[0]; + } else if (args.length != 0) { + MangaDexClient.dieWithError("Expected one argument: path to config file, or nothing"); + } + + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + ClientSettings settings; + try { - String file = "settings.json"; - if (args.length == 1) { - file = args[0]; - } else if (args.length != 0) { - MangaDexClient.dieWithError("Expected one argument: path to config file, or nothing"); + settings = gson.fromJson(new FileReader(file), ClientSettings.class); + } catch (FileNotFoundException ignored) { + settings = new ClientSettings(); + LOGGER.warn("Settings file {} not found, generating file", file); + try (FileWriter writer = new FileWriter(file)) { + writer.write(gson.toJson(settings)); + } catch (IOException e) { + MangaDexClient.dieWithError(e); } + } - ClientSettings settings = new Gson().fromJson(new FileReader(file), ClientSettings.class); + if (!ClientSettings.isSecretValid(settings.getClientSecret())) + MangaDexClient.dieWithError("Config Error: API Secret is invalid, must be 52 alphanumeric characters"); - if (!ClientSettings.isSecretValid(settings.getClientSecret())) - MangaDexClient.dieWithError("Config Error: API Secret is invalid, must be 52 alphanumeric characters"); + if (settings.getClientPort() == 0) { + MangaDexClient.dieWithError("Config Error: Invalid port number"); + } - if (settings.getClientPort() == 0) { - MangaDexClient.dieWithError("Config Error: Invalid port number"); - } + if (settings.getMaxCacheSizeMib() < 1024) { + MangaDexClient.dieWithError("Config Error: Invalid max cache size, must be >= 1024 MiB (1GiB)"); + } - if (settings.getMaxCacheSizeMib() < 1024) { - MangaDexClient.dieWithError("Config Error: Invalid max cache size, must be >= 1024 MiB (1GiB)"); - } + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Client settings loaded: {}", settings); + } - if (LOGGER.isInfoEnabled()) { - LOGGER.info("Client settings loaded: {}", settings); - } + MangaDexClient client = new MangaDexClient(settings); + Runtime.getRuntime().addShutdownHook(new Thread(client::shutdown)); + client.runLoop(); - MangaDexClient client = new MangaDexClient(settings); - Runtime.getRuntime().addShutdownHook(new Thread(client::shutdown)); - client.runLoop(); - } catch (FileNotFoundException e) { - MangaDexClient.dieWithError(e); + if (settings.getWebSettings() != null) { + // java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream(); + // System.setOut(new java.io.PrintStream(out)); + // TODO: system.out redirect + ClientSettings finalSettings = settings; + new Thread(() -> { + WebConsole webConsole = new WebConsole(finalSettings.getWebSettings().getClientWebsocketPort()) { + @Override + protected void parseMessage(String message) { + System.out.println(message); + // TODO: something happens here + // the message should be formatted in json + } + }; + // TODO: webConsole.sendMessage(t,m) whenever system.out is written to + }).start(); } } diff --git a/src/main/java/mdnet/base/ServerHandler.java b/src/main/java/mdnet/base/ServerHandler.java index a6a3e00..a639bde 100644 --- a/src/main/java/mdnet/base/ServerHandler.java +++ b/src/main/java/mdnet/base/ServerHandler.java @@ -3,6 +3,7 @@ package mdnet.base; import kong.unirest.HttpResponse; import kong.unirest.Unirest; import kong.unirest.json.JSONObject; +import mdnet.base.settings.ClientSettings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/mdnet/base/ClientSettings.java b/src/main/java/mdnet/base/settings/ClientSettings.java similarity index 76% rename from src/main/java/mdnet/base/ClientSettings.java rename to src/main/java/mdnet/base/settings/ClientSettings.java index fdf55b2..8e854c5 100644 --- a/src/main/java/mdnet/base/ClientSettings.java +++ b/src/main/java/mdnet/base/settings/ClientSettings.java @@ -1,4 +1,4 @@ -package mdnet.base; +package mdnet.base.settings; import com.google.gson.annotations.SerializedName; @@ -18,15 +18,28 @@ public final class ClientSettings { private final String clientSecret; @SerializedName("threads") private final int threads; + @SerializedName("web_settings") + private final WebSettings webSettings; + + public ClientSettings() { + this.maxCacheSizeMib = 20480; + this.maxBandwidthMibPerHour = 0; + this.maxBurstRateKibPerSecond = 0; + this.clientPort = 1200; + this.clientSecret = "PASTE-YOUR-SECRET-HERE"; + this.threads = 32; + this.webSettings = new WebSettings(); + } public ClientSettings(long maxCacheSizeMib, long maxBandwidthMibPerHour, long maxBurstRateKibPerSecond, - int clientPort, String clientSecret, int threads) { + int clientPort, String clientSecret, int threads, WebSettings webSettings) { this.maxCacheSizeMib = maxCacheSizeMib; this.maxBandwidthMibPerHour = maxBandwidthMibPerHour; this.maxBurstRateKibPerSecond = maxBurstRateKibPerSecond; this.clientPort = clientPort; this.clientSecret = Objects.requireNonNull(clientSecret); this.threads = threads; + this.webSettings = webSettings; } public long getMaxCacheSizeMib() { @@ -48,9 +61,12 @@ public final class ClientSettings { public String getClientSecret() { return clientSecret; } + public WebSettings getWebSettings() { + return webSettings; + } public int getThreads() { - return (threads > 0) ? threads : 16; + return threads; } @Override diff --git a/src/main/java/mdnet/base/settings/WebSettings.java b/src/main/java/mdnet/base/settings/WebSettings.java new file mode 100644 index 0000000..7708789 --- /dev/null +++ b/src/main/java/mdnet/base/settings/WebSettings.java @@ -0,0 +1,25 @@ +package mdnet.base.settings; + +import com.google.gson.annotations.SerializedName; + +public final class WebSettings { + @SerializedName("client_websocket_port") + private final int clientWebsocketPort; + + public WebSettings() { + this.clientWebsocketPort = 33333; + } + + public WebSettings(int clientWebsocketPort) { + this.clientWebsocketPort = clientWebsocketPort; + } + + public int getClientWebsocketPort() { + return clientWebsocketPort; + } + + @Override + public String toString() { + return "WebSettings{" + "clientWebsocketPort=" + clientWebsocketPort + '}'; + } +} diff --git a/src/main/java/mdnet/webui/WebConsole.java b/src/main/java/mdnet/webui/WebConsole.java new file mode 100644 index 0000000..9515793 --- /dev/null +++ b/src/main/java/mdnet/webui/WebConsole.java @@ -0,0 +1,107 @@ +package mdnet.webui; + +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicReference; +import mdnet.base.Statistics; +import org.java_websocket.WebSocket; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.server.WebSocketServer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class WebConsole extends WebSocketServer { + + private final static Logger LOGGER = LoggerFactory.getLogger(WebConsole.class); + + public WebConsole(int port) { + super(new InetSocketAddress(port)); + } + + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + LOGGER.info("Webclient {} connected", conn); + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + LOGGER.info("Webclient {} disconnected: {} ", conn, reason); + } + + @Override + public void onMessage(WebSocket conn, String message) { + parseMessage(message); + } + + @Override + public void onMessage(WebSocket conn, ByteBuffer message) { + // parseMessage(message.array().toString()); + } + + @Override + public void onError(WebSocket conn, Exception ex) { + ex.printStackTrace(); + if (conn != null) { + // some errors like port binding failed may not be assignable to a specific + // websocket + } + } + + @Override + public void onStart() { + LOGGER.info("Listening for connections on port: {}", this.getPort()); + setConnectionLostTimeout(0); + setConnectionLostTimeout(100); + } + + protected abstract void parseMessage(String message); + + // void parseCommand(String x) { + // switch (x) { + // case "help": + // this.broadcast(formatMessage("command", "Available commands:")); + // this.broadcast(formatMessage("command", "you")); + // this.broadcast(formatMessage("command", "are")); + // this.broadcast(formatMessage("command", "big")); + // this.broadcast(formatMessage("command", "gay")); + // break; + // case "stop": + // this.broadcast(formatMessage("command", "Mangadex Client has shut down, + // shutting down web client now")); + // return; + // default: + // this.broadcast(formatMessage("command", "That command was not recognized")); + // this.broadcast(formatMessage("command", "Try help for a list of available + // commands")); + // break; + // } + // } + + public void sendMessage(String type, Object message) { +// JSONObject out = new JSONObject(); +// switch (type) { +// case "command" : +// out.put("type", "command"); +// out.put("data", message.toString()); +// break; +// case "stats" : +// out.put("type", "stats"); +// AtomicReference temp = (AtomicReference) message; +// out.put("hits", temp.get().getCacheHits()); +// out.put("misses", temp.get().getCacheMisses()); +// out.put("bytes_sent", temp.get().getBytesSent()); +// out.put("req_served", temp.get().getRequestsServed()); +// out.put("dataval", "empty"); +// out.put("dataval", "empty"); +// out.put("dataval", "empty"); +// break; +// case "auth" : +// break; +// default : +// out.put("type", "command"); +// out.put("data", message.toString()); +// break; +// } +// broadcast(out.toString()); + } +} diff --git a/src/main/kotlin/mdnet/base/Application.kt b/src/main/kotlin/mdnet/base/Application.kt index 5820604..fbe4b3a 100644 --- a/src/main/kotlin/mdnet/base/Application.kt +++ b/src/main/kotlin/mdnet/base/Application.kt @@ -1,6 +1,7 @@ /* ktlint-disable no-wildcard-imports */ package mdnet.base +import mdnet.base.settings.ClientSettings import mdnet.cache.DiskLruCache import org.apache.http.client.config.CookieSpecs import org.apache.http.client.config.RequestConfig @@ -18,8 +19,10 @@ import org.http4k.filter.CachingFilters import org.http4k.filter.MaxAgeTtl import org.http4k.filter.ServerFilters import org.http4k.lens.Path +import org.http4k.routing.ResourceLoader import org.http4k.routing.bind import org.http4k.routing.routes +import org.http4k.routing.singlePageApp import org.http4k.server.Http4kServer import org.http4k.server.asServer import org.slf4j.LoggerFactory @@ -211,7 +214,9 @@ fun getServer(cache: DiskLruCache, serverSettings: ServerSettings, clientSetting "/data/{chapterHash}/{fileName}" bind Method.GET to app(false), "/data-saver/{chapterHash}/{fileName}" bind Method.GET to app(true), "/{token}/data/{chapterHash}/{fileName}" bind Method.GET to app(false), - "/{token}/data-saver/{chapterHash}/{fileName}" bind Method.GET to app(true) + "/{token}/data-saver/{chapterHash}/{fileName}" bind Method.GET to app(true), + + singlePageApp(ResourceLoader.Classpath("/webui")) ) ) .asServer(Netty(serverSettings.tls, clientSettings, statistics)) diff --git a/src/main/resources/webui/dataReceive.js b/src/main/resources/webui/dataReceive.js new file mode 100644 index 0000000..a23255a --- /dev/null +++ b/src/main/resources/webui/dataReceive.js @@ -0,0 +1,405 @@ +let connection; +let theme; +let style; +let port; +let ip; +let refreshRate; +let maxConsoleLines; +let graphTimeFrame; +let showConsoleLatest; +let doAnimations; +//non-option var +let statRequest; +//stat vars + + +jQuery(document).ready(function () { + loadOptions(); + $("#theme").attr("href", "themes/" + theme + ".css"); + $("#style").attr("href", "themes/" + style + ".css"); + if (doAnimations) { + $(".optionInput").addClass("smooth"); + $(".slider").addClass("smoothslider").addClass("smooth"); + $(".content").addClass("slide_up"); + $(".sideOption").addClass("smooth"); + $(".button").addClass("smooth"); + } + if (showConsoleLatest) + $("#consoleLatest").attr("hidden", false); + reconnect(); + $("#console_input").keyup(function (e) { + if (e.keyCode === 13) { + sendCommand($(this).text()); + $(this).text(""); + $('#console_text').scrollTop($("#console_text")[0].scrollHeight) + } + }) +}); + +//site functions, no connections involved + +$(window).on("click", function () { + let sideBar = $("#sideBar"); + if (sideBar.hasClass("expanded")) { + sideBar.removeClass("expanded").addClass("retract").on( + "animationend webkitAnimationEnd oAnimationEnd MSAnimationEnd", + function () { + $(this).removeClass("retract").removeClass("expanded"); + } + ); + } +}); + +function loadOptions() { + let options = JSON.parse(localStorage.getItem("options")); + if (options === null) { + options = { + refresh_rate: 5000, + theme: "lightTheme", + style: "sharpStyle", + client_port: 33333, + client_ip: "localhost", + max_console_lines: 1000, + show_console_latest: false, + graph_time_frame: 30000, + do_animations: true + } + } + theme = options.theme; + style = options.style; + port = options.client_port; + ip = options.client_ip; + refreshRate = options.refresh_rate; + maxConsoleLines = options.max_console_lines; + graphTimeFrame = options.graph_time_frame; + showConsoleLatest = options.show_console_latest; + doAnimations = options.do_animations; + $("#dataRefreshRate").val(refreshRate); + $("#port").val(port); + $("#ip").val(ip); + $("#maxConsoleLines").val(maxConsoleLines); + $("#graphTimeFrame").val(graphTimeFrame); + $("#themeIn").val(theme); + $("#styleIn").val(style); + $("#newestconsole").prop("checked", showConsoleLatest); + $("#doAnimations").prop("checked", doAnimations); +} + +function resetOptions() { + if (confirm("Do you really want to reset to defaults?")) { + $("#dataRefreshRate").val(5000); + $("#port").val(33333); + $("#ip").val("localhost"); + $("#maxConsoleLines").val(1000); + $("#graphTimeFrame").val(30000); + $("#themeIn").val("lightTheme"); + $("#styleIn").val("sharpStyle"); + $("#newestconsole").prop("checked", false); + $("#doAnimations").prop("checked", true); + applyOptions() + } +} + +function applyOptions() { + let options = { + refresh_rate: parseInt($("#dataRefreshRate").val()), + theme: $("#themeIn").val(), + style: $("#styleIn").val(), + client_port: parseInt($("#port").val()), + client_ip: $("#ip").val(), + max_console_lines: parseInt($("#maxConsoleLines").val()), + show_console_latest: $("#newestconsole").prop("checked"), + graph_time_frame: parseInt($("#graphTimeFrame").val()), + do_animations: $("#doAnimations").prop("checked") + }; + if (options.do_animations !== doAnimations) { + doAnimations = options.do_animations; + if (doAnimations) { + $(".optionInput").addClass("smooth"); + $(".slider").addClass("smoothslider").addClass("smooth"); + $(".content").addClass("slide_up"); + $(".sideOption").addClass("smooth"); + $(".button").addClass("smooth"); + } else { + $(".optionInput").removeClass("smooth"); + $(".slider").removeClass("smoothslider").removeClass("smooth"); + $(".content").removeClass("slide_up"); + $(".sideOption").removeClass("smooth"); + $(".button").removeClass("smooth"); + } + $("#doAnimationscb").addClass("updated").on( + "animationend webkitAnimationEnd oAnimationEnd MSAnimationEnd", + function () { + $(this).removeClass("updated"); + } + ).prop("checked", doAnimations); + } + if (options.refresh_rate !== refreshRate) { + console.log(options.refresh_rate + " " + refreshRate); + refreshRate = Math.max(options.refresh_rate, 500); + $("#dataRefreshRate").addClass("updated").on( + "animationend webkitAnimationEnd oAnimationEnd MSAnimationEnd", + function () { + $(this).removeClass("updated"); + } + ).val(refreshRate); + } + if (options.style !== style) { + style = options.style; + applyStyle(options.style); + $("#styleIn").addClass("updated").on( + "animationend webkitAnimationEnd oAnimationEnd MSAnimationEnd", + function () { + $(this).removeClass("updated"); + } + ); + } + if (options.theme !== theme) { + theme = options.theme; + applyTheme(options.theme); + $("#themeIn").addClass("updated").on( + "animationend webkitAnimationEnd oAnimationEnd MSAnimationEnd", + function () { + $(this).removeClass("updated"); + } + ); + } + if (options.client_port !== port) { + port = options.client_port; + $("#port").addClass("updated").on( + "animationend webkitAnimationEnd oAnimationEnd MSAnimationEnd", + function () { + $(this).removeClass("updated"); + } + ).val(port); + reconnect(); + } + if (options.client_ip !== ip) { + ip = options.client_ip; + $("#ip").addClass("updated").on( + "animationend webkitAnimationEnd oAnimationEnd MSAnimationEnd", + function () { + $(this).removeClass("updated"); + } + ).val(ip); + reconnect(); + } + if (options.graph_time_frame !== graphTimeFrame) { + graphTimeFrame = Math.max(options.graph_time_frame, 5000); + $("#graphTimeFrame").addClass("updated").on( + "animationend webkitAnimationEnd oAnimationEnd MSAnimationEnd", + function () { + $(this).removeClass("updated"); + } + ).val(graphTimeFrame); + } + if (options.max_console_lines !== maxConsoleLines) { + maxConsoleLines = Math.max(options.max_console_lines, 100); + $("#maxConsoleLines").addClass("updated").on( + "animationend webkitAnimationEnd oAnimationEnd MSAnimationEnd", + function () { + $(this).removeClass("updated"); + } + ).val(maxConsoleLines); + } + if (options.show_console_latest !== showConsoleLatest) { + showConsoleLatest = options.show_console_latest; + if (showConsoleLatest) + $("#consoleLatest").attr("hidden", false); + else + $("#consoleLatest").attr("hidden", true); + $("#newestconsolecb").addClass("updated").on( + "animationend webkitAnimationEnd oAnimationEnd MSAnimationEnd", + function () { + $(this).removeClass("updated"); + } + ).prop("checked", showConsoleLatest); + } + localStorage.setItem("options", JSON.stringify(options)); +} + +function selectTab(t, l) { + let sideBar = $("#sideBar"); + sideBar.children("div").each(function () { + let tmp = $(this); + if (tmp.attr("id") === t) { + tmp.addClass("sideSelected"); + } else + tmp.removeClass("sideSelected"); + }); + $("#content").children("div").each(function () { + let tmp = $(this); + if (tmp.attr("id") === l) { + tmp.attr("hidden", false); + } else + tmp.attr("hidden", true); + }); + if (sideBar.hasClass("expanded")) { + sideBar.removeClass("expanded").addClass("retract").on( + "animationend webkitAnimationEnd oAnimationEnd MSAnimationEnd", + function () { + $(this).removeClass("retract").removeClass("expanded"); + } + ); + } +} + +function expSide() { + let sideBar = $("#sideBar"); + if (sideBar.hasClass("expanded")) { + sideBar.removeClass("expanded").addClass("retract").on( + "animationend webkitAnimationEnd oAnimationEnd MSAnimationEnd", + function () { + $(this).removeClass("retract").removeClass("expanded"); + } + ); + } else { + sideBar.addClass("expand").on( + "animationend webkitAnimationEnd oAnimationEnd MSAnimationEnd", + function () { + $(this).addClass("expanded").removeClass("expand"); + } + ); + } + +} + +function applyTheme(t) { + if (doAnimations) + $("*").each(function () { + if (!$(this).attr("hidden")) + $(this).addClass("tempsmooth").on( + "webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend", + function () { + $(this).removeClass("tempsmooth"); + } + ); + }); + $("#theme").attr("href", "themes/" + t + ".css"); +} + +function applyStyle(s) { + if (doAnimations) + $("*").each(function () { + if (!$(this).attr("hidden")) + $(this).addClass("tempsmooth").on( + "webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend", + function () { + $(this).removeClass("tempsmooth"); + } + ); + }); + $("#style").attr("href", "themes/" + s + ".css"); +} + +//update data functions + +function updateWithMessage(m) { + //TODO: get this to talk with client + let result; + try { + result = JSON.parse(m); + switch (result.type) { + case "command": + updateConsole(result.data, 2); + break; + case "stats": + + updateValues(); + break; + default: + updateConsole("[WEB-INFO] The message received is improperly formatted: " + result.data, 2); + break; + } + } catch (e) { + updateConsole("[WEB-INFO] There was an error parsing the data \n" + e, 2); + } +} + +function updateValues() { + //TODO: use values and update web info +} + +//console functions + +function updateConsole(x, status) { + let scroll = false; + let temp = $('#console_text'); + let latest = $("#consoleLatest"); + if (temp.scrollTop() === (temp[0].scrollHeight - temp[0].clientHeight)) + scroll = true; + switch (status) { + case 1: + temp.append('
' + x + '
'); + break; + case 0: + temp.append('
' + x + '
'); + break; + default: + temp.append('
' + x + '
'); + latest.html('
' + x + '
'); + } + let childs = temp.children(); + if (childs.length > maxConsoleLines) { + let length = childs.length; + for (let i = 0; i < length - maxConsoleLines; i++) { + childs[i].remove(); + } + } + if (scroll) + temp.scrollTop(temp[0].scrollHeight); +} + +function sendCommand(x) { + if (x === "") + return; + if (connection.readyState === "OPEN") { + let data = { + type: "command", + data: x + }; + let message = JSON.stringify(data); + connection.send(message); + } else { + updateConsole(x, 0); + } +} + +//network commuication + +function reconnect() { + if (connection != null) + connection.close(); + updateConsole("[WEB-CONSOLE] Attempting to connect to client on " + ip + ":" + port, 2); + connection = new WebSocket("ws://" + ip + ":" + port); + $("#connection").removeClass("disconnected").removeClass("connected").addClass("connecting").text("Connecting"); + addListeners(connection) +} + +function addListeners(c) { + let opened = false; + c.onopen = function (event) { + $("#connection").removeClass("disconnected").removeClass("connecting").addClass("connected").text("Connected"); + opened = true; + updateConsole("[WEB-CONSOLE] Successfully to connect to client on " + ip + ":" + port, 2); + statRequest = setInterval(function () { + requestStats(); + }, refreshRate); + }; + c.onclose = function (event) { + $("#connection").addClass("disconnected").removeClass("connecting").removeClass("connected").text("Disconnected"); + if (opened) + updateConsole("[WEB-CONSOLE] Disconnected from client"); + else + updateConsole("[WEB-CONSOLE] Failed to connect to client on " + ip + ":" + port, 2); + clearInterval(statRequest); + }; + c.onmessage = function (event) { + updateWithMessage(event.data()); + }; +} + +function requestStats() { + let req = {type: "stats"}; + connection.send(JSON.stringify(req)); +} \ No newline at end of file diff --git a/src/main/resources/webui/icons/console.png b/src/main/resources/webui/icons/console.png new file mode 100644 index 0000000..560c4e4 Binary files /dev/null and b/src/main/resources/webui/icons/console.png differ diff --git a/src/main/resources/webui/icons/dashboard.png b/src/main/resources/webui/icons/dashboard.png new file mode 100644 index 0000000..e314766 Binary files /dev/null and b/src/main/resources/webui/icons/dashboard.png differ diff --git a/src/main/resources/webui/icons/info.png b/src/main/resources/webui/icons/info.png new file mode 100644 index 0000000..7e6f797 Binary files /dev/null and b/src/main/resources/webui/icons/info.png differ diff --git a/src/main/resources/webui/icons/options.png b/src/main/resources/webui/icons/options.png new file mode 100644 index 0000000..44a20ca Binary files /dev/null and b/src/main/resources/webui/icons/options.png differ diff --git a/src/main/resources/webui/icons/showmore.png b/src/main/resources/webui/icons/showmore.png new file mode 100644 index 0000000..839745f Binary files /dev/null and b/src/main/resources/webui/icons/showmore.png differ diff --git a/src/main/resources/webui/index.html b/src/main/resources/webui/index.html new file mode 100644 index 0000000..3e89d9d --- /dev/null +++ b/src/main/resources/webui/index.html @@ -0,0 +1,149 @@ + + + + + MD@H Client + + + + + + + +
+ + mangadex + +

MangaDex@Home Client Interface

+ + +
+ +
+
+
+

Dashboard

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + +
+ + \ No newline at end of file diff --git a/src/main/resources/webui/layout.css b/src/main/resources/webui/layout.css new file mode 100644 index 0000000..171f9f9 --- /dev/null +++ b/src/main/resources/webui/layout.css @@ -0,0 +1,372 @@ +body { + margin: 0; + font-family: Calibri, serif; + overflow: hidden; +} + +.smooth { + -webkit-transition: .4s; + transition: .4s; +} + +.tempsmooth { + -webkit-transition: .4s; + transition: .4s; +} + +/*Content holder positions*/ + +#pageBar { + height: 75px; + width: 100%; + position: absolute; +} + +#consoleLatest { + position: absolute; + min-height: 20px; + width: calc(100% - 755px); + margin: 30px 20px 25px 20px; + left: 545px; + overflow-x: scroll; + overflow-y: hidden; +} + +#consoleLatest::-webkit-scrollbar { + height: 3px; +} + +#connection { + position: absolute; + right: 0; + margin: 20px 20px 20px 20px; + width: 150px; + height: 35px; + border-style: solid; + outline: none; +} + +.connecting { + animation: connecting 1.5s; + animation-iteration-count: infinite; +} + +@keyframes connecting { + 0%, 100% { + filter: brightness(120%) + } + 50% { + filter: brightness(80%) + } +} + +#sideBar { + height: calc(100% - 75px); + width: 50px; + top: 75px; + position: absolute; + z-index: 10; + overflow: hidden; + user-select: none; +} + +.sideOption { + width: 100%; + height: 50px; + float: left;; +} + +.expand { + animation: expand 150ms ease-out; + -webkit-animation-fill-mode: forwards; +} + +.expanded { + width: 200px !important; +} + +.retract { + animation: expand 150ms reverse ease-in; + -webkit-animation-fill-mode: forwards; +} + +@keyframes expand { + 0% { + width: 50px + } + 100% { + width: 200px + } +} + + +#content { + height: calc(100% - 75px); + width: calc(100% - 50px); + top: 75px; + left: 50px; + position: absolute; + /*overflow-y: auto;*/ +} + +.contentHeader { + width: calc(100% - 40px); + height: 80px; + margin: 20px; +} + +/*Main dashboard positions*/ + +#dashb { + width: 100%; + height: 100%; + position: absolute; +} + +#nDat { + width: 40%; + height: calc(100% - 140px); + margin: 20px; + top: 100px; + position: absolute; + +} + +.numerical_data { + height: 150px; + width: calc(50% - 20px); + margin: 10px; + float: left; +} + +#gDat { + height: calc(100% - 140px); + width: calc(60% - 60px); + margin: 20px; + position: absolute; + top: 100px; + left: calc(40% + 20px); + overflow-y: scroll; +} + +.line_graph_data { + height: 200px; + width: calc((100% - 20px)); + margin: 10px; + float: left; +} + +/*Console and options positions*/ + +#console { + width: 100%; + height: 100%; + position: absolute; +} + +#buttonBoard { + width: calc(100% - 40px); + height: calc(40% - 20px); + margin: 20px; + position: absolute; + top: 100px; +} + +#liveConsole { + width: calc(100% - 40px); + height: calc(60% - 180px); + margin: 20px; + position: absolute; + top: calc(40% + 100px); + padding-bottom: 40px; + font-family: monospace; +} + +.consoleLine { + width: calc(100% - 5px); + float: left; + margin: 0 5px 0; + left: 0; + white-space: nowrap; +} + +.consoleLine > p { + margin: 0; +} + +#console_input { + position: absolute; + width: calc(100% - 30px); + height: 20px; + bottom: 10px; + left: 0; + margin: 0 15px; + padding-top: 10px; + padding-bottom: 10px; + white-space: nowrap; + border-width: 0; + outline: none; + background-color: inherit; + overflow: hidden; +} + +#console_text { + height: calc(100% - 40px); + width: calc(100% - 20px); + position: absolute; + outline: none; + border-width: 0; + resize: none; + font-family: monospace; + padding-left: 10px; + padding-right: 10px; + padding-bottom: 40px; + background-color: rgba(0, 0, 0, 0); + overflow: scroll; +} + +/*Web option positions*/ +#dashOptions { + width: 100%; + height: 100%; + position: absolute; +} + +#options { + width: calc(100% - 80px); + height: calc(100% - 140px); + position: absolute; + top: 100px; + margin: 20px 20px 20px 60px; +} + +#apply { + position: fixed; + bottom: 20px; + right: 20px; + width: 150px; + height: 30px; +} + +#reset{ + position: fixed; + bottom: 20px; + right: 190px; + width: 150px; + height: 30px; +} + +.option { + height: 40px; + margin: 10px; +} + +.option > h4 { + margin: 0; + float: left; + font-weight: normal; +} + +.optionLabel { + +} + +.optionInput { + left: 200px; + position: absolute; + border-style: solid; + border-width: 2px; +} + +.switchInput > span { + left: 200px; + position: absolute; + border-style: solid; +} + +.updated { + animation: fade 1.5s linear; +} + +@keyframes fade { + 0%, 100% { + filter: alpha(100%); + } + 100% { + filter: alpha(0%); + } +} + +/*misc modifications*/ + +.input { + outline: 0; +} + +.button { + outline: none; + border-width: 2px; +} + +.switch input { + opacity: 0; + width: 0; + height: 0; +} + +.slider { + +} + +.slider:before { + /*border-width: 1px;*/ + position: absolute; + content: ""; +} + +.smoothslider:before { + position: absolute; + content: ""; + -webkit-transition: .4s; + transition: .4s; +} + +/*Webkit modifications*/ + +::-webkit-scrollbar { + width: 10px; + height: 10px; +} + +::-webkit-scrollbar-button { + width: 0; + height: 0; +} + +::-webkit-scrollbar-thumb { +} + +::-webkit-scrollbar-track { + +} + +::-webkit-scrollbar-corner { + background-color: rgba(0, 0, 0, 0); +} + +/*animations*/ + +.slide_up { + animation: slideup .1s ease-out; +} + +@keyframes slideup { + 0% { + transform: translateY(50px); + opacity: 0; + } + 100% { + transform: translateY(0px); + opacity: 1; + } +} \ No newline at end of file diff --git a/src/main/resources/webui/themes/darkTheme.css b/src/main/resources/webui/themes/darkTheme.css new file mode 100644 index 0000000..cf2485d --- /dev/null +++ b/src/main/resources/webui/themes/darkTheme.css @@ -0,0 +1,167 @@ +body { + background-color: #404040; + color: #f0f0f0; +} + +#pageBar { + background-color: #303030; +} + +#consoleLatest{ + background-color: black; + color: #f0f0f0; +} + +.connected { + border-color: #0fff00 !important; +} + +.disconnected { + border-color: #e50100 !important; +} + +.connecting { + border-color: #e5d700 !important; +} + +#sideBar { + background-color: #303030; +} + +.sideOption { + background-color: #303030; +} + +.sideOption:hover { + background-color: #404040; +} + +.sideSelected { + background-color: #606060; +} + +#content { + background-color: #404040; +} + +.contentHeader { + background-color: #606060; +} + +/*Main dashboard colors*/ + +#dashb { + +} + +#nDat { +} + +.numerical_data { + background-color: #606060; +} + +#gDat { +} + +.line_graph_data { + background-color: #606060; +} + +/*Console and options colors*/ + +#liveConsole { + background-color: black; + caret-color: #f0f0f0; +} + +#console_text { + color: #f0f0f0; +} + +#console_input { + color: #f0f0f0; +} + +.unsent { + color: #e50100; +} + +.sent { + color: #0fff00; +} + + +/*Web option colors*/ +#dashOptions { +} + +#options { +} + +.option { +} + +.optionLabel { + +} + +.optionInput { + border-color: rgba(0, 0, 0, 0); + background-color: #606060; + color: #f0f0f0; +} + +/*misc*/ + +.button{ + border-color: rgba(0, 0, 0, 0); + background-color: #606060; + color: #f0f0f0; +} + +.button:hover{ + background-color: #909090; +} + +.img { + filter: invert(100%); +} + +.updated { + border-color: #1ec70d !important; +} + +.slider { + border-color: rgba(0, 0, 0, 0); + background-color: #606060; +} + +.slider::before{ + background-color: #f0f0f0; +} + +input:checked + .slider { + background-color: #909090; +} + +/*Webkit colors*/ + +::-webkit-scrollbar { +} + +::-webkit-scrollbar-button { + +} + +::-webkit-scrollbar-thumb { + background-color: #555555; +} + +::-webkit-scrollbar-thumb:hover { + background-color: #888888; +} + +::-webkit-scrollbar-track { + +} diff --git a/src/main/resources/webui/themes/eyekillerTheme.css b/src/main/resources/webui/themes/eyekillerTheme.css new file mode 100644 index 0000000..d020138 --- /dev/null +++ b/src/main/resources/webui/themes/eyekillerTheme.css @@ -0,0 +1,97 @@ +body { + background-color: #ffffff; + color: #202020; +} + +#pageBar { + background-color: #faff00; +} + +#sideBar { + background-color: #faff00; +} + +.sideOption { + +} + +.sideOption:hover { + background-color: #faff00; +} + +.sideSelected { + background-color: #faff00; +} + +#content { + background-color: #0fff00; +} + +.contentHeader { + background-color: #ff00af; +} + +/*Main dashboard colors*/ + +#dashb { + +} + +#nDat { +} + +.numerical_data { + background-color: #00ffec; +} + +#gDat { +} + +.line_graph_data { + background-color: #00ffec; +} + +/*Console and options colors*/ + +#liveConsole { + background-color: black; + caret-color: #f0f0f0; +} + +#console_text { + color: #f0f0f0; +} + +#console_input { + color: #f0f0f0; +} + +/*misc*/ + +.img { +} + +.updated { + border-color: #1ec70d; +} + +/*Webkit colors*/ + +::-webkit-scrollbar { +} + +::-webkit-scrollbar-button { + +} + +::-webkit-scrollbar-thumb { + background-color: #aaaaaa; +} + +::-webkit-scrollbar-thumb:hover { + background-color: #888888; +} + +::-webkit-scrollbar-track { + +} diff --git a/src/main/resources/webui/themes/lightTheme.css b/src/main/resources/webui/themes/lightTheme.css new file mode 100644 index 0000000..8076817 --- /dev/null +++ b/src/main/resources/webui/themes/lightTheme.css @@ -0,0 +1,166 @@ +body { + background-color: #ffffff; + color: #202020; +} + +#pageBar { + background-color: #f8f9fa; +} + +#consoleLatest{ + background-color: black; + color: #f0f0f0; +} + +.connected { + border-color: #0fff00 !important; +} + +.disconnected { + border-color: #e50100 !important; +} + +.connecting { + border-color: #e5d700 !important; +} + +#sideBar { + background-color: #f8f9fa; +} + +.sideOption { + +} + +.sideOption:hover { + background-color: #eeeeee; +} + +.sideSelected { + background-color: #e1e1e1; +} + +#content { + background-color: #ffffff; +} + +.contentHeader { + background-color: #ededed; +} + +/*Main dashboard colors*/ + +#dashb { + +} + +#nDat { +} + +.numerical_data { + background-color: #ededed; +} + +#gDat { +} + +.line_graph_data { + background-color: #ededed; +} + +/*Console and options colors*/ + +#liveConsole { + background-color: black; + caret-color: #f0f0f0; +} + +#console_text { + color: #f0f0f0; +} + +#console_input { + color: #f0f0f0; +} + +.unsent { + color: #cb0000; +} + +.sent { + color: #1ec70d; +} + + +/*Web option colors*/ +#dashOptions { +} + +#options { +} + +.option { +} + +.optionLabel { + +} + +.optionInput { + border-color: rgba(0, 0, 0, 0); + background-color: #eaeaea; + color: #202020; +} + +/*misc*/ + +.button{ + border-color: rgba(0, 0, 0, 0); + background-color: #eaeaea; + color: #202020; +} + +.button:hover{ + background-color: #dadada; +} + +.img { +} + +.updated { + border-color: #1ec70d !important; +} + +.slider { + border-color: rgba(0, 0, 0, 0); + background-color: #eaeaea; +} + +.slider::before{ + background-color: #202020; +} + +input:checked + .slider { + background-color: #adadad; +} + +/*Webkit colors*/ + +::-webkit-scrollbar { +} + +::-webkit-scrollbar-button { + +} + +::-webkit-scrollbar-thumb { + background-color: #cacaca; +} + +::-webkit-scrollbar-thumb:hover { + background-color: #dedede; +} + +::-webkit-scrollbar-track { + +} diff --git a/src/main/resources/webui/themes/midnightTheme.css b/src/main/resources/webui/themes/midnightTheme.css new file mode 100644 index 0000000..ea260b0 --- /dev/null +++ b/src/main/resources/webui/themes/midnightTheme.css @@ -0,0 +1,163 @@ +body { + background-color: #101010; + color: #bfbfbf; +} + +#pageBar { + background-color: #202020; +} + +#consoleLatest { + background-color: black; + color: #f0f0f0; +} + +.connected { + border-color: #0fff00 !important; +} + +.disconnected { + border-color: #e50100 !important; +} + +.connecting { + border-color: #e5d700 !important; +} + +#sideBar { + background-color: #202020; +} + +.sideOption:hover { + background-color: #404040; +} + +.sideSelected { + background-color: #505050; +} + +#content { + background-color: #101010; +} + +.contentHeader { + background-color: #404040; +} + +/*Main dashboard colors*/ + +#dashb { + +} + +#nDat { +} + +.numerical_data { + background-color: #404040; +} + +#gDat { +} + +.line_graph_data { + background-color: #404040; +} + +/*Console and options colors*/ + +#liveConsole { + background-color: black; + caret-color: #f0f0f0; +} + +#console_text { + color: #f0f0f0; +} + +#console_input { + color: #f0f0f0; +} + +.unsent { + color: #e50100; +} + +.sent { + color: #0fff00; +} + + +/*Web option colors*/ +#dashOptions { +} + +#options { +} + +.option { +} + +.optionLabel { + +} + +.optionInput { + border-color: rgba(0, 0, 0, 0); + background-color: #404040; + color: #bfbfbf; +} + +/*misc*/ + +.button{ + border-color: rgba(0, 0, 0, 0); + background-color: #404040; + color: #bfbfbf; +} + +.button:hover{ + background-color: #797979; +} + +.img { + filter: invert(100%); +} + +.updated { + border-color: #1ec70d !important; +} + +.slider { + border-color: rgba(0, 0, 0, 0); + background-color: #404040; +} + +.slider::before { + background-color: #bfbfbf; +} + +input:checked + .slider { + background-color: #757575; +} + +/*Webkit colors*/ + +::-webkit-scrollbar { +} + +::-webkit-scrollbar-button { + +} + +::-webkit-scrollbar-thumb { + background-color: #555555; +} + +::-webkit-scrollbar-thumb:hover { + background-color: #888888; +} + +::-webkit-scrollbar-track { + +} diff --git a/src/main/resources/webui/themes/sharpStyle.css b/src/main/resources/webui/themes/sharpStyle.css new file mode 100644 index 0000000..68e5d02 --- /dev/null +++ b/src/main/resources/webui/themes/sharpStyle.css @@ -0,0 +1,22 @@ +.input { + padding-left: 2px; +} + +.slider { + width: 60px; + height: 12px; + margin-top: 2px; +} + +.slider:before { + height: 12px; + width: 30px; + left: 0; + bottom: 0; +} + +input:checked + .slider:before { + -webkit-transform: translateX(30px); + -ms-transform: translateX(30px); + transform: translateX(30px); +} \ No newline at end of file diff --git a/src/main/resources/webui/themes/softStyle.css b/src/main/resources/webui/themes/softStyle.css new file mode 100644 index 0000000..2279813 --- /dev/null +++ b/src/main/resources/webui/themes/softStyle.css @@ -0,0 +1,145 @@ +/*Content holder positions*/ + +#pageBar { +} + +#connection { + border-radius: 10px; +} + +#consoleLatest { + border-radius: 10px; +} + +#sideBar { +} + +.sideSelected { + border-radius: 10px; +} + +.sideOption:hover { + border-radius: 10px; +} + +#expSide { +} + +#dash { +} + +#cons { +} + +#opt { +} + +#content { +} + +.contentHeader { + border-radius: 10px; +} + +/*Main dashboard positions*/ + +#dashb { +} + +#nDat { + +} + +.numerical_data { + border-radius: 10px; +} + +#gDat { + border-radius: 10px; +} + +.line_graph_data { + border-radius: 10px; +} + +/*Console and options positions*/ + +#console { +} + +#buttonBoard { +} + +#liveConsole { + border-radius: 10px; +} + +#console_input { +} + +#console_text { + border-radius: 10px; +} + +/*Web option positions*/ +#dashOptions { +} + +#options { +} + +#apply { + border-radius: 10px; +} + +.option { +} + +.optionLabel { + +} + +.optionInput { +} + +/*misc modifications*/ + +.input { + padding-left: 5px; + border-radius: 10px; +} + +.slider { + border-radius: 30px; + width: 60px; + height: 16px; +} + +.slider:before { + border-radius: 7px; + height: 14px; + width: 28px; + left: 3px; + bottom: 1px; +} + +input:checked + .slider:before { + -webkit-transform: translateX(26px); + -ms-transform: translateX(26px); + transform: translateX(26px); +} + +/*Webkit modifications*/ + +::-webkit-scrollbar { +} + +::-webkit-scrollbar-button { +} + +::-webkit-scrollbar-thumb { + border-radius: 10px; +} + +::-webkit-scrollbar-track { +} \ No newline at end of file