Merge branch 'webui'
# Conflicts: # src/main/kotlin/mdnet/base/web/Application.kt
This commit is contained in:
commit
a86b313ad5
|
@ -22,8 +22,12 @@ dependencies {
|
||||||
implementation group: "org.http4k", name: "http4k-core", version: "$http_4k_version"
|
implementation group: "org.http4k", name: "http4k-core", version: "$http_4k_version"
|
||||||
implementation group: "org.http4k", name: "http4k-server-netty", version: "$http_4k_version"
|
implementation group: "org.http4k", name: "http4k-server-netty", version: "$http_4k_version"
|
||||||
implementation group: "org.http4k", name: "http4k-client-apache", version: "$http_4k_version"
|
implementation group: "org.http4k", name: "http4k-client-apache", version: "$http_4k_version"
|
||||||
|
implementation group: "org.http4k", name: "http4k-format-gson", version: "3.249.0"
|
||||||
|
|
||||||
implementation group: "commons-io", name: "commons-io", version: "2.7"
|
implementation group: "commons-io", name: "commons-io", version: "2.7"
|
||||||
|
|
||||||
|
implementation group: 'org.java-websocket', name: 'Java-WebSocket', version: '1.5.1'
|
||||||
|
|
||||||
implementation "ch.qos.logback:logback-classic:$logback_version"
|
implementation "ch.qos.logback:logback-classic:$logback_version"
|
||||||
runtimeOnly 'io.netty:netty-tcnative-boringssl-static:2.0.30.Final'
|
runtimeOnly 'io.netty:netty-tcnative-boringssl-static:2.0.30.Final'
|
||||||
}
|
}
|
||||||
|
@ -35,12 +39,14 @@ java {
|
||||||
|
|
||||||
spotless {
|
spotless {
|
||||||
java {
|
java {
|
||||||
|
indentWithSpaces(4)
|
||||||
eclipse()
|
eclipse()
|
||||||
removeUnusedImports()
|
removeUnusedImports()
|
||||||
trimTrailingWhitespace()
|
trimTrailingWhitespace()
|
||||||
endWithNewline()
|
endWithNewline()
|
||||||
}
|
}
|
||||||
kotlin {
|
kotlin {
|
||||||
|
indentWithSpaces(4)
|
||||||
ktlint()
|
ktlint()
|
||||||
trimTrailingWhitespace()
|
trimTrailingWhitespace()
|
||||||
endWithNewline()
|
endWithNewline()
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
{
|
{
|
||||||
"client_secret": "7rc7p00md0n0xsvqnv4rv17fthvjjrzpdghak1yq45833zvdvnb0",
|
"client_secret": "s76t1dazfctvtgq9dyvgw9herxc4gcz39q0q0y3taxpkgg0ahq8g",
|
||||||
"max_cache_size_mib": 2048,
|
"max_cache_size_mib": 2048,
|
||||||
"client_port": 8080,
|
"client_port": 443,
|
||||||
"max_burst_rate_kib_per_second": 100,
|
"max_burst_rate_kib_per_second": 0,
|
||||||
"max_bandwidth_mib_per_hour": 1,
|
"max_bandwidth_mib_per_hour": 0,
|
||||||
"threads_per_cpu": 32
|
"threads_per_cpu": 32,
|
||||||
|
"web_settings":
|
||||||
|
{
|
||||||
|
"ui_port": 8080
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,30 @@
|
||||||
package mdnet.base;
|
package mdnet.base;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import mdnet.base.settings.ClientSettings;
|
||||||
|
import mdnet.base.web.ApplicationKt;
|
||||||
|
import mdnet.base.web.WebUiKt;
|
||||||
import mdnet.cache.DiskLruCache;
|
import mdnet.cache.DiskLruCache;
|
||||||
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.time.Instant;
|
||||||
import java.io.FileReader;
|
import java.util.ArrayList;
|
||||||
import java.io.IOException;
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
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;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class MangaDexClient {
|
public class MangaDexClient {
|
||||||
|
private final static Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||||
private final static Logger LOGGER = LoggerFactory.getLogger(MangaDexClient.class);
|
private final static Logger LOGGER = LoggerFactory.getLogger(MangaDexClient.class);
|
||||||
|
|
||||||
// This lock protects the Http4kServer from concurrent restart attempts
|
// This lock protects the Http4kServer from concurrent restart attempts
|
||||||
|
@ -24,13 +32,25 @@ public class MangaDexClient {
|
||||||
private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
|
private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
|
||||||
private final ServerHandler serverHandler;
|
private final ServerHandler serverHandler;
|
||||||
private final ClientSettings clientSettings;
|
private final ClientSettings clientSettings;
|
||||||
private final AtomicReference<Statistics> statistics;
|
|
||||||
private ServerSettings serverSettings;
|
|
||||||
|
|
||||||
// if this is null, then the server has shutdown
|
private final Map<Instant, Statistics> statsMap = Collections
|
||||||
private Http4kServer engine;
|
.synchronizedMap(new LinkedHashMap<Instant, Statistics>(80) {
|
||||||
|
@Override
|
||||||
|
protected boolean removeEldestEntry(Map.Entry eldest) {
|
||||||
|
return this.size() > 80;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
private final AtomicReference<Statistics> statistics;
|
||||||
|
|
||||||
|
private ServerSettings serverSettings;
|
||||||
|
private Http4kServer engine; // if this is null, then the server has shutdown
|
||||||
|
private Http4kServer webUi;
|
||||||
private DiskLruCache cache;
|
private DiskLruCache cache;
|
||||||
|
|
||||||
|
// these variables are for runLoop();
|
||||||
|
private int counter = 0;
|
||||||
|
private long lastBytesSent = 0;
|
||||||
|
|
||||||
public MangaDexClient(ClientSettings clientSettings) {
|
public MangaDexClient(ClientSettings clientSettings) {
|
||||||
this.clientSettings = clientSettings;
|
this.clientSettings = clientSettings;
|
||||||
this.serverHandler = new ServerHandler(clientSettings);
|
this.serverHandler = new ServerHandler(clientSettings);
|
||||||
|
@ -39,14 +59,22 @@ public class MangaDexClient {
|
||||||
try {
|
try {
|
||||||
cache = DiskLruCache.open(new File("cache"), 3, 3,
|
cache = DiskLruCache.open(new File("cache"), 3, 3,
|
||||||
clientSettings.getMaxCacheSizeMib() * 1024 * 1024 /* MiB to bytes */);
|
clientSettings.getMaxCacheSizeMib() * 1024 * 1024 /* MiB to bytes */);
|
||||||
|
|
||||||
|
DiskLruCache.Snapshot snapshot = cache.get("statistics");
|
||||||
|
if (snapshot != null) {
|
||||||
|
String json = snapshot.getString(0);
|
||||||
|
snapshot.close();
|
||||||
|
statistics.set(GSON.fromJson(json, Statistics.class));
|
||||||
|
} else {
|
||||||
|
statistics.set(new Statistics());
|
||||||
|
}
|
||||||
|
lastBytesSent = statistics.get().getBytesSent();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
MangaDexClient.dieWithError(e);
|
MangaDexClient.dieWithError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function also does most of the program initialization.
|
|
||||||
public void runLoop() {
|
public void runLoop() {
|
||||||
statistics.set(new Statistics());
|
|
||||||
loginAndStartServer();
|
loginAndStartServer();
|
||||||
if (serverSettings.getLatestBuild() > Constants.CLIENT_BUILD) {
|
if (serverSettings.getLatestBuild() > Constants.CLIENT_BUILD) {
|
||||||
if (LOGGER.isWarnEnabled()) {
|
if (LOGGER.isWarnEnabled()) {
|
||||||
|
@ -55,23 +83,21 @@ public class MangaDexClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
statsMap.put(Instant.now(), statistics.get());
|
||||||
|
|
||||||
|
if (clientSettings.getWebSettings() != null) {
|
||||||
|
webUi = WebUiKt.getUiServer(clientSettings.getWebSettings(), statistics, statsMap);
|
||||||
|
webUi.start();
|
||||||
|
}
|
||||||
|
|
||||||
if (LOGGER.isInfoEnabled()) {
|
if (LOGGER.isInfoEnabled()) {
|
||||||
LOGGER.info("MDNet initialization completed successfully. Starting normal operation.");
|
LOGGER.info("MDNet initialization completed successfully. Starting normal operation.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// we don't really care about the Atomic part here
|
|
||||||
AtomicInteger counter = new AtomicInteger();
|
|
||||||
// ping keep-alive every 45 seconds
|
|
||||||
executorService.scheduleAtFixedRate(() -> {
|
executorService.scheduleAtFixedRate(() -> {
|
||||||
int num = counter.get();
|
if (counter == 80) {
|
||||||
if (num == 80) {
|
counter = 0;
|
||||||
counter.set(0);
|
lastBytesSent = statistics.get().getBytesSent();
|
||||||
|
|
||||||
// if server is stopped due to egress limits, restart it
|
|
||||||
if (LOGGER.isInfoEnabled()) {
|
|
||||||
LOGGER.info("Hourly update: refreshing statistics");
|
|
||||||
}
|
|
||||||
statistics.set(new Statistics());
|
|
||||||
|
|
||||||
if (engine == null) {
|
if (engine == null) {
|
||||||
if (LOGGER.isInfoEnabled()) {
|
if (LOGGER.isInfoEnabled()) {
|
||||||
|
@ -81,16 +107,30 @@ public class MangaDexClient {
|
||||||
loginAndStartServer();
|
loginAndStartServer();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
counter.set(num + 1);
|
counter++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
statsMap.put(Instant.now(), statistics.get());
|
||||||
|
|
||||||
|
try {
|
||||||
|
DiskLruCache.Editor editor = cache.edit("statistics");
|
||||||
|
if (editor != null) {
|
||||||
|
String json = GSON.toJson(statistics.get(), Statistics.class);
|
||||||
|
editor.setString(0, json);
|
||||||
|
editor.setString(1, "");
|
||||||
|
editor.setString(2, "");
|
||||||
|
editor.commit();
|
||||||
|
}
|
||||||
|
} catch (IOException ignored) {}
|
||||||
|
|
||||||
// if the server is offline then don't try and refresh certs
|
// if the server is offline then don't try and refresh certs
|
||||||
if (engine == null) {
|
if (engine == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clientSettings.getMaxBandwidthMibPerHour() != 0 && clientSettings.getMaxBandwidthMibPerHour() * 1024
|
long currentBytesSent = statistics.get().getBytesSent() - lastBytesSent;
|
||||||
* 1024 /* MiB to bytes */ < statistics.get().getBytesSent().get()) {
|
if (clientSettings.getMaxBandwidthMibPerHour() != 0
|
||||||
|
&& clientSettings.getMaxBandwidthMibPerHour() * 1024 * 1024 /* MiB to bytes */ < currentBytesSent) {
|
||||||
if (LOGGER.isInfoEnabled()) {
|
if (LOGGER.isInfoEnabled()) {
|
||||||
LOGGER.info("Shutting down server as hourly bandwidth limit reached");
|
LOGGER.info("Shutting down server as hourly bandwidth limit reached");
|
||||||
}
|
}
|
||||||
|
@ -164,6 +204,12 @@ public class MangaDexClient {
|
||||||
|
|
||||||
logoutAndStopServer();
|
logoutAndStopServer();
|
||||||
}
|
}
|
||||||
|
webUi.close();
|
||||||
|
try {
|
||||||
|
cache.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.error("Cache failed to close", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
@ -171,7 +217,6 @@ public class MangaDexClient {
|
||||||
+ ") initializing\n");
|
+ ") initializing\n");
|
||||||
System.out.println("Copyright (c) 2020, MangaDex Network");
|
System.out.println("Copyright (c) 2020, MangaDex Network");
|
||||||
|
|
||||||
try {
|
|
||||||
String file = "settings.json";
|
String file = "settings.json";
|
||||||
if (args.length == 1) {
|
if (args.length == 1) {
|
||||||
file = args[0];
|
file = args[0];
|
||||||
|
@ -179,18 +224,21 @@ public class MangaDexClient {
|
||||||
MangaDexClient.dieWithError("Expected one argument: path to config file, or nothing");
|
MangaDexClient.dieWithError("Expected one argument: path to config file, or nothing");
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientSettings settings = new Gson().fromJson(new FileReader(file), ClientSettings.class);
|
ClientSettings settings;
|
||||||
|
|
||||||
if (!ClientSettings.isSecretValid(settings.getClientSecret()))
|
try {
|
||||||
MangaDexClient.dieWithError("Config Error: API Secret is invalid, must be 52 alphanumeric characters");
|
settings = GSON.fromJson(new FileReader(file), ClientSettings.class);
|
||||||
|
} catch (FileNotFoundException ignored) {
|
||||||
if (settings.getClientPort() == 0) {
|
settings = new ClientSettings();
|
||||||
MangaDexClient.dieWithError("Config Error: Invalid port number");
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.getMaxCacheSizeMib() < 1024) {
|
validateSettings(settings);
|
||||||
MangaDexClient.dieWithError("Config Error: Invalid max cache size, must be >= 1024 MiB (1GiB)");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (LOGGER.isInfoEnabled()) {
|
if (LOGGER.isInfoEnabled()) {
|
||||||
LOGGER.info("Client settings loaded: {}", settings);
|
LOGGER.info("Client settings loaded: {}", settings);
|
||||||
|
@ -199,9 +247,6 @@ public class MangaDexClient {
|
||||||
MangaDexClient client = new MangaDexClient(settings);
|
MangaDexClient client = new MangaDexClient(settings);
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(client::shutdown));
|
Runtime.getRuntime().addShutdownHook(new Thread(client::shutdown));
|
||||||
client.runLoop();
|
client.runLoop();
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
MangaDexClient.dieWithError(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void dieWithError(Throwable e) {
|
public static void dieWithError(Throwable e) {
|
||||||
|
@ -213,8 +258,48 @@ public class MangaDexClient {
|
||||||
|
|
||||||
public static void dieWithError(String error) {
|
public static void dieWithError(String error) {
|
||||||
if (LOGGER.isErrorEnabled()) {
|
if (LOGGER.isErrorEnabled()) {
|
||||||
LOGGER.error("Critical Error: " + error);
|
LOGGER.error("Critical Error: {}", error);
|
||||||
}
|
}
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void validateSettings(ClientSettings settings) {
|
||||||
|
if (!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.getMaxCacheSizeMib() < 1024) {
|
||||||
|
MangaDexClient.dieWithError("Config Error: Invalid max cache size, must be >= 1024 MiB (1GiB)");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.getThreads() < 4) {
|
||||||
|
MangaDexClient.dieWithError("Config Error: Invalid number of threads, must be >= 8");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.getMaxBandwidthMibPerHour() < 0) {
|
||||||
|
MangaDexClient.dieWithError("Config Error: Max bandwidth must be >= 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.getMaxBurstRateKibPerSecond() < 0) {
|
||||||
|
MangaDexClient.dieWithError("Config Error: Max burst rate must be >= 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.getWebSettings() != null) {
|
||||||
|
if (settings.getWebSettings().getUiPort() == 0) {
|
||||||
|
MangaDexClient.dieWithError("Config Error: Invalid UI port number");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.getWebSettings().getUiWebsocketPort() == 0) {
|
||||||
|
MangaDexClient.dieWithError("Config Error: Invalid websocket port number");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isSecretValid(String clientSecret) {
|
||||||
|
final int CLIENT_KEY_LENGTH = 52;
|
||||||
|
return Pattern.matches("^[a-zA-Z0-9]{" + CLIENT_KEY_LENGTH + "}$", clientSecret);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
package mdnet.base;
|
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
|
||||||
|
|
||||||
public class Statistics {
|
|
||||||
private final AtomicInteger requestsServed;
|
|
||||||
private final AtomicInteger cacheHits;
|
|
||||||
private final AtomicInteger cacheMisses;
|
|
||||||
private final AtomicLong bytesSent;
|
|
||||||
|
|
||||||
public Statistics() {
|
|
||||||
requestsServed = new AtomicInteger();
|
|
||||||
cacheHits = new AtomicInteger();
|
|
||||||
cacheMisses = new AtomicInteger();
|
|
||||||
bytesSent = new AtomicLong();
|
|
||||||
}
|
|
||||||
|
|
||||||
public AtomicInteger getRequestsServed() {
|
|
||||||
return requestsServed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AtomicInteger getCacheHits() {
|
|
||||||
return cacheHits;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AtomicInteger getCacheMisses() {
|
|
||||||
return cacheMisses;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AtomicLong getBytesSent() {
|
|
||||||
return bytesSent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "Statistics{" + "requestsServed=" + requestsServed + ", cacheHits=" + cacheHits + ", cacheMisses="
|
|
||||||
+ cacheMisses + ", bytesSent=" + bytesSent + '}';
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +1,8 @@
|
||||||
package mdnet.base;
|
package mdnet.base.settings;
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
public final class ClientSettings {
|
public final class ClientSettings {
|
||||||
@SerializedName("max_cache_size_mib")
|
@SerializedName("max_cache_size_mib")
|
||||||
|
@ -18,15 +17,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 +60,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
|
||||||
|
@ -59,9 +74,4 @@ public final class ClientSettings {
|
||||||
+ maxBandwidthMibPerHour + ", maxBurstRateKibPerSecond=" + maxBurstRateKibPerSecond + ", clientPort="
|
+ maxBandwidthMibPerHour + ", maxBurstRateKibPerSecond=" + maxBurstRateKibPerSecond + ", clientPort="
|
||||||
+ clientPort + ", clientSecret='" + "<hidden>" + '\'' + ", threads=" + getThreads() + '}';
|
+ clientPort + ", clientSecret='" + "<hidden>" + '\'' + ", threads=" + getThreads() + '}';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isSecretValid(String clientSecret) {
|
|
||||||
final int CLIENT_KEY_LENGTH = 52;
|
|
||||||
return Pattern.matches("^[a-zA-Z0-9]{" + CLIENT_KEY_LENGTH + "}$", clientSecret);
|
|
||||||
}
|
|
||||||
}
|
}
|
33
src/main/java/mdnet/base/settings/WebSettings.java
Normal file
33
src/main/java/mdnet/base/settings/WebSettings.java
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package mdnet.base.settings;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
public final class WebSettings {
|
||||||
|
@SerializedName("ui_websocket_port")
|
||||||
|
private final int uiWebsocketPort;
|
||||||
|
@SerializedName("ui_port")
|
||||||
|
private final int uiPort;
|
||||||
|
|
||||||
|
public WebSettings() {
|
||||||
|
this.uiWebsocketPort = 33333;
|
||||||
|
this.uiPort = 8080;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebSettings(int uiWebsocketPort, int uiPort) {
|
||||||
|
this.uiWebsocketPort = uiWebsocketPort;
|
||||||
|
this.uiPort = uiPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getUiWebsocketPort() {
|
||||||
|
return uiWebsocketPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getUiPort() {
|
||||||
|
return uiPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "WebSettings{" + "uiWebsocketPort=" + uiWebsocketPort + ", uiPort=" + uiPort + '}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,7 +14,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package mdnet.base;
|
package mdnet.cache;
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.commons.io.input.ProxyInputStream;
|
import org.apache.commons.io.input.ProxyInputStream;
|
40
src/main/java/mdnet/cache/DiskLruCache.java
vendored
40
src/main/java/mdnet/cache/DiskLruCache.java
vendored
|
@ -82,10 +82,9 @@ import java.util.regex.Pattern;
|
||||||
* <li>When an entry is being <strong>edited</strong>, it is not necessary to
|
* <li>When an entry is being <strong>edited</strong>, it is not necessary to
|
||||||
* supply data for every value; values default to their previous value.
|
* supply data for every value; values default to their previous value.
|
||||||
* </ul>
|
* </ul>
|
||||||
* Every {@link #editImpl} call must be matched by a call to
|
* Every {@link #edit} call must be matched by a call to {@link Editor#commit}
|
||||||
* {@link Editor#commit} or {@link Editor#abort}. Committing is atomic: a read
|
* or {@link Editor#abort}. Committing is atomic: a read observes the full set
|
||||||
* observes the full set of values as they were before or after the commit, but
|
* of values as they were before or after the commit, but never a mix of values.
|
||||||
* never a mix of values.
|
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Clients call {@link #get} to read a snapshot of an entry. The read will
|
* Clients call {@link #get} to read a snapshot of an entry. The read will
|
||||||
|
@ -412,7 +411,7 @@ public final class DiskLruCache implements Closeable {
|
||||||
return getImpl(key);
|
return getImpl(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized Snapshot getImpl(String key) throws IOException {
|
private synchronized Snapshot getImpl(String key) throws IOException {
|
||||||
checkNotClosed();
|
checkNotClosed();
|
||||||
Entry entry = lruEntries.get(key);
|
Entry entry = lruEntries.get(key);
|
||||||
if (entry == null) {
|
if (entry == null) {
|
||||||
|
@ -967,20 +966,7 @@ public final class DiskLruCache implements Closeable {
|
||||||
Path oldCache = Paths.get(directory + File.separator + key + "." + i);
|
Path oldCache = Paths.get(directory + File.separator + key + "." + i);
|
||||||
Path newCache = Paths.get(directory + subKeyPath + File.separator + key + "." + i);
|
Path newCache = Paths.get(directory + subKeyPath + File.separator + key + "." + i);
|
||||||
|
|
||||||
File newCacheDirectory = new File(directory + subKeyPath, key + "." + i + ".tmp");
|
migrateCacheFile(i, oldCache, newCache);
|
||||||
newCacheDirectory.getParentFile().mkdirs();
|
|
||||||
|
|
||||||
if (Files.exists(oldCache)) {
|
|
||||||
try {
|
|
||||||
Files.move(oldCache, newCache, StandardCopyOption.ATOMIC_MOVE);
|
|
||||||
} catch (FileAlreadyExistsException faee) {
|
|
||||||
try {
|
|
||||||
Files.delete(oldCache);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
}
|
|
||||||
} catch (IOException ex) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new File(directory + subKeyPath, key + "." + i);
|
return new File(directory + subKeyPath, key + "." + i);
|
||||||
}
|
}
|
||||||
|
@ -990,6 +976,12 @@ public final class DiskLruCache implements Closeable {
|
||||||
Path oldCache = Paths.get(directory + File.separator + key + "." + i + ".tmp");
|
Path oldCache = Paths.get(directory + File.separator + key + "." + i + ".tmp");
|
||||||
Path newCache = Paths.get(directory + subKeyPath + File.separator + key + "." + i + ".tmp");
|
Path newCache = Paths.get(directory + subKeyPath + File.separator + key + "." + i + ".tmp");
|
||||||
|
|
||||||
|
migrateCacheFile(i, oldCache, newCache);
|
||||||
|
|
||||||
|
return new File(directory + subKeyPath, key + "." + i + ".tmp");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void migrateCacheFile(int i, Path oldCache, Path newCache) {
|
||||||
File newCacheDirectory = new File(directory + subKeyPath, key + "." + i + ".tmp");
|
File newCacheDirectory = new File(directory + subKeyPath, key + "." + i + ".tmp");
|
||||||
newCacheDirectory.getParentFile().mkdirs();
|
newCacheDirectory.getParentFile().mkdirs();
|
||||||
|
|
||||||
|
@ -999,13 +991,11 @@ public final class DiskLruCache implements Closeable {
|
||||||
} catch (FileAlreadyExistsException faee) {
|
} catch (FileAlreadyExistsException faee) {
|
||||||
try {
|
try {
|
||||||
Files.delete(oldCache);
|
Files.delete(oldCache);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException ex) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new File(directory + subKeyPath, key + "." + i + ".tmp");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
107
src/main/java/mdnet/webui/WebConsole.java
Normal file
107
src/main/java/mdnet/webui/WebConsole.java
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ import io.netty.handler.ssl.SslContextBuilder
|
||||||
import io.netty.handler.stream.ChunkedWriteHandler
|
import io.netty.handler.stream.ChunkedWriteHandler
|
||||||
import io.netty.handler.traffic.GlobalTrafficShapingHandler
|
import io.netty.handler.traffic.GlobalTrafficShapingHandler
|
||||||
import io.netty.handler.traffic.TrafficCounter
|
import io.netty.handler.traffic.TrafficCounter
|
||||||
|
import mdnet.base.settings.ClientSettings
|
||||||
import org.http4k.core.HttpHandler
|
import org.http4k.core.HttpHandler
|
||||||
import org.http4k.server.Http4kChannelHandler
|
import org.http4k.server.Http4kChannelHandler
|
||||||
import org.http4k.server.Http4kServer
|
import org.http4k.server.Http4kServer
|
||||||
|
@ -36,26 +37,26 @@ import javax.net.ssl.SSLException
|
||||||
|
|
||||||
private val LOGGER = LoggerFactory.getLogger("Application")
|
private val LOGGER = LoggerFactory.getLogger("Application")
|
||||||
|
|
||||||
class Netty(private val tls: ServerSettings.TlsCert, private val clientSettings: ClientSettings, private val stats: AtomicReference<Statistics>) : ServerConfig {
|
class Netty(private val tls: ServerSettings.TlsCert, private val clientSettings: ClientSettings, private val statistics: AtomicReference<Statistics>) : ServerConfig {
|
||||||
private val threadsToAllocate = clientSettings.getThreads()
|
|
||||||
|
|
||||||
override fun toServer(httpHandler: HttpHandler): Http4kServer = object : Http4kServer {
|
override fun toServer(httpHandler: HttpHandler): Http4kServer = object : Http4kServer {
|
||||||
private val masterGroup = NioEventLoopGroup(threadsToAllocate)
|
private val masterGroup = NioEventLoopGroup(clientSettings.threads)
|
||||||
private val workerGroup = NioEventLoopGroup(threadsToAllocate)
|
private val workerGroup = NioEventLoopGroup(clientSettings.threads)
|
||||||
private lateinit var closeFuture: ChannelFuture
|
private lateinit var closeFuture: ChannelFuture
|
||||||
private lateinit var address: InetSocketAddress
|
private lateinit var address: InetSocketAddress
|
||||||
|
|
||||||
private val burstLimiter = object : GlobalTrafficShapingHandler(
|
private val burstLimiter = object : GlobalTrafficShapingHandler(
|
||||||
workerGroup, 1024 * clientSettings.maxBurstRateKibPerSecond, 0, 50) {
|
workerGroup, 1024 * clientSettings.maxBurstRateKibPerSecond, 0, 50) {
|
||||||
override fun doAccounting(counter: TrafficCounter) {
|
override fun doAccounting(counter: TrafficCounter) {
|
||||||
stats.get().bytesSent.getAndAdd(counter.cumulativeWrittenBytes())
|
statistics.getAndUpdate {
|
||||||
|
it.copy(bytesSent = it.bytesSent + counter.cumulativeWrittenBytes())
|
||||||
|
}
|
||||||
counter.resetCumulativeTime()
|
counter.resetCumulativeTime()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun start(): Http4kServer = apply {
|
override fun start(): Http4kServer = apply {
|
||||||
if (LOGGER.isInfoEnabled) {
|
if (LOGGER.isInfoEnabled) {
|
||||||
LOGGER.info("Starting webserver with {} threads", threadsToAllocate)
|
LOGGER.info("Starting webserver with {} threads", clientSettings.threads)
|
||||||
}
|
}
|
||||||
|
|
||||||
val (mainCert, chainCert) = getX509Certs(tls.certificate)
|
val (mainCert, chainCert) = getX509Certs(tls.certificate)
|
||||||
|
|
12
src/main/kotlin/mdnet/base/Statistics.kt
Normal file
12
src/main/kotlin/mdnet/base/Statistics.kt
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package mdnet.base
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class Statistics(
|
||||||
|
@field:SerializedName("requests_served") val requestsServed: Int = 0,
|
||||||
|
@field:SerializedName("cache_hits") val cacheHits: Int = 0,
|
||||||
|
@field:SerializedName("cache_misses") val cacheMisses: Int = 0,
|
||||||
|
@field:SerializedName("browser_cached") val browserCached: Int = 0,
|
||||||
|
@field:SerializedName("bytes_sent") val bytesSent: Long = 0,
|
||||||
|
@field:SerializedName("bytes_on_disk") val bytesOnDisk: Long = 0
|
||||||
|
)
|
|
@ -1,14 +1,18 @@
|
||||||
/* ktlint-disable no-wildcard-imports */
|
/* ktlint-disable no-wildcard-imports */
|
||||||
package mdnet.base
|
package mdnet.base.web
|
||||||
|
|
||||||
|
import mdnet.base.Constants
|
||||||
|
import mdnet.base.Netty
|
||||||
|
import mdnet.base.ServerSettings
|
||||||
|
import mdnet.base.Statistics
|
||||||
|
import mdnet.base.settings.ClientSettings
|
||||||
|
import mdnet.cache.CachingInputStream
|
||||||
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
|
||||||
import org.apache.http.impl.client.HttpClients
|
import org.apache.http.impl.client.HttpClients
|
||||||
import org.http4k.client.ApacheClient
|
import org.http4k.client.ApacheClient
|
||||||
import org.http4k.core.BodyMode
|
import org.http4k.core.BodyMode
|
||||||
import org.http4k.core.Filter
|
|
||||||
import org.http4k.core.HttpHandler
|
|
||||||
import org.http4k.core.Method
|
import org.http4k.core.Method
|
||||||
import org.http4k.core.Request
|
import org.http4k.core.Request
|
||||||
import org.http4k.core.Response
|
import org.http4k.core.Response
|
||||||
|
@ -27,10 +31,6 @@ import java.io.BufferedInputStream
|
||||||
import java.io.BufferedOutputStream
|
import java.io.BufferedOutputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.time.ZoneOffset
|
|
||||||
import java.time.ZonedDateTime
|
|
||||||
import java.time.format.DateTimeFormatter
|
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
import javax.crypto.Cipher
|
import javax.crypto.Cipher
|
||||||
|
@ -39,7 +39,7 @@ import javax.crypto.CipherOutputStream
|
||||||
import javax.crypto.spec.SecretKeySpec
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
private val LOGGER = LoggerFactory.getLogger("Application")
|
private val LOGGER = LoggerFactory.getLogger("Application")
|
||||||
private val THREADS_TO_ALLOCATE = 262144 // 2**18 // Honestly, no reason to not just let 'er rip. Inactive connections will expire on their own :D
|
private const val THREADS_TO_ALLOCATE = 262144 // 2**18 // Honestly, no reason to not just let 'er rip. Inactive connections will expire on their own :D
|
||||||
|
|
||||||
fun getServer(cache: DiskLruCache, serverSettings: ServerSettings, clientSettings: ClientSettings, statistics: AtomicReference<Statistics>): Http4kServer {
|
fun getServer(cache: DiskLruCache, serverSettings: ServerSettings, clientSettings: ClientSettings, statistics: AtomicReference<Statistics>): Http4kServer {
|
||||||
val executor = Executors.newCachedThreadPool()
|
val executor = Executors.newCachedThreadPool()
|
||||||
|
@ -57,7 +57,6 @@ fun getServer(cache: DiskLruCache, serverSettings: ServerSettings, clientSetting
|
||||||
.build())
|
.build())
|
||||||
.setMaxConnTotal(THREADS_TO_ALLOCATE)
|
.setMaxConnTotal(THREADS_TO_ALLOCATE)
|
||||||
.setMaxConnPerRoute(THREADS_TO_ALLOCATE)
|
.setMaxConnPerRoute(THREADS_TO_ALLOCATE)
|
||||||
// Have it at the maximum open sockets a user can have in most modern OSes. No reason to limit this, just limit it at the Netty side.
|
|
||||||
.build())
|
.build())
|
||||||
|
|
||||||
val app = { dataSaver: Boolean ->
|
val app = { dataSaver: Boolean ->
|
||||||
|
@ -81,11 +80,12 @@ fun getServer(cache: DiskLruCache, serverSettings: ServerSettings, clientSetting
|
||||||
md5Bytes("$chapterHash.$fileName")
|
md5Bytes("$chapterHash.$fileName")
|
||||||
}
|
}
|
||||||
val cacheId = printHexString(rc4Bytes)
|
val cacheId = printHexString(rc4Bytes)
|
||||||
|
statistics.getAndUpdate {
|
||||||
statistics.get().requestsServed.incrementAndGet()
|
it.copy(requestsServed = it.requestsServed + 1)
|
||||||
|
}
|
||||||
|
|
||||||
// Netty doesn't do Content-Length or Content-Type, so we have the pleasure of doing that ourselves
|
// Netty doesn't do Content-Length or Content-Type, so we have the pleasure of doing that ourselves
|
||||||
fun respondWithImage(input: InputStream, length: String?, type: String, lastModified: String?, cached: Boolean): Response =
|
fun respondWithImage(input: InputStream, length: String?, type: String, lastModified: String?): Response =
|
||||||
Response(Status.OK)
|
Response(Status.OK)
|
||||||
.header("Content-Type", type)
|
.header("Content-Type", type)
|
||||||
.header("X-Content-Type-Options", "nosniff")
|
.header("X-Content-Type-Options", "nosniff")
|
||||||
|
@ -108,20 +108,15 @@ fun getServer(cache: DiskLruCache, serverSettings: ServerSettings, clientSetting
|
||||||
it
|
it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.let {
|
|
||||||
if (cached != null && cached == true) {
|
|
||||||
it.header("X-Cache", "HIT")
|
|
||||||
} else {
|
|
||||||
it.header("X-Cache", "MISS")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val snapshot = cache.get(cacheId)
|
val snapshot = cache.get(cacheId)
|
||||||
if (snapshot != null) {
|
if (snapshot != null) {
|
||||||
statistics.get().cacheHits.incrementAndGet()
|
|
||||||
|
|
||||||
// our files never change, so it's safe to use the browser cache
|
// our files never change, so it's safe to use the browser cache
|
||||||
if (request.header("If-Modified-Since") != null) {
|
if (request.header("If-Modified-Since") != null) {
|
||||||
|
statistics.getAndUpdate {
|
||||||
|
it.copy(browserCached = it.browserCached + 1)
|
||||||
|
}
|
||||||
|
|
||||||
if (LOGGER.isInfoEnabled) {
|
if (LOGGER.isInfoEnabled) {
|
||||||
LOGGER.info("Request for $sanitizedUri cached by browser")
|
LOGGER.info("Request for $sanitizedUri cached by browser")
|
||||||
}
|
}
|
||||||
|
@ -132,18 +127,24 @@ fun getServer(cache: DiskLruCache, serverSettings: ServerSettings, clientSetting
|
||||||
Response(Status.NOT_MODIFIED)
|
Response(Status.NOT_MODIFIED)
|
||||||
.header("Last-Modified", lastModified)
|
.header("Last-Modified", lastModified)
|
||||||
} else {
|
} else {
|
||||||
|
statistics.getAndUpdate {
|
||||||
|
it.copy(cacheHits = it.cacheHits + 1)
|
||||||
|
}
|
||||||
|
|
||||||
if (LOGGER.isInfoEnabled) {
|
if (LOGGER.isInfoEnabled) {
|
||||||
LOGGER.info("Request for $sanitizedUri hit cache")
|
LOGGER.info("Request for $sanitizedUri hit cache")
|
||||||
}
|
}
|
||||||
|
|
||||||
respondWithImage(
|
respondWithImage(
|
||||||
CipherInputStream(BufferedInputStream(snapshot.getInputStream(0)), getRc4(rc4Bytes)),
|
CipherInputStream(BufferedInputStream(snapshot.getInputStream(0)), getRc4(rc4Bytes)),
|
||||||
snapshot.getLength(0).toString(), snapshot.getString(1), snapshot.getString(2),
|
snapshot.getLength(0).toString(), snapshot.getString(1), snapshot.getString(2)
|
||||||
true
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
statistics.get().cacheMisses.incrementAndGet()
|
statistics.getAndUpdate {
|
||||||
|
it.copy(cacheMisses = it.cacheMisses + 1)
|
||||||
|
}
|
||||||
|
|
||||||
if (LOGGER.isInfoEnabled) {
|
if (LOGGER.isInfoEnabled) {
|
||||||
LOGGER.info("Request for $sanitizedUri missed cache")
|
LOGGER.info("Request for $sanitizedUri missed cache")
|
||||||
}
|
}
|
||||||
|
@ -195,14 +196,14 @@ fun getServer(cache: DiskLruCache, serverSettings: ServerSettings, clientSetting
|
||||||
editor.abort()
|
editor.abort()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
respondWithImage(tee, contentLength, contentType, lastModified, false)
|
respondWithImage(tee, contentLength, contentType, lastModified)
|
||||||
} else {
|
} else {
|
||||||
editor?.abort()
|
editor?.abort()
|
||||||
|
|
||||||
if (LOGGER.isTraceEnabled) {
|
if (LOGGER.isTraceEnabled) {
|
||||||
LOGGER.trace("Request for $sanitizedUri is being served")
|
LOGGER.trace("Request for $sanitizedUri is being served")
|
||||||
}
|
}
|
||||||
respondWithImage(mdResponse.body.stream, contentLength, contentType, lastModified, false)
|
respondWithImage(mdResponse.body.stream, contentLength, contentType, lastModified)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -231,33 +232,6 @@ private fun getRc4(key: ByteArray): Cipher {
|
||||||
return rc4
|
return rc4
|
||||||
}
|
}
|
||||||
|
|
||||||
private val HTTP_TIME_FORMATTER = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss O", Locale.ENGLISH)
|
|
||||||
|
|
||||||
private fun addCommonHeaders(): Filter {
|
|
||||||
return Filter { next: HttpHandler ->
|
|
||||||
{ request: Request ->
|
|
||||||
val response = next(request)
|
|
||||||
response.header("Date", HTTP_TIME_FORMATTER.format(ZonedDateTime.now(ZoneOffset.UTC)))
|
|
||||||
.header("Server", "Mangadex@Home Node ${Constants.CLIENT_VERSION} (${Constants.CLIENT_BUILD})")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun catchAllHideDetails(): Filter {
|
|
||||||
return Filter { next: HttpHandler ->
|
|
||||||
{ request: Request ->
|
|
||||||
try {
|
|
||||||
next(request)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
if (LOGGER.isWarnEnabled) {
|
|
||||||
LOGGER.warn("Request error detected", e)
|
|
||||||
}
|
|
||||||
Response(Status.INTERNAL_SERVER_ERROR)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun md5Bytes(stringToHash: String): ByteArray {
|
private fun md5Bytes(stringToHash: String): ByteArray {
|
||||||
val digest = MessageDigest.getInstance("MD5")
|
val digest = MessageDigest.getInstance("MD5")
|
||||||
return digest.digest(stringToHash.toByteArray())
|
return digest.digest(stringToHash.toByteArray())
|
47
src/main/kotlin/mdnet/base/web/WebUi.kt
Normal file
47
src/main/kotlin/mdnet/base/web/WebUi.kt
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
/* ktlint-disable no-wildcard-imports */
|
||||||
|
package mdnet.base.web
|
||||||
|
|
||||||
|
import mdnet.base.Statistics
|
||||||
|
import mdnet.base.settings.WebSettings
|
||||||
|
import org.http4k.core.Body
|
||||||
|
import org.http4k.core.Method
|
||||||
|
import org.http4k.core.Response
|
||||||
|
import org.http4k.core.Status
|
||||||
|
import org.http4k.core.then
|
||||||
|
import org.http4k.filter.ServerFilters
|
||||||
|
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.Netty
|
||||||
|
import org.http4k.server.asServer
|
||||||
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
import org.http4k.format.Gson.auto
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
fun getUiServer(
|
||||||
|
webSettings: WebSettings,
|
||||||
|
statistics: AtomicReference<Statistics>,
|
||||||
|
statsMap: Map<Instant, Statistics>
|
||||||
|
): Http4kServer {
|
||||||
|
val statsMapLens = Body.auto<Map<Instant, Statistics>>().toLens()
|
||||||
|
|
||||||
|
return catchAllHideDetails()
|
||||||
|
.then(ServerFilters.CatchLensFailure)
|
||||||
|
.then(addCommonHeaders())
|
||||||
|
.then(
|
||||||
|
routes(
|
||||||
|
"/api/stats" bind Method.GET to {
|
||||||
|
statsMapLens(mapOf(Instant.now() to statistics.get()), Response(Status.OK))
|
||||||
|
},
|
||||||
|
"/api/pastStats" bind Method.GET to {
|
||||||
|
synchronized(statsMap) {
|
||||||
|
statsMapLens(statsMap, Response(Status.OK))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
singlePageApp(ResourceLoader.Classpath("/webui"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.asServer(Netty(webSettings.uiPort))
|
||||||
|
}
|
43
src/main/kotlin/mdnet/base/web/common.kt
Normal file
43
src/main/kotlin/mdnet/base/web/common.kt
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
/* ktlint-disable no-wildcard-imports */
|
||||||
|
package mdnet.base.web
|
||||||
|
|
||||||
|
import mdnet.base.Constants
|
||||||
|
import org.http4k.core.Filter
|
||||||
|
import org.http4k.core.HttpHandler
|
||||||
|
import org.http4k.core.Request
|
||||||
|
import org.http4k.core.Response
|
||||||
|
import org.http4k.core.Status
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.time.ZoneOffset
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
private val HTTP_TIME_FORMATTER = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss O", Locale.ENGLISH)
|
||||||
|
|
||||||
|
private val LOGGER = LoggerFactory.getLogger("Application")
|
||||||
|
|
||||||
|
fun addCommonHeaders(): Filter {
|
||||||
|
return Filter { next: HttpHandler ->
|
||||||
|
{ request: Request ->
|
||||||
|
val response = next(request)
|
||||||
|
response.header("Date", HTTP_TIME_FORMATTER.format(ZonedDateTime.now(ZoneOffset.UTC)))
|
||||||
|
.header("Server", "Mangadex@Home Node ${Constants.CLIENT_VERSION} (${Constants.CLIENT_BUILD})")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun catchAllHideDetails(): Filter {
|
||||||
|
return Filter { next: HttpHandler ->
|
||||||
|
{ request: Request ->
|
||||||
|
try {
|
||||||
|
next(request)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (LOGGER.isWarnEnabled) {
|
||||||
|
LOGGER.warn("Request error detected", e)
|
||||||
|
}
|
||||||
|
Response(Status.INTERNAL_SERVER_ERROR)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
<configuration>
|
<configuration>
|
||||||
|
<shutdownHook/>
|
||||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||||
<level>${file-level:-TRACE}</level>
|
<level>${file-level:-TRACE}</level>
|
||||||
|
|
705
src/main/resources/webui/dataReceive.js
Normal file
705
src/main/resources/webui/dataReceive.js
Normal file
|
@ -0,0 +1,705 @@
|
||||||
|
let connection;
|
||||||
|
let theme;
|
||||||
|
let style;
|
||||||
|
let port;
|
||||||
|
let ip;
|
||||||
|
let refreshRate;
|
||||||
|
let maxConsoleLines;
|
||||||
|
let graphTimeFrame;
|
||||||
|
let showConsoleLatest;
|
||||||
|
let doAnimations;
|
||||||
|
let lockDash;
|
||||||
|
//non-option var
|
||||||
|
let statRequest;
|
||||||
|
//stat vars
|
||||||
|
let hitmiss,
|
||||||
|
byte,
|
||||||
|
cached,
|
||||||
|
req;
|
||||||
|
|
||||||
|
//dockable things
|
||||||
|
let config = {
|
||||||
|
settings: {
|
||||||
|
hasHeaders: true,
|
||||||
|
constrainDragToContainer: false,
|
||||||
|
reorderEnabled: true,
|
||||||
|
selectionEnabled: false,
|
||||||
|
popoutWholeStack: false,
|
||||||
|
blockedPopoutsThrowError: true,
|
||||||
|
closePopoutsOnUnload: true,
|
||||||
|
showPopoutIcon: false,
|
||||||
|
showMaximiseIcon: false,
|
||||||
|
showCloseIcon: lockDash
|
||||||
|
},
|
||||||
|
dimensions: {
|
||||||
|
borderWidth: 20,
|
||||||
|
minItemHeight: 10,
|
||||||
|
minItemWidth: 10,
|
||||||
|
headerHeight: 20,
|
||||||
|
dragProxyWidth: 300,
|
||||||
|
dragProxyHeight: 200
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
close: 'close',
|
||||||
|
maximise: 'maximise',
|
||||||
|
minimise: 'minimise',
|
||||||
|
popout: 'open in new window'
|
||||||
|
},
|
||||||
|
content: [{
|
||||||
|
type: 'column',
|
||||||
|
content: [{
|
||||||
|
type: 'row',
|
||||||
|
content: [{
|
||||||
|
type: 'column',
|
||||||
|
content: [{
|
||||||
|
type: 'row',
|
||||||
|
content: [{
|
||||||
|
type: 'component',
|
||||||
|
componentName: 'Hit Percent',
|
||||||
|
width: 50,
|
||||||
|
componentState: {label: 'F'}
|
||||||
|
}, {
|
||||||
|
type: 'component',
|
||||||
|
componentName: 'Hits',
|
||||||
|
componentState: {label: 'B'}
|
||||||
|
}, {
|
||||||
|
type: 'component',
|
||||||
|
componentName: 'Misses',
|
||||||
|
componentState: {label: 'C'}
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
type: 'row',
|
||||||
|
content: [{
|
||||||
|
type: 'component',
|
||||||
|
componentName: 'Requests Served',
|
||||||
|
componentState: {label: 'B'}
|
||||||
|
}, {
|
||||||
|
type: 'component',
|
||||||
|
componentName: 'Bytes Sent',
|
||||||
|
componentState: {label: 'C'}
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
type: 'column',
|
||||||
|
content: [{
|
||||||
|
type: 'component',
|
||||||
|
componentName: 'Network Utilization',
|
||||||
|
componentState: {label: 'B'}
|
||||||
|
}, {
|
||||||
|
type: 'component',
|
||||||
|
componentName: 'CPU Utilization',
|
||||||
|
componentState: {label: 'C'}
|
||||||
|
}, {
|
||||||
|
type: 'component',
|
||||||
|
componentName: 'Disk Utilization',
|
||||||
|
componentState: {label: 'D'}
|
||||||
|
}, {
|
||||||
|
type: 'component',
|
||||||
|
componentName: 'RAM Utilization',
|
||||||
|
componentState: {label: 'E'}
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
type: 'row',
|
||||||
|
height: 20,
|
||||||
|
content: [{
|
||||||
|
type: 'component',
|
||||||
|
componentName: 'Cache Size',
|
||||||
|
componentState: {label: 'F'}
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
let dashlayout;
|
||||||
|
|
||||||
|
function loadDash() {
|
||||||
|
let savedState = localStorage.getItem("dashState");
|
||||||
|
if (savedState !== null) {
|
||||||
|
dashlayout = new GoldenLayout(JSON.parse(savedState), $("#dashboard"));
|
||||||
|
} else {
|
||||||
|
dashlayout = new GoldenLayout(config, $("#dashboard"));
|
||||||
|
}
|
||||||
|
//graphs
|
||||||
|
dashlayout.registerComponent('Network Utilization', function (container, state) {
|
||||||
|
container.getElement().append('<div id="networkUtil" class="line_graph_data"></div>');
|
||||||
|
});
|
||||||
|
dashlayout.registerComponent('CPU Utilization', function (container, state) {
|
||||||
|
container.getElement().append('<div id="cpuUtil" class="line_graph_data"></div>');
|
||||||
|
});
|
||||||
|
dashlayout.registerComponent('Disk Utilization', function (container, state) {
|
||||||
|
container.getElement().append('<div id="discUtil" class="line_graph_data"></div>');
|
||||||
|
});
|
||||||
|
dashlayout.registerComponent('Cache Size', function (container, state) {
|
||||||
|
|
||||||
|
container.getElement().append('<div id="cacheSize" class="line_graph_data"></div>');
|
||||||
|
});
|
||||||
|
dashlayout.registerComponent('RAM Utilization', function (container, state) {
|
||||||
|
container.getElement().append(' <div id="ramUtil" class="line_graph_data"></div>');
|
||||||
|
});
|
||||||
|
// numbers
|
||||||
|
dashlayout.registerComponent('Hits', function (container, state) {
|
||||||
|
container.getElement().append('<div id="hits" class="numerical_data"></div>');
|
||||||
|
});
|
||||||
|
dashlayout.registerComponent('Misses', function (container, state) {
|
||||||
|
container.getElement().append('<div id="misses" class="numerical_data"></div>');
|
||||||
|
});
|
||||||
|
dashlayout.registerComponent('Requests Served', function (container, state) {
|
||||||
|
container.getElement().append('<div id="reqServed" class="numerical_data"></div>');
|
||||||
|
});
|
||||||
|
dashlayout.registerComponent('Bytes Sent', function (container, state) {
|
||||||
|
container.getElement().append('<div id="bytesSent" class="numerical_data"></div>');
|
||||||
|
});
|
||||||
|
dashlayout.registerComponent('Hit Percent', function (container, state) {
|
||||||
|
container.getElement().append('<div id="hitPercent" class="numerical_data"></div>');
|
||||||
|
});
|
||||||
|
|
||||||
|
dashlayout.init();
|
||||||
|
dashlayout.on('stateChanged', function () {
|
||||||
|
localStorage.setItem('dashState', JSON.stringify(dashlayout.toConfig()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
jQuery(document).ready(function () {
|
||||||
|
loadDash();
|
||||||
|
loadOptions();
|
||||||
|
$(window).resize(function () {
|
||||||
|
let dash = $("#dashboard");
|
||||||
|
dashlayout.updateSize(dash.width(), dash.height());
|
||||||
|
});
|
||||||
|
$("#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)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
loadStuff();
|
||||||
|
fetch("/api/allStats")
|
||||||
|
.then(response => async function () {
|
||||||
|
let respj = JSON.parse(await response.text());
|
||||||
|
updateValues(respj);
|
||||||
|
console.log(respj);
|
||||||
|
});
|
||||||
|
statRequest = setInterval(getStats, refreshRate);
|
||||||
|
});
|
||||||
|
|
||||||
|
function loadStuff() {
|
||||||
|
hitmiss = new Chart(document.getElementById('hitpie').getContext('2d'), {
|
||||||
|
type: 'doughnut',
|
||||||
|
data: {
|
||||||
|
datasets: [{
|
||||||
|
data: [0, 0, 0]
|
||||||
|
}],
|
||||||
|
labels: [
|
||||||
|
'Hits',
|
||||||
|
'Misses',
|
||||||
|
'Browser Cached'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
options: {}
|
||||||
|
});
|
||||||
|
req = new Chart(document.getElementById('requestsserved').getContext('2d'), {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: [],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Requests Served',
|
||||||
|
backgroundColor: "#f00",
|
||||||
|
borderColor: "#f00",
|
||||||
|
data: [],
|
||||||
|
fill: false
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
yAxes: [{
|
||||||
|
ticks: {
|
||||||
|
beginAtZero: true
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
byte = new Chart(document.getElementById('bytessent').getContext('2d'), {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: [],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Bytes Sent',
|
||||||
|
backgroundColor: "#f00",
|
||||||
|
borderColor: "#f00",
|
||||||
|
data: [],
|
||||||
|
fill: false
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
yAxes: [{
|
||||||
|
ticks: {
|
||||||
|
beginAtZero: true
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cached = new Chart(document.getElementById('browsercached').getContext('2d'), {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: [],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Bytes On Disk',
|
||||||
|
backgroundColor: "#f00",
|
||||||
|
borderColor: "#f00",
|
||||||
|
data: [],
|
||||||
|
fill: false
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
yAxes: [{
|
||||||
|
ticks: {
|
||||||
|
beginAtZero: true
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//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,
|
||||||
|
lock_dashboard: 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;
|
||||||
|
lockDash = options.lock_dashboard;
|
||||||
|
$("#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);
|
||||||
|
$("#lockDash").prop("checked", lockDash)
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetOptions() {
|
||||||
|
if (confirm("Do you really want to reset all customizations 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);
|
||||||
|
dashlayout.destroy();
|
||||||
|
localStorage.removeItem('dashState');
|
||||||
|
loadDash();
|
||||||
|
selectTab('dash', 'dashb');
|
||||||
|
let dash = $("#dashboard");
|
||||||
|
dashlayout.updateSize(dash.width(), dash.height());
|
||||||
|
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"),
|
||||||
|
lock_dashboard: $("#lockDash").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);
|
||||||
|
}
|
||||||
|
if (options.lock_dashboard !== lockDash) {
|
||||||
|
lockDash = options.lock_dashboard;
|
||||||
|
config.settings.showCloseIcon = !lockDash;
|
||||||
|
// localStorage.setItem('dashState', JSON.stringify(dashlayout.toConfig()));
|
||||||
|
// $("#dashboard").empty();
|
||||||
|
// loadDash();
|
||||||
|
$("#lockDashcb").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)
|
||||||
|
$(document.body).children().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)
|
||||||
|
$(document.body).children().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 getStats() {
|
||||||
|
fetch("/api/stats")
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(response => {
|
||||||
|
updateValues(response);
|
||||||
|
console.log(response);
|
||||||
|
});
|
||||||
|
//TODO: use values and update web info
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateValues(data) {
|
||||||
|
for (let key in data) {
|
||||||
|
if (data.hasOwnProperty(key)) {
|
||||||
|
let x = data[key];
|
||||||
|
hitmiss.data.datasets[0].data[0] = x.cache_hits;
|
||||||
|
hitmiss.data.datasets[0].data[1] = x.cache_misses;
|
||||||
|
hitmiss.data.datasets[0].data[2] = x.browser_cached;
|
||||||
|
|
||||||
|
hitmiss.update()
|
||||||
|
req.data.labels.push(key);
|
||||||
|
req.data.datasets.forEach((dataset) => {
|
||||||
|
dataset.data.push(x.requests_served);
|
||||||
|
});
|
||||||
|
req.update()
|
||||||
|
byte.data.labels.push(key);
|
||||||
|
byte.data.datasets.forEach((dataset) => {
|
||||||
|
dataset.data.push(x.bytes_sent);
|
||||||
|
});
|
||||||
|
byte.update()
|
||||||
|
cached.data.labels.push(key);
|
||||||
|
cached.data.datasets.forEach((dataset) => {
|
||||||
|
dataset.data.push(x.bytes_on_disk);
|
||||||
|
});
|
||||||
|
cached.update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//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);
|
||||||
|
|
||||||
|
};
|
||||||
|
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));
|
||||||
|
}
|
BIN
src/main/resources/webui/icons/close.png
Normal file
BIN
src/main/resources/webui/icons/close.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 460 B |
BIN
src/main/resources/webui/icons/console.png
Normal file
BIN
src/main/resources/webui/icons/console.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 675 B |
BIN
src/main/resources/webui/icons/dashboard.png
Normal file
BIN
src/main/resources/webui/icons/dashboard.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 756 B |
BIN
src/main/resources/webui/icons/info.png
Normal file
BIN
src/main/resources/webui/icons/info.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 723 B |
BIN
src/main/resources/webui/icons/options.png
Normal file
BIN
src/main/resources/webui/icons/options.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 793 B |
BIN
src/main/resources/webui/icons/showmore.png
Normal file
BIN
src/main/resources/webui/icons/showmore.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 374 B |
126
src/main/resources/webui/index.html
Normal file
126
src/main/resources/webui/index.html
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
<!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="https://cdn.jsdelivr.net/npm/chart.js@2.8.0"></script>
|
||||||
|
<script type="text/javascript" src="https://golden-layout.com/files/latest/js/goldenlayout.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="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: 100px; margin: calc((50px - 29px)/2)">Options</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="content">
|
||||||
|
<div id="dashb" class="content" style="overflow-y: scroll">
|
||||||
|
<div class="contentHeader">
|
||||||
|
<h1 style="margin: 0;position:absolute; top: 42px; padding-left: 30px">Dashboard</h1>
|
||||||
|
</div>
|
||||||
|
<div id="dashboard" hidden></div>
|
||||||
|
<div id="pleasejustwork" style="height: calc(100% - 140px); width: calc(100% - 40px); margin: 20px">
|
||||||
|
<div id="thelonelynumber" style="position: absolute; width: 30%; margin: 20px;">
|
||||||
|
<div class="line_graph_data">
|
||||||
|
<canvas id="hitpie"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div id="thegraphfamily"
|
||||||
|
style="position: absolute; width: calc(70% - 80px); margin: 20px; left: calc(30% + 40px); top: 100px">
|
||||||
|
<div style="margin: 20px; width: calc(100% - 40px)" class="line_graph_data">
|
||||||
|
<canvas id="bytessent" style="height: 250px"></canvas>
|
||||||
|
</div>
|
||||||
|
<div style="margin: 20px; width: calc(100% - 40px)" class="line_graph_data">
|
||||||
|
<canvas id="requestsserved" style="height: 250px"></canvas>
|
||||||
|
</div>
|
||||||
|
<div style="margin: 20px; width: calc(100% - 40px)" class="line_graph_data">
|
||||||
|
<canvas id="browsercached" style="height: 250px"></canvas>
|
||||||
|
</div>
|
||||||
|
</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>
|
||||||
|
<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 (not maintained)</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>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>
|
680
src/main/resources/webui/layout.css
Normal file
680
src/main/resources/webui/layout.css
Normal file
|
@ -0,0 +1,680 @@
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dashboard{
|
||||||
|
position: absolute;
|
||||||
|
margin: 20px;
|
||||||
|
width: calc(100% - 40px);
|
||||||
|
height: calc(100% - 140px);
|
||||||
|
top: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.numerical_data {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#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: 100%;*/
|
||||||
|
/* width: 100%;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
width: 150px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*golden layout*/
|
||||||
|
.lm_root {
|
||||||
|
position: relative
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_row > .lm_item {
|
||||||
|
float: left
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_content {
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dragging, .lm_dragging * {
|
||||||
|
cursor: move !important;
|
||||||
|
user-select: none
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_maximised {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 40
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_maximise_placeholder {
|
||||||
|
display: none
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_splitter {
|
||||||
|
position: relative;
|
||||||
|
z-index: 20
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_splitter:hover, .lm_splitter.lm_dragging {
|
||||||
|
background: orange
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_splitter.lm_vertical .lm_drag_handle {
|
||||||
|
width: 100%;
|
||||||
|
height: 15px;
|
||||||
|
position: absolute;
|
||||||
|
top: -5px;
|
||||||
|
cursor: ns-resize
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_splitter.lm_horizontal {
|
||||||
|
float: left;
|
||||||
|
height: 100%
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_splitter.lm_horizontal .lm_drag_handle {
|
||||||
|
width: 15px;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
left: -5px;
|
||||||
|
cursor: ew-resize
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header {
|
||||||
|
overflow: visible;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header [class^=lm_] {
|
||||||
|
box-sizing: content-box !important
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_controls {
|
||||||
|
position: absolute;
|
||||||
|
right: 3px
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_controls > li {
|
||||||
|
cursor: pointer;
|
||||||
|
float: left;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
text-align: center
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style-type: none
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_tabs {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_tab {
|
||||||
|
cursor: pointer;
|
||||||
|
float: left;
|
||||||
|
height: 14px;
|
||||||
|
padding: 0 25px 5px 10px;
|
||||||
|
top: 1px;
|
||||||
|
position: relative
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_tab i {
|
||||||
|
width: 2px;
|
||||||
|
height: 19px;
|
||||||
|
position: absolute
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_tab i.lm_left {
|
||||||
|
top: 0;
|
||||||
|
left: -2px
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_tab i.lm_right {
|
||||||
|
top: 0;
|
||||||
|
right: -2px
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_tab .lm_title {
|
||||||
|
display: inline-block;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_tab .lm_close_tab {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
text-align: center
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_stack.lm_left .lm_header, .lm_stack.lm_right .lm_header {
|
||||||
|
height: 100%
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dragProxy.lm_left .lm_header, .lm_dragProxy.lm_right .lm_header, .lm_stack.lm_left .lm_header, .lm_stack.lm_right .lm_header {
|
||||||
|
width: 20px;
|
||||||
|
float: left;
|
||||||
|
vertical-align: top
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dragProxy.lm_left .lm_header .lm_tabs, .lm_dragProxy.lm_right .lm_header .lm_tabs, .lm_stack.lm_left .lm_header .lm_tabs, .lm_stack.lm_right .lm_header .lm_tabs {
|
||||||
|
transform-origin: left top;
|
||||||
|
top: 0;
|
||||||
|
width: 1000px
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dragProxy.lm_left .lm_header .lm_controls, .lm_dragProxy.lm_right .lm_header .lm_controls, .lm_stack.lm_left .lm_header .lm_controls, .lm_stack.lm_right .lm_header .lm_controls {
|
||||||
|
bottom: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dragProxy.lm_left .lm_items, .lm_dragProxy.lm_right .lm_items, .lm_stack.lm_left .lm_items, .lm_stack.lm_right .lm_items {
|
||||||
|
float: left
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dragProxy.lm_left .lm_header .lm_tabs, .lm_stack.lm_left .lm_header .lm_tabs {
|
||||||
|
transform: rotate(-90deg) scaleX(-1);
|
||||||
|
left: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dragProxy.lm_left .lm_header .lm_tabs .lm_tab, .lm_stack.lm_left .lm_header .lm_tabs .lm_tab {
|
||||||
|
transform: scaleX(-1);
|
||||||
|
margin-top: 1px
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dragProxy.lm_left .lm_header .lm_tabdropdown_list, .lm_stack.lm_left .lm_header .lm_tabdropdown_list {
|
||||||
|
top: initial;
|
||||||
|
right: initial;
|
||||||
|
left: 20px
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dragProxy.lm_right .lm_content {
|
||||||
|
float: left
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dragProxy.lm_right .lm_header .lm_tabs, .lm_stack.lm_right .lm_header .lm_tabs {
|
||||||
|
transform: rotate(90deg) scaleX(1);
|
||||||
|
left: 100%;
|
||||||
|
margin-left: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dragProxy.lm_right .lm_header .lm_controls, .lm_stack.lm_right .lm_header .lm_controls {
|
||||||
|
left: 3px
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dragProxy.lm_right .lm_header .lm_tabdropdown_list, .lm_stack.lm_right .lm_header .lm_tabdropdown_list {
|
||||||
|
top: initial;
|
||||||
|
right: 20px
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dragProxy.lm_bottom .lm_header .lm_tab, .lm_stack.lm_bottom .lm_header .lm_tab {
|
||||||
|
margin-top: 0;
|
||||||
|
border-top: none
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dragProxy.lm_bottom .lm_header .lm_controls, .lm_stack.lm_bottom .lm_header .lm_controls {
|
||||||
|
top: 3px
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dragProxy.lm_bottom .lm_header .lm_tabdropdown_list, .lm_stack.lm_bottom .lm_header .lm_tabdropdown_list {
|
||||||
|
top: initial;
|
||||||
|
bottom: 20px
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_drop_tab_placeholder {
|
||||||
|
float: left;
|
||||||
|
width: 100px;
|
||||||
|
height: 10px;
|
||||||
|
visibility: hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_controls .lm_tabdropdown:before {
|
||||||
|
content: '';
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
vertical-align: middle;
|
||||||
|
display: inline-block;
|
||||||
|
border-top: 5px dashed;
|
||||||
|
border-right: 5px solid transparent;
|
||||||
|
border-left: 5px solid transparent;
|
||||||
|
color: white
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_tabdropdown_list {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 0;
|
||||||
|
z-index: 5;
|
||||||
|
overflow: hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_tabdropdown_list .lm_tab {
|
||||||
|
clear: both;
|
||||||
|
padding-right: 10px;
|
||||||
|
margin: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_tabdropdown_list .lm_tab .lm_title {
|
||||||
|
width: 100px
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_tabdropdown_list .lm_close_tab {
|
||||||
|
display: none !important
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dragProxy {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 30
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dragProxy .lm_header {
|
||||||
|
background: transparent
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dragProxy .lm_content {
|
||||||
|
border-top: none;
|
||||||
|
overflow: hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dropTargetIndicator {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 20
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dropTargetIndicator .lm_inner {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
top: 0;
|
||||||
|
left: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_transition_indicator {
|
||||||
|
display: none;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 20
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_popin {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 9999
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_popin > * {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_popin > .lm_bg {
|
||||||
|
z-index: 10
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_popin > .lm_icon {
|
||||||
|
z-index: 20
|
||||||
|
}
|
||||||
|
|
||||||
|
/*# sourceMappingURL=goldenlayout-base.css.map */
|
334
src/main/resources/webui/themes/darkTheme.css
Normal file
334
src/main/resources/webui/themes/darkTheme.css
Normal file
|
@ -0,0 +1,334 @@
|
||||||
|
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: #555555;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gDat {
|
||||||
|
}
|
||||||
|
|
||||||
|
.line_graph_data {
|
||||||
|
background-color: #555555;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*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 {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*golden layout*/
|
||||||
|
.lm_goldenlayout {
|
||||||
|
background: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_content {
|
||||||
|
background: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dragProxy .lm_content {
|
||||||
|
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.9)
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dropTargetIndicator {
|
||||||
|
box-shadow: inset 0 0 30px #000000;
|
||||||
|
outline: 1px dashed #cccccc;
|
||||||
|
transition: all 200ms ease
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dropTargetIndicator .lm_inner {
|
||||||
|
background: #000000;
|
||||||
|
opacity: .2
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_splitter {
|
||||||
|
background: inherit;
|
||||||
|
opacity: .001;
|
||||||
|
transition: opacity 200ms ease
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_splitter:hover, .lm_splitter.lm_dragging {
|
||||||
|
background: #303030;
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header {
|
||||||
|
background-color: #606060;
|
||||||
|
height: 20px;
|
||||||
|
user-select: none
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header.lm_selectable {
|
||||||
|
cursor: pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_tab {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #f0f0f0;
|
||||||
|
background-color: #303030;
|
||||||
|
margin-right: 2px;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
padding-top: 1px
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_tab .lm_close_tab {
|
||||||
|
width: 9px;
|
||||||
|
height: 9px;
|
||||||
|
background-image: url("../icons/close.png");
|
||||||
|
filter: invert(100%);
|
||||||
|
background-size: 14px 14px;
|
||||||
|
background-position: center center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
top: 4px;
|
||||||
|
right: 6px;
|
||||||
|
opacity: .4
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_tab .lm_close_tab:hover {
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_tab.lm_active {
|
||||||
|
background-color: #909090;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_tab.lm_active .lm_close_tab:hover {
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dragProxy.lm_bottom .lm_header .lm_tab, .lm_stack.lm_bottom .lm_header .lm_tab {
|
||||||
|
box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.3)
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dragProxy.lm_bottom .lm_header .lm_tab.lm_active, .lm_stack.lm_bottom .lm_header .lm_tab.lm_active {
|
||||||
|
box-shadow: 0 2px 2px #000000
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_selected .lm_header {
|
||||||
|
background-color: #452500
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_tab:hover, .lm_tab.lm_active {
|
||||||
|
background: #202020;
|
||||||
|
color: #f0f0f0
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_controls .lm_tabdropdown:before {
|
||||||
|
color: #ffffff
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_controls > li {
|
||||||
|
position: relative;
|
||||||
|
background-position: center center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
opacity: .4;
|
||||||
|
transition: opacity 300ms ease
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_controls > li:hover {
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_controls .lm_popout {
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAPklEQVR4nI2Q0QoAIAwCNfr/X7aXCpGN8snBdgejJOzckpkxs9jR6K6T5JpU0nWl5pSXTk7qwh8SnNT+CAAWCgkKFpuSWsUAAAAASUVORK5CYII=)
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_controls .lm_maximise {
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAKElEQVR4nGP8////fwYCgImQAgYGBgYWKM2IR81/okwajIpgvsMbVgAwgQYRVakEKQAAAABJRU5ErkJggg==)
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_controls .lm_close {
|
||||||
|
top: 2px;
|
||||||
|
width: 18px !important;
|
||||||
|
height: 18px !important;
|
||||||
|
background-image: url("../icons/close.png");
|
||||||
|
background-size: 16px 16px;
|
||||||
|
background-position: center center;
|
||||||
|
filter: invert(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_maximised .lm_header {
|
||||||
|
background-color: #000000
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_maximised .lm_controls .lm_maximise {
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAJ0lEQVR4nGP8//8/AzGAiShVI1YhCwMDA8OsWbPwBmZaWhoj0SYCAN1lBxMAX4n0AAAAAElFTkSuQmCC)
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_transition_indicator {
|
||||||
|
background-color: #000000;
|
||||||
|
border: 1px dashed #555555
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_popin {
|
||||||
|
cursor: pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_popin .lm_bg {
|
||||||
|
background: #ffffff;
|
||||||
|
opacity: .3
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_popin .lm_icon {
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAAJCAYAAADpeqZqAAAAWklEQVR4nJWOyw3AIAxDHcQC7L8jbwT3AlJBfNp3SiI7dtRaLSlKKeoA1oEsKSQZCEluexw8Tm3ohk+E7bnOUHUGcNh+HwbBygw4AZ7FN/Lt84p0l+yTflV8AKQyLdcCRJi/AAAAAElFTkSuQmCC);
|
||||||
|
background-position: center center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
border-left: 1px solid #eeeeee;
|
||||||
|
border-top: 1px solid #eeeeee;
|
||||||
|
opacity: .7
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_popin:hover .lm_icon {
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/*# sourceMappingURL=goldenlayout-dark-theme.css.map */
|
97
src/main/resources/webui/themes/eyekillerTheme.css
Normal file
97
src/main/resources/webui/themes/eyekillerTheme.css
Normal 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 {
|
||||||
|
|
||||||
|
}
|
329
src/main/resources/webui/themes/lightTheme.css
Normal file
329
src/main/resources/webui/themes/lightTheme.css
Normal file
|
@ -0,0 +1,329 @@
|
||||||
|
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 {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*golden layout overrides*/
|
||||||
|
|
||||||
|
.lm_goldenlayout {
|
||||||
|
background: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_content {
|
||||||
|
background: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dragProxy .lm_content {
|
||||||
|
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.9)
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dropTargetIndicator {
|
||||||
|
box-shadow: inset 0 0 30px #000000;
|
||||||
|
outline: 1px dashed #cccccc;
|
||||||
|
transition: all 200ms ease
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dropTargetIndicator .lm_inner {
|
||||||
|
background: #000000;
|
||||||
|
opacity: .2
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_splitter {
|
||||||
|
background: inherit;
|
||||||
|
opacity: .001;
|
||||||
|
transition: opacity 200ms ease
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_splitter:hover, .lm_splitter.lm_dragging {
|
||||||
|
background-color: #dadada;
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header {
|
||||||
|
background-color: #ededed;
|
||||||
|
height: 20px;
|
||||||
|
user-select: none
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header.lm_selectable {
|
||||||
|
cursor: pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_tab {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #202020;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
margin-right: 2px;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
padding-top: 1px
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_tab .lm_close_tab {
|
||||||
|
width: 9px;
|
||||||
|
height: 9px;
|
||||||
|
background-image: url("../icons/close.png");
|
||||||
|
background-position: center center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
top: 4px;
|
||||||
|
right: 6px;
|
||||||
|
opacity: .4
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_tab .lm_close_tab:hover {
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_tab.lm_active {
|
||||||
|
background-color: #e1e1e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_tab.lm_active .lm_close_tab:hover {
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dragProxy.lm_bottom .lm_header .lm_tab, .lm_stack.lm_bottom .lm_header .lm_tab {
|
||||||
|
box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.3)
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dragProxy.lm_bottom .lm_header .lm_tab.lm_active, .lm_stack.lm_bottom .lm_header .lm_tab.lm_active {
|
||||||
|
box-shadow: 0 2px 2px #000000
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_selected .lm_header {
|
||||||
|
background-color: #452500
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_tab:hover, .lm_tab.lm_active {
|
||||||
|
background-color: #dadada;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_controls .lm_tabdropdown:before {
|
||||||
|
color: #ffffff
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_controls > li {
|
||||||
|
position: relative;
|
||||||
|
background-position: center center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
opacity: .4;
|
||||||
|
transition: opacity 300ms ease
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_controls > li:hover {
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_controls .lm_popout {
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAPklEQVR4nI2Q0QoAIAwCNfr/X7aXCpGN8snBdgejJOzckpkxs9jR6K6T5JpU0nWl5pSXTk7qwh8SnNT+CAAWCgkKFpuSWsUAAAAASUVORK5CYII=)
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_controls .lm_maximise {
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAKElEQVR4nGP8////fwYCgImQAgYGBgYWKM2IR81/okwajIpgvsMbVgAwgQYRVakEKQAAAABJRU5ErkJggg==)
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_controls .lm_close {
|
||||||
|
top: 2px;
|
||||||
|
width: 10px !important;
|
||||||
|
height: 10px !important;
|
||||||
|
background-image: url("../icons/close.png");
|
||||||
|
background-position: center center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_maximised .lm_header {
|
||||||
|
background-color: #000000
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_maximised .lm_controls .lm_maximise {
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAJ0lEQVR4nGP8//8/AzGAiShVI1YhCwMDA8OsWbPwBmZaWhoj0SYCAN1lBxMAX4n0AAAAAElFTkSuQmCC)
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_transition_indicator {
|
||||||
|
background-color: #000000;
|
||||||
|
border: 1px dashed #555555
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_popin {
|
||||||
|
cursor: pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_popin .lm_bg {
|
||||||
|
background: #ffffff;
|
||||||
|
opacity: .3
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_popin .lm_icon {
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAAJCAYAAADpeqZqAAAAWklEQVR4nJWOyw3AIAxDHcQC7L8jbwT3AlJBfNp3SiI7dtRaLSlKKeoA1oEsKSQZCEluexw8Tm3ohk+E7bnOUHUGcNh+HwbBygw4AZ7FN/Lt84p0l+yTflV8AKQyLdcCRJi/AAAAAElFTkSuQmCC);
|
||||||
|
background-position: center center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
border-left: 1px solid #eeeeee;
|
||||||
|
border-top: 1px solid #eeeeee;
|
||||||
|
opacity: .7
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_popin:hover .lm_icon {
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/*# sourceMappingURL=goldenlayout-dark-theme.css.map */
|
327
src/main/resources/webui/themes/midnightTheme.css
Normal file
327
src/main/resources/webui/themes/midnightTheme.css
Normal file
|
@ -0,0 +1,327 @@
|
||||||
|
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 {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.numerical_data {
|
||||||
|
background-color: #353535;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line_graph_data {
|
||||||
|
background-color: #353535;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*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 {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*golden layout*/
|
||||||
|
.lm_goldenlayout {
|
||||||
|
background: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_content {
|
||||||
|
background: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dragProxy .lm_content {
|
||||||
|
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.9)
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dropTargetIndicator {
|
||||||
|
box-shadow: inset 0 0 30px #000000;
|
||||||
|
outline: 1px dashed #cccccc;
|
||||||
|
transition: all 200ms ease
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dropTargetIndicator .lm_inner {
|
||||||
|
background: #000000;
|
||||||
|
opacity: .2
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_splitter {
|
||||||
|
background: inherit;
|
||||||
|
opacity: .001;
|
||||||
|
transition: opacity 200ms ease
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_splitter:hover, .lm_splitter.lm_dragging {
|
||||||
|
background: #303030;
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header {
|
||||||
|
background-color: #404040;
|
||||||
|
height: 20px;
|
||||||
|
user-select: none
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header.lm_selectable {
|
||||||
|
cursor: pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_tab {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #bfbfbf;
|
||||||
|
background-color: #404040;
|
||||||
|
margin-right: 2px;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
padding-top: 1px
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_tab .lm_close_tab {
|
||||||
|
width: 9px;
|
||||||
|
height: 9px;
|
||||||
|
background-image: url("../icons/close.png");
|
||||||
|
filter: invert(100%);
|
||||||
|
background-size: 14px 14px;
|
||||||
|
background-position: center center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
top: 4px;
|
||||||
|
right: 6px;
|
||||||
|
opacity: .4
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_tab .lm_close_tab:hover {
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_tab.lm_active {
|
||||||
|
background-color: #606060;
|
||||||
|
}
|
||||||
|
.lm_header .lm_tab.lm_active:hover {
|
||||||
|
background-color: #505050;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_tab.lm_active .lm_close_tab:hover {
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dragProxy.lm_bottom .lm_header .lm_tab, .lm_stack.lm_bottom .lm_header .lm_tab {
|
||||||
|
box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.3)
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_dragProxy.lm_bottom .lm_header .lm_tab.lm_active, .lm_stack.lm_bottom .lm_header .lm_tab.lm_active {
|
||||||
|
box-shadow: 0 2px 2px #000000
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_selected .lm_header {
|
||||||
|
background-color: #452500
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_tab:hover{
|
||||||
|
background-color: #505050;
|
||||||
|
color: #bfbfbf
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_header .lm_controls .lm_tabdropdown:before {
|
||||||
|
color: #ffffff
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_controls > li {
|
||||||
|
position: relative;
|
||||||
|
background-position: center center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
opacity: .4;
|
||||||
|
transition: opacity 300ms ease
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_controls > li:hover {
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_controls .lm_popout {
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAPklEQVR4nI2Q0QoAIAwCNfr/X7aXCpGN8snBdgejJOzckpkxs9jR6K6T5JpU0nWl5pSXTk7qwh8SnNT+CAAWCgkKFpuSWsUAAAAASUVORK5CYII=)
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_controls .lm_maximise {
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAKElEQVR4nGP8////fwYCgImQAgYGBgYWKM2IR81/okwajIpgvsMbVgAwgQYRVakEKQAAAABJRU5ErkJggg==)
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_controls .lm_close {
|
||||||
|
top: 2px;
|
||||||
|
width: 18px !important;
|
||||||
|
height: 18px !important;
|
||||||
|
background-image: url("../icons/close.png");
|
||||||
|
background-size: 16px 16px;
|
||||||
|
background-position: center center;
|
||||||
|
filter: invert(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_maximised .lm_header {
|
||||||
|
background-color: #000000
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_maximised .lm_controls .lm_maximise {
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAJ0lEQVR4nGP8//8/AzGAiShVI1YhCwMDA8OsWbPwBmZaWhoj0SYCAN1lBxMAX4n0AAAAAElFTkSuQmCC)
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_transition_indicator {
|
||||||
|
background-color: #000000;
|
||||||
|
border: 1px dashed #555555
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_popin {
|
||||||
|
cursor: pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_popin .lm_bg {
|
||||||
|
background: #ffffff;
|
||||||
|
opacity: .3
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_popin .lm_icon {
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAAJCAYAAADpeqZqAAAAWklEQVR4nJWOyw3AIAxDHcQC7L8jbwT3AlJBfNp3SiI7dtRaLSlKKeoA1oEsKSQZCEluexw8Tm3ohk+E7bnOUHUGcNh+HwbBygw4AZ7FN/Lt84p0l+yTflV8AKQyLdcCRJi/AAAAAElFTkSuQmCC);
|
||||||
|
background-position: center center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
border-left: 1px solid #eeeeee;
|
||||||
|
border-top: 1px solid #eeeeee;
|
||||||
|
opacity: .7
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_popin:hover .lm_icon {
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/*# sourceMappingURL=goldenlayout-dark-theme.css.map */
|
22
src/main/resources/webui/themes/sharpStyle.css
Normal file
22
src/main/resources/webui/themes/sharpStyle.css
Normal 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);
|
||||||
|
}
|
165
src/main/resources/webui/themes/softStyle.css
Normal file
165
src/main/resources/webui/themes/softStyle.css
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
/*Content holder positions*/
|
||||||
|
|
||||||
|
#pageBar {
|
||||||
|
}
|
||||||
|
|
||||||
|
#connection {
|
||||||
|
}
|
||||||
|
|
||||||
|
#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: 0 0 10px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gDat {
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line_graph_data {
|
||||||
|
border-radius: 0 0 10px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Console and options positions*/
|
||||||
|
|
||||||
|
#console {
|
||||||
|
}
|
||||||
|
|
||||||
|
#buttonBoard {
|
||||||
|
}
|
||||||
|
|
||||||
|
#liveConsole {
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#console_input {
|
||||||
|
}
|
||||||
|
|
||||||
|
#console_text {
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Web option positions*/
|
||||||
|
#dashOptions {
|
||||||
|
}
|
||||||
|
|
||||||
|
#options {
|
||||||
|
}
|
||||||
|
|
||||||
|
#apply {
|
||||||
|
}
|
||||||
|
|
||||||
|
.option {
|
||||||
|
}
|
||||||
|
|
||||||
|
.optionLabel {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.optionInput {
|
||||||
|
}
|
||||||
|
|
||||||
|
/*misc modifications*/
|
||||||
|
|
||||||
|
.input {
|
||||||
|
padding-left: 5px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button{
|
||||||
|
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 {
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Golden Layout Overrides*/
|
||||||
|
|
||||||
|
.lm_header {
|
||||||
|
border-radius: 10px 10px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_tabs:first-child .lm_tab{
|
||||||
|
border-top-left-radius: 10px;
|
||||||
|
}
|
||||||
|
.lm_tabs:first-child .lm_tab ~ .lm_tab{
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lm_splitter {
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue