Squash a lot of things

This commit is contained in:
carbotaniuman 2020-06-11 16:06:57 -05:00
parent 5928e81907
commit 83663a786b
20 changed files with 1897 additions and 30 deletions

View file

@ -1,15 +1,15 @@
package mdnet.base; package mdnet.base;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import mdnet.base.settings.ClientSettings;
import mdnet.cache.DiskLruCache; import mdnet.cache.DiskLruCache;
import mdnet.webui.WebConsole;
import org.http4k.server.Http4kServer; import org.http4k.server.Http4kServer;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.File; import java.io.*;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -171,36 +171,63 @@ public class MangaDexClient {
+ ") initializing\n"); + ") initializing\n");
System.out.println("Copyright (c) 2020, MangaDex Network"); 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 { try {
String file = "settings.json"; settings = gson.fromJson(new FileReader(file), ClientSettings.class);
if (args.length == 1) { } catch (FileNotFoundException ignored) {
file = args[0]; settings = new ClientSettings();
} else if (args.length != 0) { LOGGER.warn("Settings file {} not found, generating file", file);
MangaDexClient.dieWithError("Expected one argument: path to config file, or nothing"); 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())) if (settings.getClientPort() == 0) {
MangaDexClient.dieWithError("Config Error: API Secret is invalid, must be 52 alphanumeric characters"); MangaDexClient.dieWithError("Config Error: Invalid port number");
}
if (settings.getClientPort() == 0) { if (settings.getMaxCacheSizeMib() < 1024) {
MangaDexClient.dieWithError("Config Error: Invalid port number"); MangaDexClient.dieWithError("Config Error: Invalid max cache size, must be >= 1024 MiB (1GiB)");
} }
if (settings.getMaxCacheSizeMib() < 1024) { if (LOGGER.isInfoEnabled()) {
MangaDexClient.dieWithError("Config Error: Invalid max cache size, must be >= 1024 MiB (1GiB)"); LOGGER.info("Client settings loaded: {}", settings);
} }
if (LOGGER.isInfoEnabled()) { MangaDexClient client = new MangaDexClient(settings);
LOGGER.info("Client settings loaded: {}", settings); Runtime.getRuntime().addShutdownHook(new Thread(client::shutdown));
} client.runLoop();
MangaDexClient client = new MangaDexClient(settings); if (settings.getWebSettings() != null) {
Runtime.getRuntime().addShutdownHook(new Thread(client::shutdown)); // java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream();
client.runLoop(); // System.setOut(new java.io.PrintStream(out));
} catch (FileNotFoundException e) { // TODO: system.out redirect
MangaDexClient.dieWithError(e); 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();
} }
} }

View file

@ -3,6 +3,7 @@ package mdnet.base;
import kong.unirest.HttpResponse; import kong.unirest.HttpResponse;
import kong.unirest.Unirest; import kong.unirest.Unirest;
import kong.unirest.json.JSONObject; import kong.unirest.json.JSONObject;
import mdnet.base.settings.ClientSettings;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;

View file

@ -1,4 +1,4 @@
package mdnet.base; package mdnet.base.settings;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
@ -18,15 +18,28 @@ public final class ClientSettings {
private final String clientSecret; private final String clientSecret;
@SerializedName("threads") @SerializedName("threads")
private final int 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, 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.maxCacheSizeMib = maxCacheSizeMib;
this.maxBandwidthMibPerHour = maxBandwidthMibPerHour; this.maxBandwidthMibPerHour = maxBandwidthMibPerHour;
this.maxBurstRateKibPerSecond = maxBurstRateKibPerSecond; this.maxBurstRateKibPerSecond = maxBurstRateKibPerSecond;
this.clientPort = clientPort; this.clientPort = clientPort;
this.clientSecret = Objects.requireNonNull(clientSecret); this.clientSecret = Objects.requireNonNull(clientSecret);
this.threads = threads; this.threads = threads;
this.webSettings = webSettings;
} }
public long getMaxCacheSizeMib() { public long getMaxCacheSizeMib() {
@ -48,9 +61,12 @@ public final class ClientSettings {
public String getClientSecret() { public String getClientSecret() {
return clientSecret; return clientSecret;
} }
public WebSettings getWebSettings() {
return webSettings;
}
public int getThreads() { public int getThreads() {
return (threads > 0) ? threads : 16; return threads;
} }
@Override @Override

View file

@ -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 + '}';
}
}

View file

@ -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<Statistics> temp = (AtomicReference<Statistics>) 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());
}
}

View file

@ -1,6 +1,7 @@
/* ktlint-disable no-wildcard-imports */ /* ktlint-disable no-wildcard-imports */
package mdnet.base package mdnet.base
import mdnet.base.settings.ClientSettings
import mdnet.cache.DiskLruCache import mdnet.cache.DiskLruCache
import org.apache.http.client.config.CookieSpecs import org.apache.http.client.config.CookieSpecs
import org.apache.http.client.config.RequestConfig 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.MaxAgeTtl
import org.http4k.filter.ServerFilters import org.http4k.filter.ServerFilters
import org.http4k.lens.Path import org.http4k.lens.Path
import org.http4k.routing.ResourceLoader
import org.http4k.routing.bind import org.http4k.routing.bind
import org.http4k.routing.routes import org.http4k.routing.routes
import org.http4k.routing.singlePageApp
import org.http4k.server.Http4kServer import org.http4k.server.Http4kServer
import org.http4k.server.asServer import org.http4k.server.asServer
import org.slf4j.LoggerFactory 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/{chapterHash}/{fileName}" bind Method.GET to app(false),
"/data-saver/{chapterHash}/{fileName}" bind Method.GET to app(true), "/data-saver/{chapterHash}/{fileName}" bind Method.GET to app(true),
"/{token}/data/{chapterHash}/{fileName}" bind Method.GET to app(false), "/{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)) .asServer(Netty(serverSettings.tls, clientSettings, statistics))

View file

@ -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('<div class="consoleLine sent">' + x + '</div>');
break;
case 0:
temp.append('<div class="consoleLine unsent">' + x + '</div>');
break;
default:
temp.append('<div class="consoleLine">' + x + '</div>');
latest.html('<div class="consoleLine">' + x + '</div>');
}
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));
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 756 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 723 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 793 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 B

View file

@ -0,0 +1,149 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>MD@H Client</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="dataReceive.js"></script>
<link rel="stylesheet" type="text/css" href="layout.css">
<link rel="stylesheet" type="text/css" href="" id="style">
<link rel="stylesheet" type="text/css" href="themes/darkTheme.css" id="theme">
</head>
<body>
<div id="pageBar">
<a href="https://mangadex.org/">
<img src="https://mangadex.org/images/misc/navbar.svg?3" alt="mangadex" width="65px"
height="65px" style="float: left; padding: 5px; border-radius: 50%">
</a>
<h1 style="position: absolute; left: 75px; margin-left: 10px">MangaDex@Home Client Interface</h1>
<div id="consoleLatest" hidden></div>
<button id="connection" class="connecting button" onclick="reconnect()">Disconnected</button>
</div>
<div id="sideBar">
<div id="expSide" class="sideOption" onclick="expSide()">
<img src="icons/showmore.png" alt="dash" width="30px" height="30px" style="padding: 10px" class="img">
<h2 style="position: absolute; left: 50px; top: 0; margin: calc((50px - 29px)/2)">Menu</h2>
</div>
<div id="dash" class="sideOption sideSelected" onclick="selectTab('dash','dashb')">
<img src="icons/dashboard.png" alt="dash" width="30px" height="30px" style="padding: 10px" class="img">
<h2 style="position: absolute; left: 50px; top: 50px; margin: calc((50px - 29px)/2)">Dashboard</h2>
</div>
<div id="cons" class="sideOption" onclick="selectTab('cons','console')">
<img src="icons/console.png" alt="dash" width="30px" height="30px" style="padding: 10px" class="img">
<h2 style="position: absolute; left: 50px; top: 100px; margin: calc((50px - 29px)/2)">Console</h2>
</div>
<div id="opt" class="sideOption" onclick="selectTab('opt','dashOptions')">
<img src="icons/options.png" alt="dash" width="30px" height="30px" style="padding: 10px" class="img">
<h2 style="position: absolute; left: 50px; top: 150px; margin: calc((50px - 29px)/2)">Options</h2>
</div>
<div id="inf" class="sideOption" onclick="selectTab('inf','info')">
<img src="icons/info.png" alt="dash" width="30px" height="30px" style="padding: 10px" class="img">
<h2 style="position: absolute; left: 50px; top: 200px; margin: calc((50px - 29px)/2)">Info</h2>
</div>
</div>
<div id="content">
<div id="dashb" class="content">
<div class="contentHeader">
<h1 style="margin: 0;position:absolute; top: 42px; padding-left: 30px">Dashboard</h1>
</div>
<div id="nDat">
<div id="hits" class="numerical_data"></div>
<div id="misses" class="numerical_data"></div>
<div id="hitPercent" class="numerical_data"></div>
<div id="reqServed" class="numerical_data"></div>
<div id="bytesSent" class="numerical_data"></div>
</div>
<div id="gDat">
<div id="cpuUtil" class="line_graph_data"></div>
<div id="networkUtil" class="line_graph_data"></div>
<div id="discUtil" class="line_graph_data"></div>
<div id="cacheSize" class="line_graph_data"></div>
<div id="ramUtil" class="line_graph_data"></div>
</div>
</div>
<div id="console" class="content" hidden>
<div class="contentHeader">
<h1 style="margin: 0;position:absolute; top: 42px; padding-left: 30px">Console</h1>
</div>
<div id="buttonBoard">
<!-- client control stuffs-->
<h2 style="margin-left: 40px">Client Status: Stopped</h2>
</div>
<div id="liveConsole">
<div id="console_text"></div>
<div id="console_input" contenteditable="true"></div>
</div>
</div>
<div id="dashOptions" class="content" hidden>
<div class="contentHeader">
<h1 style="margin: 0;position:absolute; top: 42px; padding-left: 30px">Options</h1>
</div>
<div id="options">
<h3>General</h3>
<div class="option">
<h4>Data Refresh Rate</h4>
<label><input id="dataRefreshRate" type="number" class="optionInput input" placeholder="5000" min="500"></label>
</div>
<div class="option">
<h4>Websocket Port</h4>
<label><input id="port" type="number" class="optionInput input" placeholder="33333"></label>
</div>
<div class="option">
<h4>Client IP</h4>
<label><input id="ip" type="text" class="optionInput input" placeholder="localhost"></label>
</div>
<div class="option">
<h4>Max Console Lines</h4>
<label><input id="maxConsoleLines" type="number" class="optionInput input" placeholder="1000" min="100"></label>
</div>
<div class="option">
<h4>Graph Time Frame</h4>
<label><input id="graphTimeFrame" type="number" class="optionInput input" placeholder="30000"
min="5000"></label>
</div>
<h3>Display</h3>
<div class="option">
<h4>Theme</h4>
<label><select id="themeIn" class="optionInput input">
<option value="lightTheme">Light</option>
<option value="darkTheme">Dark</option>
<option value="midnightTheme">Midnight</option>
<option value="eyekillerTheme">High Vibrancy</option>
</select></label>
</div>
<div class="option">
<h4>Style</h4>
<label><select id="styleIn" class="optionInput input">
<option value="sharpStyle">Sharp</option>
<option value="softStyle">Soft</option>
</select></label>
</div>
<div class="option">
<h4>Show Console Latest</h4>
<label class="switch switchInput">
<input id="newestconsole" type="checkbox">
<span id="newestconsolecb" class="slider"></span>
</label>
</div>
<div class="option">
<h4>Animations</h4>
<label class="switch switchInput">
<input id="doAnimations" type="checkbox">
<span id="doAnimationscb" class="slider"></span>
</label>
</div>
</div>
<button id="apply" class="button" onclick="applyOptions()">Apply</button>
<button id="reset" class="button" onclick="resetOptions()">Reset</button>
</div>
<div id="info" class="content" hidden>
<div class="contentHeader">
<h1 style="margin: 0;position:absolute; top: 42px; padding-left: 30px">Client Info</h1>
</div>
<div id="information">
</div>
</div>
</div>
</body>
</html>

View file

@ -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;
}
}

View file

@ -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 {
}

View file

@ -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 {
}

View file

@ -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 {
}

View file

@ -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 {
}

View file

@ -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);
}

View file

@ -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 {
}