mirror of
https://github.com/Grasscutters/gcgm-plugin.git
synced 2024-11-25 15:32:52 +08:00
RAM usage and player count graphs. Now uses serverHooks
This commit is contained in:
parent
e6a94436dc
commit
9ed0d17975
@ -6,9 +6,12 @@ import com.benj4.gcgm.utils.*;
|
|||||||
import com.benj4.gcgm.utils.web.WebUtils;
|
import com.benj4.gcgm.utils.web.WebUtils;
|
||||||
import emu.grasscutter.Grasscutter;
|
import emu.grasscutter.Grasscutter;
|
||||||
import emu.grasscutter.plugin.Plugin;
|
import emu.grasscutter.plugin.Plugin;
|
||||||
|
import emu.grasscutter.plugin.api.ServerHook;
|
||||||
|
import emu.grasscutter.server.dispatch.DispatchServer;
|
||||||
import emu.grasscutter.server.event.EventHandler;
|
import emu.grasscutter.server.event.EventHandler;
|
||||||
import emu.grasscutter.server.event.HandlerPriority;
|
import emu.grasscutter.server.event.HandlerPriority;
|
||||||
import emu.grasscutter.server.event.game.ServerTickEvent;
|
import emu.grasscutter.server.event.game.ServerTickEvent;
|
||||||
|
import emu.grasscutter.server.game.GameServer;
|
||||||
import emu.grasscutter.utils.Utils;
|
import emu.grasscutter.utils.Utils;
|
||||||
import express.Express;
|
import express.Express;
|
||||||
|
|
||||||
@ -18,7 +21,7 @@ public class GCGMPlugin extends Plugin {
|
|||||||
|
|
||||||
private static GCGMPlugin INSTANCE;
|
private static GCGMPlugin INSTANCE;
|
||||||
EventHandler<ServerTickEvent> serverTickEventHandler;
|
EventHandler<ServerTickEvent> serverTickEventHandler;
|
||||||
private static WebSocketServer webSocketServer;
|
private WebSocketServer webSocketServer;
|
||||||
private File webData;
|
private File webData;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -57,12 +60,11 @@ public class GCGMPlugin extends Plugin {
|
|||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
if(webData.exists()) {
|
if(webData.exists()) {
|
||||||
Express app = Grasscutter.getDispatchServer().getServer();
|
WebUtils.addStaticFiles(webData);
|
||||||
WebUtils.addStaticFiles(app, webData);
|
|
||||||
webSocketServer = new WebSocketServer();
|
webSocketServer = new WebSocketServer();
|
||||||
webSocketServer.start(app);
|
webSocketServer.start();
|
||||||
|
|
||||||
Grasscutter.getPluginManager().registerListener(serverTickEventHandler);
|
serverTickEventHandler.register();
|
||||||
|
|
||||||
Grasscutter.getLogger().info("[GCGM] GCGM Enabled");
|
Grasscutter.getLogger().info("[GCGM] GCGM Enabled");
|
||||||
Grasscutter.getLogger().info("[GCGM] You can access your GM panel by navigating to " + GCGMUtils.GetDispatchAddress() + WebUtils.PAGE_ROOT);
|
Grasscutter.getLogger().info("[GCGM] You can access your GM panel by navigating to " + GCGMUtils.GetDispatchAddress() + WebUtils.PAGE_ROOT);
|
||||||
@ -78,11 +80,19 @@ public class GCGMPlugin extends Plugin {
|
|||||||
Grasscutter.getLogger().info("[GCGM] GCGM Disabled");
|
Grasscutter.getLogger().info("[GCGM] GCGM Disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GCGMPlugin GetInstance() {
|
public static GCGMPlugin getInstance() {
|
||||||
return INSTANCE;
|
return INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static WebSocketServer getWebSocketServer() {
|
public WebSocketServer getWebSocketServer() {
|
||||||
return webSocketServer;
|
return webSocketServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static GameServer getGameServer() {
|
||||||
|
return GCGMPlugin.getInstance().getServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DispatchServer getDispatchServer() {
|
||||||
|
return ServerHook.getInstance().getDispatchServer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,35 @@
|
|||||||
package com.benj4.gcgm.handlers;
|
package com.benj4.gcgm.handlers;
|
||||||
|
|
||||||
import com.benj4.gcgm.GCGMPlugin;
|
import com.benj4.gcgm.GCGMPlugin;
|
||||||
|
import com.benj4.gcgm.server.websocket.json.TickData;
|
||||||
import com.benj4.gcgm.server.websocket.json.WSData;
|
import com.benj4.gcgm.server.websocket.json.WSData;
|
||||||
|
import com.benj4.gcgm.utils.GCGMUtils;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
import emu.grasscutter.server.event.game.ServerTickEvent;
|
import emu.grasscutter.server.event.game.ServerTickEvent;
|
||||||
import emu.grasscutter.utils.EventConsumer;
|
import emu.grasscutter.utils.EventConsumer;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
public class ServerTickHandler implements EventConsumer<ServerTickEvent> {
|
public class ServerTickHandler implements EventConsumer<ServerTickEvent> {
|
||||||
|
private static Instant firstTick;
|
||||||
private static Instant lastTick;
|
private static Instant lastTick;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void consume(ServerTickEvent serverTickEvent) {
|
public void consume(ServerTickEvent serverTickEvent) {
|
||||||
if(lastTick != null) {
|
if(lastTick != null) {
|
||||||
Instant now = Instant.now();
|
Instant now = Instant.now();
|
||||||
long timeTaken = now.toEpochMilli() - lastTick.toEpochMilli();
|
TickData data = new TickData();
|
||||||
GCGMPlugin.getWebSocketServer().broadcast(new WSData("tick", timeTaken));
|
data.tickTimeElapsed = now.toEpochMilli() - lastTick.toEpochMilli();
|
||||||
|
data.serverUptime = lastTick.toEpochMilli() - firstTick.toEpochMilli();
|
||||||
|
data.getFreeMemory = GCGMUtils.GetFreeJVMMemory();
|
||||||
|
data.getAllocatedMemory = GCGMUtils.GetAllocatedJVMMemory();
|
||||||
|
data.playerCount = GCGMPlugin.getGameServer().getPlayers().size();
|
||||||
|
|
||||||
|
GCGMPlugin.getInstance().getWebSocketServer().broadcast(new WSData("tick", data));
|
||||||
lastTick = now;
|
lastTick = now;
|
||||||
} else {
|
} else {
|
||||||
lastTick = Instant.now();
|
lastTick = Instant.now();
|
||||||
|
firstTick = Instant.now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.benj4.gcgm.server.websocket;
|
package com.benj4.gcgm.server.websocket;
|
||||||
|
|
||||||
|
import com.benj4.gcgm.GCGMPlugin;
|
||||||
import com.benj4.gcgm.server.websocket.json.WSData;
|
import com.benj4.gcgm.server.websocket.json.WSData;
|
||||||
import emu.grasscutter.Grasscutter;
|
import emu.grasscutter.Grasscutter;
|
||||||
import express.Express;
|
import express.Express;
|
||||||
@ -16,7 +17,9 @@ public class WebSocketServer {
|
|||||||
//SocketIOServer socketIOServer;
|
//SocketIOServer socketIOServer;
|
||||||
private static Map<WsContext, String> userUsernameMap = new ConcurrentHashMap<>();
|
private static Map<WsContext, String> userUsernameMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public void start(Express app) {
|
public void start() {
|
||||||
|
Express app = GCGMPlugin.getDispatchServer().getServer();
|
||||||
|
|
||||||
app.ws("/gm", ws -> {
|
app.ws("/gm", ws -> {
|
||||||
ws.onConnect(ctx -> {
|
ws.onConnect(ctx -> {
|
||||||
String username = "Not logged in";
|
String username = "Not logged in";
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
package com.benj4.gcgm.server.websocket.json;
|
||||||
|
|
||||||
|
public class TickData {
|
||||||
|
public long tickTimeElapsed;
|
||||||
|
public long serverUptime;
|
||||||
|
public long getFreeMemory;
|
||||||
|
public long getAllocatedMemory;
|
||||||
|
public int playerCount;
|
||||||
|
}
|
@ -2,10 +2,10 @@ package com.benj4.gcgm.server.websocket.json;
|
|||||||
|
|
||||||
public class WSData {
|
public class WSData {
|
||||||
public String eventName;
|
public String eventName;
|
||||||
public Object object;
|
public Object data;
|
||||||
|
|
||||||
public WSData(String eventName, Object object) {
|
public WSData(String eventName, Object data) {
|
||||||
this.eventName = eventName;
|
this.eventName = eventName;
|
||||||
this.object = object;
|
this.data = data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package com.benj4.gcgm.utils;
|
|||||||
|
|
||||||
import com.benj4.gcgm.GCGMPlugin;
|
import com.benj4.gcgm.GCGMPlugin;
|
||||||
import emu.grasscutter.Grasscutter;
|
import emu.grasscutter.Grasscutter;
|
||||||
|
import emu.grasscutter.server.game.GameServer;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -11,6 +12,8 @@ import java.nio.file.StandardCopyOption;
|
|||||||
|
|
||||||
public class GCGMUtils {
|
public class GCGMUtils {
|
||||||
|
|
||||||
|
private static Runtime RUNTIME = Runtime.getRuntime();
|
||||||
|
|
||||||
public static String GetDispatchAddress() {
|
public static String GetDispatchAddress() {
|
||||||
return "http" + (Grasscutter.getConfig().getDispatchOptions().FrontHTTPS ? "s" : "") + "://" +
|
return "http" + (Grasscutter.getConfig().getDispatchOptions().FrontHTTPS ? "s" : "") + "://" +
|
||||||
(Grasscutter.getConfig().getDispatchOptions().PublicIp.isEmpty() ? Grasscutter.getConfig().getDispatchOptions().Ip : Grasscutter.getConfig().getDispatchOptions().PublicIp) +
|
(Grasscutter.getConfig().getDispatchOptions().PublicIp.isEmpty() ? Grasscutter.getConfig().getDispatchOptions().Ip : Grasscutter.getConfig().getDispatchOptions().PublicIp) +
|
||||||
@ -20,7 +23,7 @@ public class GCGMUtils {
|
|||||||
public static boolean CopyFile(String resourceName, String copyLocation) {
|
public static boolean CopyFile(String resourceName, String copyLocation) {
|
||||||
try {
|
try {
|
||||||
Grasscutter.getLogger().info("[GCGM] Copying 'DefaultWebApp.zip' to './plugins/GCGM'");
|
Grasscutter.getLogger().info("[GCGM] Copying 'DefaultWebApp.zip' to './plugins/GCGM'");
|
||||||
Files.copy(GCGMPlugin.GetInstance().getResource(resourceName), Paths.get(new File(copyLocation).toURI()), StandardCopyOption.REPLACE_EXISTING);
|
Files.copy(GCGMPlugin.getInstance().getResource(resourceName), Paths.get(new File(copyLocation).toURI()), StandardCopyOption.REPLACE_EXISTING);
|
||||||
return true;
|
return true;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Grasscutter.getLogger().error(String.format("[GCGM] An error occurred while trying to copy '%s' to '%s'", resourceName, copyLocation));
|
Grasscutter.getLogger().error(String.format("[GCGM] An error occurred while trying to copy '%s' to '%s'", resourceName, copyLocation));
|
||||||
@ -28,4 +31,12 @@ public class GCGMUtils {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static long GetFreeJVMMemory() {
|
||||||
|
return GCGMUtils.RUNTIME.freeMemory();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long GetAllocatedJVMMemory() {
|
||||||
|
return GCGMUtils.RUNTIME.totalMemory();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.benj4.gcgm.utils.web;
|
package com.benj4.gcgm.utils.web;
|
||||||
|
|
||||||
|
import com.benj4.gcgm.GCGMPlugin;
|
||||||
import emu.grasscutter.Grasscutter;
|
import emu.grasscutter.Grasscutter;
|
||||||
import emu.grasscutter.utils.Utils;
|
import emu.grasscutter.utils.Utils;
|
||||||
import express.Express;
|
import express.Express;
|
||||||
@ -12,7 +13,8 @@ public class WebUtils {
|
|||||||
|
|
||||||
public static final String PAGE_ROOT = "/gm";
|
public static final String PAGE_ROOT = "/gm";
|
||||||
|
|
||||||
public static void addStaticFiles(Express app, File staticRoot) {
|
public static void addStaticFiles(File staticRoot) {
|
||||||
|
Express app = GCGMPlugin.getDispatchServer().getServer();
|
||||||
app.raw().config.precompressStaticFiles = false; // MUST BE SET TO FALSE OR FILES SUCH AS IMAGES WILL APPEAR CORRUPTED
|
app.raw().config.precompressStaticFiles = false; // MUST BE SET TO FALSE OR FILES SUCH AS IMAGES WILL APPEAR CORRUPTED
|
||||||
app.raw().config.addStaticFiles(PAGE_ROOT, staticRoot.getAbsolutePath(), Location.EXTERNAL);
|
app.raw().config.addStaticFiles(PAGE_ROOT, staticRoot.getAbsolutePath(), Location.EXTERNAL);
|
||||||
app.raw().config.addSinglePageRoot(PAGE_ROOT, Utils.toFilePath(staticRoot.getPath() + "/index.html"), Location.EXTERNAL);
|
app.raw().config.addSinglePageRoot(PAGE_ROOT, Utils.toFilePath(staticRoot.getPath() + "/index.html"), Location.EXTERNAL);
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
|
import { WebsocketProvider } from './Context/WebsocketProvider';
|
||||||
import Dashboard from './Dashboard';
|
import Dashboard from './Dashboard';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
|
<WebsocketProvider>
|
||||||
<Dashboard />
|
<Dashboard />
|
||||||
|
</WebsocketProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
.graph {
|
.graph {
|
||||||
position: absolute;
|
float: left;
|
||||||
transform: translate(-50%, 0%);
|
|
||||||
top: 6%;
|
|
||||||
left: 50%;
|
|
||||||
border-color: grey;
|
border-color: grey;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
width: 800px;
|
width: 800px;
|
||||||
|
margin: 10px;
|
||||||
|
margin-left: 50px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
@ -1,94 +1,114 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { Component, useState, useEffect } from "react";
|
||||||
import {
|
import { Chart as ChartJS, CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend } from "chart.js";
|
||||||
Chart as ChartJS,
|
|
||||||
CategoryScale,
|
|
||||||
LinearScale,
|
|
||||||
PointElement,
|
|
||||||
LineElement,
|
|
||||||
Title,
|
|
||||||
Tooltip,
|
|
||||||
Legend,
|
|
||||||
} from 'chart.js';
|
|
||||||
import { useWebSocket, ReadyState } from "react-use-websocket/dist/lib/use-websocket";
|
|
||||||
import { Line } from "react-chartjs-2";
|
import { Line } from "react-chartjs-2";
|
||||||
|
|
||||||
import "./StatsWidget.css";
|
import "./StatsWidget.css";
|
||||||
|
import WebsocketContext, { useWebsocket } from "../../Context/WebsocketProvider";
|
||||||
|
import { toHaveStyle } from "@testing-library/jest-dom/dist/matchers";
|
||||||
|
|
||||||
ChartJS.register(
|
ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement);
|
||||||
CategoryScale,
|
|
||||||
LinearScale,
|
|
||||||
PointElement,
|
|
||||||
LineElement,
|
|
||||||
);
|
|
||||||
|
|
||||||
const CHART_COLORS = {
|
const CHART_COLORS = {
|
||||||
red: 'rgb(255, 99, 132)',
|
red: "rgb(255, 99, 132)",
|
||||||
orange: 'rgb(255, 159, 64)',
|
orange: "rgb(255, 159, 64)",
|
||||||
yellow: 'rgb(255, 205, 86)',
|
yellow: "rgb(255, 205, 86)",
|
||||||
green: 'rgb(75, 192, 192)',
|
green: "rgb(75, 192, 192)",
|
||||||
blue: 'rgb(54, 162, 235)',
|
blue: "rgb(54, 162, 235)",
|
||||||
purple: 'rgb(153, 102, 255)',
|
purple: "rgb(153, 102, 255)",
|
||||||
grey: 'rgb(201, 203, 207)'
|
grey: "rgb(201, 203, 207)",
|
||||||
};
|
};
|
||||||
|
|
||||||
const TICK_COUNT = 50;
|
const TICK_COUNT = 25;
|
||||||
const labels = [];
|
const labels = [];
|
||||||
for (let i = 0; i <= TICK_COUNT; ++i) {
|
for (let i = 0; i <= TICK_COUNT; ++i) {
|
||||||
labels.push(i.toString());
|
labels.push(i.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function StatsWidget() {
|
export default class StatsWidget extends Component {
|
||||||
const protocol = window.location.protocol === "https:" ? "wss" : "wss"
|
constructor(props) {
|
||||||
const host = window.location.host;
|
super(props);
|
||||||
const port = window.location.port;
|
|
||||||
|
|
||||||
const { sendMessage, lastMessage, readyState } = useWebSocket(protocol + "://"+ host +":" + port + "/gm");
|
this.state = {
|
||||||
|
graphHistory: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(lastProps) {
|
||||||
|
if (this.props.data !== this.props.ignoreData) {
|
||||||
|
if (this.props.serverUptime !== lastProps.serverUptime) {
|
||||||
|
var newGraph = this.state.graphHistory;
|
||||||
|
console.log(this.props);
|
||||||
|
newGraph.push({ tick: this.props.data });
|
||||||
|
|
||||||
|
if (newGraph.length > TICK_COUNT) {
|
||||||
|
newGraph.splice(0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
graphHistory: newGraph,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="graph">
|
||||||
|
<p> { this.props.title } </p>
|
||||||
|
<p> {JSON.stringify(this.state.data)} </p>
|
||||||
|
<Line
|
||||||
|
datasetIdKey="1"
|
||||||
|
data={{
|
||||||
|
labels: this.state.graphHistory.map((gh, i) => this.state.graphHistory.length - i),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: "",
|
||||||
|
data: this.state.graphHistory.map((gh) => gh.tick),
|
||||||
|
borderColor: CHART_COLORS.red,
|
||||||
|
fill: false,
|
||||||
|
cubicInterpolationMode: "monotone",
|
||||||
|
tension: 0.4,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
options={{
|
||||||
|
y: {
|
||||||
|
suggestedMin: this.props.suggestedYMin,
|
||||||
|
suggestedMax: this.props.suggestedYMax,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*export default function StatsWidget() {
|
||||||
const [graphHistory, setGraphHistory] = useState([]);
|
const [graphHistory, setGraphHistory] = useState([]);
|
||||||
|
const [lastMessage, connectionStatus, sendMessage] = useWebsocket();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (lastMessage !== null) {
|
console.log(lastMessage())
|
||||||
const data = JSON.parse(lastMessage.data);
|
if (lastMessage() !== null) {
|
||||||
|
const { data } = JSON.parse(lastMessage().data);
|
||||||
var newGraph = graphHistory;
|
var newGraph = graphHistory;
|
||||||
newGraph.push({ time: new Date().getTime(), tick: data.object});
|
newGraph.push({ time: new Date().getTime(), tick: data.tickTimeElapsed });
|
||||||
if(newGraph.length > TICK_COUNT) {
|
if(newGraph.length > TICK_COUNT) {
|
||||||
newGraph.splice(0, 1);
|
newGraph.splice(0, 1);
|
||||||
}
|
}
|
||||||
setGraphHistory(newGraph);
|
setGraphHistory(newGraph);
|
||||||
}
|
}
|
||||||
}, [lastMessage, setGraphHistory]);
|
}, [graphHistory, lastMessage, setGraphHistory]);
|
||||||
|
|
||||||
/*const connectionStatus = {
|
const connectionStatus = {
|
||||||
[ReadyState.CONNECTING]: "Connecting",
|
[ReadyState.CONNECTING]: "Connecting",
|
||||||
[ReadyState.OPEN]: "Open",
|
[ReadyState.OPEN]: "Open",
|
||||||
[ReadyState.CLOSING]: "Closing",
|
[ReadyState.CLOSING]: "Closing",
|
||||||
[ReadyState.CLOSED]: "Closed",
|
[ReadyState.CLOSED]: "Closed",
|
||||||
[ReadyState.UNINSTANTIATED]: "Uninstantiated",
|
[ReadyState.UNINSTANTIATED]: "Uninstantiated",
|
||||||
}[readyState];*/
|
}[readyState];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="graph">
|
|
||||||
<p> Server Performance (Ticks) </p>
|
|
||||||
<Line
|
|
||||||
datasetIdKey="1"
|
|
||||||
data={{
|
|
||||||
labels,
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: '',
|
|
||||||
data: graphHistory.map((l) => l.tick),
|
|
||||||
borderColor: CHART_COLORS.red,
|
|
||||||
fill: false,
|
|
||||||
cubicInterpolationMode: 'monotone',
|
|
||||||
tension: 0.4
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}}
|
|
||||||
options={{
|
|
||||||
y: {
|
|
||||||
suggestedMin: 995,
|
|
||||||
suggestedMax: 1005
|
|
||||||
}
|
|
||||||
}}/>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}*/
|
||||||
|
61
web-interface/src/Context/WebsocketProvider.js
Normal file
61
web-interface/src/Context/WebsocketProvider.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import React, { useContext, useCallback, useState } from 'react'
|
||||||
|
import { useWebSocket as Websocket, ReadyState } from "react-use-websocket/dist/lib/use-websocket";
|
||||||
|
import { ReactIsInDevelopmentMode } from '../util/util';
|
||||||
|
|
||||||
|
const WebsocketContext = React.createContext();
|
||||||
|
|
||||||
|
const protocol = window.location.protocol === "https:" ? "wss" : "wss"
|
||||||
|
const host = window.location.host;
|
||||||
|
const port = window.location.port;
|
||||||
|
|
||||||
|
function getWebsocketURL() {
|
||||||
|
if(ReactIsInDevelopmentMode) {
|
||||||
|
return "wss://localhost:443/gm";
|
||||||
|
} else {
|
||||||
|
return protocol + "://"+ host +":" + port + "/gm";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useWebsocket() {
|
||||||
|
return useContext(WebsocketContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WebsocketProvider({ children }) {
|
||||||
|
const { sendMessage, lastMessage, readyState } = Websocket(getWebsocketURL());
|
||||||
|
|
||||||
|
const connectionStatus = useCallback(() => {
|
||||||
|
if(readyState === ReadyState.CONNECTING) {
|
||||||
|
return "Connecting";
|
||||||
|
} else if(readyState === ReadyState.OPEN) {
|
||||||
|
return "Open";
|
||||||
|
} else if(readyState === ReadyState.CLOSING) {
|
||||||
|
return "Closing";
|
||||||
|
} else if(readyState === ReadyState.CLOSED) {
|
||||||
|
return "Closed";
|
||||||
|
} else if(readyState === ReadyState.UNINSTANTIATED) {
|
||||||
|
return "Uninstantiated";
|
||||||
|
}
|
||||||
|
}, [readyState]);
|
||||||
|
|
||||||
|
const getLastMessage = useCallback(() => {
|
||||||
|
if(lastMessage != null) {
|
||||||
|
if(lastMessage.data != null) {
|
||||||
|
return JSON.parse(lastMessage.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}, [lastMessage]);
|
||||||
|
|
||||||
|
const send = useCallback((data) => {
|
||||||
|
sendMessage(data, false);
|
||||||
|
}, [sendMessage]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<WebsocketContext.Provider value={{lastMessage: getLastMessage, connectionStatus, sendMessage: send}}>
|
||||||
|
{children}
|
||||||
|
</WebsocketContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WebsocketContext;
|
@ -4,3 +4,8 @@
|
|||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
float: left;
|
||||||
|
width: calc(100vw - 350px);
|
||||||
|
}
|
@ -1,16 +1,46 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from "react";
|
||||||
import Sidepanel from './Components/Dashboard/Sidepanel/Sidepanel'
|
import Sidepanel from "./Components/Dashboard/Sidepanel/Sidepanel";
|
||||||
import StatsWidget from './Components/Dashboard/StatsWidget'
|
import StatsWidget from "./Components/Dashboard/StatsWidget";
|
||||||
|
import WebsocketContext, { useWebsocket } from "./Context/WebsocketProvider";
|
||||||
|
|
||||||
import './Dashboard.css'
|
import "./Dashboard.css";
|
||||||
|
import { bytesToMegabytes } from "./util/util";
|
||||||
|
|
||||||
export default class Dashboard extends Component {
|
export default class Dashboard extends Component {
|
||||||
|
static contextType = WebsocketContext;
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
lastMessage: {
|
||||||
|
serverUptime: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
if (typeof this.context.lastMessage().data !== "undefined") {
|
||||||
|
if (this.context.lastMessage().data.serverUptime !== this.state.lastMessage.serverUptime) {
|
||||||
|
//console.log(this.context.lastMessage().data);
|
||||||
|
this.setState({
|
||||||
|
lastMessage: this.context.lastMessage().data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className='dashboard'>
|
<div className="dashboard">
|
||||||
<Sidepanel />
|
<Sidepanel />
|
||||||
<StatsWidget />
|
<div className="content">
|
||||||
|
<StatsWidget title="Server Performance (Ticks)" serverUptime={this.state.lastMessage.serverUptime} data={typeof this.state.lastMessage.tickTimeElapsed !== "undefined" ? this.state.lastMessage.tickTimeElapsed : -1} ignoreData={-1} suggestedYMin={995} suggestedYMax={1005} />
|
||||||
|
<StatsWidget title="Server Performance (RAM)" serverUptime={this.state.lastMessage.serverUptime} data={typeof this.state.lastMessage.getFreeMemory !== "undefined" ? bytesToMegabytes(this.state.lastMessage.getAllocatedMemory - this.state.lastMessage.getFreeMemory, 2) : -1} ignoreData={-1} suggestedYMin={0} suggestedYMax={bytesToMegabytes(this.state.lastMessage.getAllocatedMemory)} />
|
||||||
|
<StatsWidget title="Online Players" serverUptime={this.state.lastMessage.serverUptime} data={typeof this.state.lastMessage.playerCount !== "undefined" ? this.state.lastMessage.playerCount : -1} ignoreData={-1} suggestedYMin={0} suggestedYMax={10} />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
web-interface/src/util/util.js
Normal file
24
web-interface/src/util/util.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export function ReactIsInDevelopmentMode() {
|
||||||
|
return '_self' in React.createElement('div');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatBytesToString(bytes, decimals = 2) {
|
||||||
|
if (bytes === 0) return '0 Bytes';
|
||||||
|
|
||||||
|
const k = 1024;
|
||||||
|
const dm = decimals < 0 ? 0 : decimals;
|
||||||
|
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||||
|
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function bytesToMegabytes(bytes, decimals = 2) {
|
||||||
|
const dm = decimals < 0 ? 0 : decimals;
|
||||||
|
|
||||||
|
const k = 1024;
|
||||||
|
return parseFloat((bytes / Math.pow(k, 2)).toFixed(dm));
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user