Merge remote-tracking branch 'origin/dev-webconsole' into local-merge
# Conflicts: # bridge-protocol/build.gradle # bridge/src/main/java/asys/bridge/bukkit/BridgePlugin.java # bridge/src/main/java/asys/bridge/client/ClientPacketHandler.java # mcserver-manager/build.gradle # mcserver-manager/src/main/java/asys/mcsmanager/server/ServerPacketHandler.java
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
group = 'asys'
|
||||
version = '0.3-SNAPSHOT'
|
||||
version = '0.4-SNAPSHOT'
|
||||
|
||||
task jar(type: Jar, overwrite: true) {
|
||||
// не собирать jar
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* DmitriyMX <d.mihailov@samson-rus.com>
|
||||
* 2017-06-05
|
||||
*/
|
||||
package asys.mcsmanager.packets;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class SC_Command extends Packet {
|
||||
private String command;
|
||||
|
||||
public SC_Command() {
|
||||
}
|
||||
|
||||
public SC_Command(String command) {
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
public String getCommand() {
|
||||
return command;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSelfData(ByteBuf buffer) {
|
||||
command = readString(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSelfData(ByteBuf buffer) {
|
||||
writeString(buffer, command);
|
||||
}
|
||||
}
|
||||
@@ -19,12 +19,15 @@ public class PacketDecoder extends ReplayingDecoder<Packet> {
|
||||
protected void decode(ChannelHandlerContext contect, ByteBuf inBuf, List<Object> out) throws Exception {
|
||||
int id = inBuf.readUnsignedByte();
|
||||
Class<? extends Packet> pktClass = contect.channel().attr(KNOWN_PACKETS).get().get(id);
|
||||
if (pktClass == null) return;
|
||||
if (pktClass == null) return; //TODO надо бы в логгере писать про отсутствующий пакет
|
||||
|
||||
if (contect.channel().attr(KNOWN_HANDLERS).get().containsKey(pktClass)) {
|
||||
Packet packet = pktClass.newInstance();
|
||||
packet.readSelfData(inBuf);
|
||||
out.add(packet);
|
||||
} else {
|
||||
//TODO по хорошему, надо информровать, что отсутствует обработчик пакета
|
||||
inBuf.skipBytes(inBuf.readableBytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,23 +13,17 @@ import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class BridgePlugin extends JavaPlugin {
|
||||
public static BridgePlugin INSTANCE;
|
||||
private String appConnect = "ASys";
|
||||
private Client client;
|
||||
private ScheduledExecutorService ses;
|
||||
private ScheduledFuture<?> sesFuture, sesPingFuture;
|
||||
private TaskTicker connectTicker, pingTicker;
|
||||
private int tryConnect = 0;
|
||||
private BridgeLoggerAppender loggerAppender;
|
||||
private boolean needReconnect = true;
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
saveDefaultConfig();
|
||||
if (getConfig().getInt("mode") == 1) {
|
||||
((Logger)LogManager.getRootLogger()).addAppender(loggerAppender = new BridgeLoggerAppender());
|
||||
} else {
|
||||
@@ -42,16 +36,16 @@ public class BridgePlugin extends JavaPlugin {
|
||||
public void onEnable() {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = this;
|
||||
saveDefaultConfig();
|
||||
|
||||
startReconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (sesFuture != null) {
|
||||
sesFuture.cancel(false);
|
||||
}
|
||||
|
||||
setNeedReconnect(false);
|
||||
stopReconnect();
|
||||
stopPing();
|
||||
|
||||
if (client.isConnected()) {
|
||||
@@ -70,8 +64,8 @@ public class BridgePlugin extends JavaPlugin {
|
||||
|
||||
public void startReconnect() {
|
||||
client = new Client();
|
||||
ses = Executors.newScheduledThreadPool(2);
|
||||
sesFuture = ses.scheduleAtFixedRate(() -> {
|
||||
connectTicker = new TaskTicker().setStepTimeMs(5000L);
|
||||
connectTicker.setTask(() -> {
|
||||
getLogger().info(String.format("Connect(%d) to %s...", ++tryConnect, appConnect));
|
||||
if (getConfig().getInt("mode") == 1) {
|
||||
client.connect(getConfig().getString("host"), getConfig().getInt("port"));
|
||||
@@ -84,13 +78,12 @@ public class BridgePlugin extends JavaPlugin {
|
||||
getLogger().warning(
|
||||
String.format("Connection(%d) fail. Try reconnect...", tryConnect));
|
||||
}
|
||||
}, 0L, 5L, TimeUnit.SECONDS);
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void stopReconnect() {
|
||||
if (sesFuture != null) {
|
||||
sesFuture.cancel(false);
|
||||
sesFuture = null;
|
||||
if (connectTicker != null) {
|
||||
connectTicker.stop();
|
||||
tryConnect = 0;
|
||||
}
|
||||
}
|
||||
@@ -99,7 +92,8 @@ public class BridgePlugin extends JavaPlugin {
|
||||
if (getConfig().getInt("mode") == 1) {
|
||||
getLoggerAppender().setChannel(channel);
|
||||
}
|
||||
sesPingFuture = ses.scheduleAtFixedRate(() -> {
|
||||
pingTicker = new TaskTicker().setStepTimeMs(5000L);
|
||||
pingTicker.setTask(() -> {
|
||||
channel.write(new CS_Ping(
|
||||
System.currentTimeMillis(),
|
||||
20.0D, //FIXME
|
||||
@@ -115,19 +109,27 @@ public class BridgePlugin extends JavaPlugin {
|
||||
getLogger().warning("Try reconnect...");
|
||||
startReconnect();
|
||||
}
|
||||
}, 0L, 5L, TimeUnit.SECONDS);
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void stopPing() {
|
||||
if (getConfig().getInt("mode") == 1) {
|
||||
getLoggerAppender().setChannel(null);
|
||||
}
|
||||
if (sesPingFuture != null) {
|
||||
sesPingFuture.cancel(false);
|
||||
if (pingTicker != null) {
|
||||
pingTicker.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private BridgeLoggerAppender getLoggerAppender() {
|
||||
public BridgeLoggerAppender getLoggerAppender() {
|
||||
return loggerAppender;
|
||||
}
|
||||
|
||||
public boolean isNeedReconnect() {
|
||||
return needReconnect;
|
||||
}
|
||||
|
||||
public void setNeedReconnect(boolean needReconnect) {
|
||||
this.needReconnect = needReconnect;
|
||||
}
|
||||
}
|
||||
|
||||
48
bridge/src/main/java/asys/bridge/bukkit/TaskTicker.java
Normal file
48
bridge/src/main/java/asys/bridge/bukkit/TaskTicker.java
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* DmitriyMX <d.mihailov@samson-rus.com>
|
||||
* 2017-05-18
|
||||
*/
|
||||
package asys.bridge.bukkit;
|
||||
|
||||
public class TaskTicker implements Runnable {
|
||||
private Runnable task;
|
||||
private long stepTimeMs = 1000L;
|
||||
private Thread thread;
|
||||
private boolean loop = false;
|
||||
|
||||
TaskTicker setTask(Runnable task) {
|
||||
this.task = task;
|
||||
return this;
|
||||
}
|
||||
|
||||
TaskTicker setStepTimeMs(long stepTimeMs) {
|
||||
this.stepTimeMs = stepTimeMs;
|
||||
return this;
|
||||
}
|
||||
|
||||
void start() {
|
||||
thread = new Thread(this, "TaskTicker");
|
||||
loop = true;
|
||||
thread.start();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
loop = false;
|
||||
if (thread != null) {
|
||||
thread.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (loop || !Thread.currentThread().isInterrupted()) {
|
||||
task.run();
|
||||
|
||||
try {
|
||||
Thread.sleep(stepTimeMs);
|
||||
} catch (InterruptedException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,13 +26,15 @@ public class ClientPacketHandler extends ChannelInboundHandlerAdapter implements
|
||||
|
||||
private static final BiMap<Integer, Class<? extends Packet>> pingPackets = ImmutableBiMap.of(
|
||||
3, CS_Ping.class,
|
||||
4, CS_ConsoleMessage.class
|
||||
4, CS_ConsoleMessage.class,
|
||||
5, SC_Command.class
|
||||
);
|
||||
|
||||
ClientPacketHandler() {
|
||||
if (handshakeHandlers == null) {
|
||||
handshakeHandlers = ImmutableMap.of(
|
||||
SC_HandshakeResult.class, this
|
||||
SC_HandshakeResult.class, this,
|
||||
SC_Command.class, this
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -61,11 +63,12 @@ public class ClientPacketHandler extends ChannelInboundHandlerAdapter implements
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext context) throws Exception {
|
||||
if (BridgePlugin.INSTANCE != null) {
|
||||
BridgePlugin.INSTANCE.getLogger().warning("Lost connection!");
|
||||
BridgePlugin.INSTANCE.stopPing();
|
||||
BridgePlugin.INSTANCE.getLogger().warning("Try reconnect...");
|
||||
if (BridgePlugin.INSTANCE.isNeedReconnect()) {
|
||||
BridgePlugin.INSTANCE.getLogger().warning("Lost connection! Try reconnect...");
|
||||
BridgePlugin.INSTANCE.startReconnect();
|
||||
}
|
||||
}
|
||||
context.channel().attr(KNOWN_PACKETS).remove();
|
||||
context.channel().attr(KNOWN_HANDLERS).remove();
|
||||
super.channelInactive(context);
|
||||
@@ -74,16 +77,32 @@ public class ClientPacketHandler extends ChannelInboundHandlerAdapter implements
|
||||
@Override
|
||||
public void handle(Packet packet, ChannelHandlerContext context) {
|
||||
BridgePlugin.INSTANCE.getLogger().info("handle : " + packet.getClass().getSimpleName());
|
||||
if (packet instanceof SC_HandshakeResult) {
|
||||
if (BridgePlugin.INSTANCE.getConfig().getInt("mode") == 1) {
|
||||
SC_HandshakeResult pkt = (SC_HandshakeResult) packet;
|
||||
if (pkt.getErrorCode() != 0) {
|
||||
handleHandshakeResult((SC_HandshakeResult) packet, context);
|
||||
}
|
||||
} else if (packet instanceof SC_Command) {
|
||||
handleCommand((SC_Command) packet);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleHandshakeResult(SC_HandshakeResult packet, ChannelHandlerContext context) {
|
||||
if (packet.getErrorCode() != 0) {
|
||||
BridgePlugin.INSTANCE.getLogger().severe(
|
||||
String.format("Handshake: #%d %s", pkt.getErrorCode(), pkt.getMessage()));
|
||||
String.format("Handshake: #%d %s", packet.getErrorCode(), packet.getMessage()));
|
||||
BridgePlugin.INSTANCE.setNeedReconnect(false);
|
||||
} else {
|
||||
context.channel().attr(KNOWN_PACKETS).set(pingPackets);
|
||||
BridgePlugin.INSTANCE.getLogger().info("Handshake: OK");
|
||||
BridgePlugin.INSTANCE.startPing(context.channel());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCommand(SC_Command packet) {
|
||||
BridgePlugin.INSTANCE.getLogger().info("Command: " + packet.getCommand());
|
||||
BridgePlugin.INSTANCE.getServer().dispatchCommand(
|
||||
BridgePlugin.INSTANCE.getServer().getConsoleSender(),
|
||||
packet.getCommand()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
group = 'asys'
|
||||
version = '0.8.7-SNAPSHOT'
|
||||
version = '0.8.13-SNAPSHOT'
|
||||
|
||||
apply plugin: 'osgi'
|
||||
|
||||
@@ -16,6 +16,7 @@ jar {
|
||||
'!asys.mcsmanager.packets.*',
|
||||
'io.netty.buffer;version="[4.0,5)"',
|
||||
'io.netty.handler.codec;version="[4.0,5)"',
|
||||
'io.netty.handler.codec.http;version="[4.0,5)"',
|
||||
'*'
|
||||
}
|
||||
|
||||
@@ -32,4 +33,5 @@ dependencies {
|
||||
compile project(':webinterface')
|
||||
include files(project(':bridge-protocol').sourceSets.main.output.classesDir)
|
||||
compile group: 'io.netty', name: 'netty-codec', version: nettyVersion
|
||||
compile group: 'io.netty', name: 'netty-codec-http', version: nettyVersion
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
package asys.mcsmanager;
|
||||
|
||||
import asys.api.Config;
|
||||
import asys.mcsmanager.server.Server;
|
||||
import asys.webinterface.api.Webinterface;
|
||||
import org.osgi.framework.BundleActivator;
|
||||
import org.osgi.framework.BundleContext;
|
||||
@@ -20,7 +19,8 @@ public class Activator implements BundleActivator, ServiceListener {
|
||||
private ServiceTracker<?, Webinterface> serviceTracker;
|
||||
private MCSM_WebModule module;
|
||||
private Webinterface webinterface;
|
||||
private Server serverManager;
|
||||
private asys.mcsmanager.server.Server serverManager;
|
||||
private asys.mcsmanager.websocket.Server webconsoleServer;
|
||||
|
||||
@Override
|
||||
public void start(BundleContext context) throws Exception {
|
||||
@@ -43,13 +43,21 @@ public class Activator implements BundleActivator, ServiceListener {
|
||||
int port = config.getInt("asys.mcsmanager.port", 8779);
|
||||
String passcode = config.getString("asys.mcsmanager.passcode", "testpasscode");
|
||||
logger.debug("Start server manager: {}:{}", host, port);
|
||||
serverManager = new Server();
|
||||
serverManager = new asys.mcsmanager.server.Server();
|
||||
serverManager.start(host, port, passcode, manager);
|
||||
|
||||
host = config.getString("asys.mcsmanager.webconsole.host", "127.0.0.1");
|
||||
port = config.getInt("asys.mcsmanager.webconsole.port", 8770);
|
||||
logger.debug("Start webconsole server: {}:{}", host, port);
|
||||
webconsoleServer = new asys.mcsmanager.websocket.Server();
|
||||
webconsoleServer.start(host, port, manager);
|
||||
|
||||
serviceConfigTracker.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop(BundleContext context) throws Exception {
|
||||
webconsoleServer.shutdown();
|
||||
serverManager.shutdown();
|
||||
|
||||
if (webinterface != null) {
|
||||
|
||||
@@ -26,6 +26,7 @@ public class MCSM_WebModule extends WebModule {
|
||||
private final String MODULE_NAME = "mcsmanager";
|
||||
private final String MODULE_URL = "/"+MODULE_NAME;
|
||||
private final Pattern URL_PATTERN_JS = Pattern.compile(MODULE_URL+"/(\\w+)\\.js");
|
||||
private final Pattern URL_PATTERN_CSS = Pattern.compile(MODULE_URL+"/(\\w+)\\.css");
|
||||
private Manager manager;
|
||||
|
||||
MCSM_WebModule(Manager manager) {
|
||||
@@ -66,6 +67,19 @@ public class MCSM_WebModule extends WebModule {
|
||||
this.sendContent(httpExchange, 0, stream);
|
||||
return true;
|
||||
}
|
||||
|
||||
//FIXME дублирование кода
|
||||
matcher = URL_PATTERN_CSS.matcher(urlPath);
|
||||
if (matcher.find()) {
|
||||
InputStream stream = getClass().getResourceAsStream("/" + matcher.group(1) + ".css");
|
||||
if (stream == null) {
|
||||
this.sendHttpCode(httpExchange, 404, "not found");
|
||||
return true;
|
||||
}
|
||||
httpExchange.getResponseHeaders().add("Content-Type", "text/css;charset=utf-8");
|
||||
this.sendContent(httpExchange, 0, stream);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -4,7 +4,11 @@
|
||||
*/
|
||||
package asys.mcsmanager;
|
||||
|
||||
import asys.mcsmanager.packets.CS_ConsoleMessage;
|
||||
import asys.mcsmanager.packets.CS_Ping;
|
||||
import asys.mcsmanager.packets.SC_Command;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -13,19 +17,43 @@ import java.util.*;
|
||||
public class Manager {
|
||||
private Logger logger = LoggerFactory.getLogger(Manager.class);
|
||||
private Map<String, ServerInfo> serversMap = new HashMap<>();
|
||||
private Map<String, Channel> serverChannels = new HashMap<>();
|
||||
private List<Channel> webconsoleListener = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Добавляем в список ClientID
|
||||
* @param clientId id сервера. Чувствителен к регистру
|
||||
* @return <code>false</code>, если сервер с таким id уже имеется
|
||||
*/
|
||||
public boolean addClientId(String clientId) {
|
||||
public boolean addClientId(String clientId, Channel channel) {
|
||||
if (serversMap.containsKey(clientId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.debug("addClientId: {}", clientId);
|
||||
serversMap.put(clientId, new ServerInfo(clientId));
|
||||
serverChannels.put(clientId, channel);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void removeClientId(String clientId) {
|
||||
if (clientId != null) {
|
||||
serverChannels.remove(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean sendCommand(String clientId, String command) {
|
||||
/*
|
||||
if (serverChannels.containsKey(clientId)) {
|
||||
serverChannels.get(clientId).writeAndFlush(new SC_Command(command));
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
//FIXME временный костыль
|
||||
Channel channel = serverChannels.entrySet().iterator().next().getValue();
|
||||
channel.writeAndFlush(new SC_Command(command));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -47,4 +75,22 @@ public class Manager {
|
||||
public ServerInfo getInfo(String clientId) {
|
||||
return serversMap.get(clientId);
|
||||
}
|
||||
|
||||
public void addWebConsoleListener(Channel channel) {
|
||||
this.webconsoleListener.add(channel);
|
||||
}
|
||||
|
||||
public void removeWebConsoleListener(Channel channel) {
|
||||
this.webconsoleListener.remove(channel);
|
||||
}
|
||||
|
||||
public void sendBroadcastToWebConsoleListeners(CS_ConsoleMessage message) {
|
||||
for (Channel channel : this.webconsoleListener) {
|
||||
channel.writeAndFlush(new TextWebSocketFrame(String.format(
|
||||
"[L:%d] %s",
|
||||
message.getLevel(),
|
||||
message.getMessage()
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@ class ServerPacketHandler extends ChannelInboundHandlerAdapter implements IPacke
|
||||
|
||||
private static final BiMap<Integer, Class<? extends Packet>> pingPackets = ImmutableBiMap.of(
|
||||
3, CS_Ping.class,
|
||||
4, CS_ConsoleMessage.class
|
||||
4, CS_ConsoleMessage.class,
|
||||
5, SC_Command.class
|
||||
);
|
||||
private static Map<Class<? extends Packet>, IPacketHandler> pingHandlers;
|
||||
|
||||
@@ -56,6 +57,12 @@ class ServerPacketHandler extends ChannelInboundHandlerAdapter implements IPacke
|
||||
super.channelActive(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext context) throws Exception {
|
||||
manager.removeClientId(context.channel().attr(CLIENTID).get());
|
||||
super.channelInactive(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Packet packet, ChannelHandlerContext context) {
|
||||
if (packet.getClass() == CS_Handshake.class) {
|
||||
@@ -77,7 +84,7 @@ class ServerPacketHandler extends ChannelInboundHandlerAdapter implements IPacke
|
||||
return;
|
||||
}
|
||||
|
||||
if (!manager.addClientId(packet.getClientId())) {
|
||||
if (!manager.addClientId(packet.getClientId(), context.channel())) {
|
||||
try {
|
||||
context.channel().writeAndFlush(HandshakeResult.CLIENTID_EXISTS).sync().channel().close();
|
||||
} catch (InterruptedException ignore) {
|
||||
@@ -98,6 +105,6 @@ class ServerPacketHandler extends ChannelInboundHandlerAdapter implements IPacke
|
||||
}
|
||||
|
||||
private void handleCSConsoleMessage(CS_ConsoleMessage packet) {
|
||||
LoggerFactory.getLogger(getClass()).debug("[L:{}] {}", packet.getLevel(), packet.getMessage());
|
||||
manager.sendBroadcastToWebConsoleListeners(packet);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* DmitriyMX <dimon550@gmail.com>
|
||||
* 2017-05-09
|
||||
*/
|
||||
package asys.mcsmanager.websocket;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static asys.mcsmanager.websocket.Server.manager;
|
||||
|
||||
public class FrameHandler extends SimpleChannelInboundHandler<WebSocketFrame> {
|
||||
private final Logger logger = LoggerFactory.getLogger(FrameHandler.class);
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
manager.addWebConsoleListener(ctx.channel());
|
||||
super.channelActive(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
manager.removeWebConsoleListener(ctx.channel());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception {
|
||||
if (frame instanceof TextWebSocketFrame) {
|
||||
String requestText = ((TextWebSocketFrame)frame).text();
|
||||
if (requestText.startsWith(":")) {
|
||||
//FIXME убрать костыли
|
||||
manager.sendCommand(null, requestText.substring(1));
|
||||
}
|
||||
} else {
|
||||
logger.warn("unsupport frame type: {}", frame.getClass().getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* DmitriyMX <dimon550@gmail.com>
|
||||
* 2017-05-08
|
||||
*/
|
||||
package asys.mcsmanager.websocket;
|
||||
|
||||
import asys.mcsmanager.Manager;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||
import io.netty.handler.codec.http.HttpServerCodec;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
|
||||
|
||||
public class Server {
|
||||
static Manager manager;
|
||||
private EventLoopGroup bossGroup, workerGroup;
|
||||
|
||||
public void start(String host, int port, Manager manager) {
|
||||
Server.manager = manager;
|
||||
bossGroup = new NioEventLoopGroup(1);
|
||||
workerGroup = new NioEventLoopGroup();
|
||||
|
||||
ServerBootstrap serverBootstrap = createServerBootstrap();
|
||||
serverBootstrap.bind(host, port);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
workerGroup.shutdownGracefully();
|
||||
bossGroup.shutdownGracefully();
|
||||
}
|
||||
|
||||
private ServerBootstrap createServerBootstrap() {
|
||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||
|
||||
bootstrap.group(bossGroup, workerGroup)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.childHandler(createChannelInitializer());
|
||||
|
||||
return bootstrap;
|
||||
}
|
||||
|
||||
private ChannelInitializer createChannelInitializer() {
|
||||
return new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(SocketChannel socketChannel) throws Exception {
|
||||
socketChannel.pipeline().addLast(
|
||||
new HttpServerCodec(),
|
||||
new HttpObjectAggregator(65536),
|
||||
new WebSocketServerProtocolHandler("/", null, true),
|
||||
new FrameHandler()
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
333
mcserver-manager/src/main/resources/ansi_up.js
Normal file
333
mcserver-manager/src/main/resources/ansi_up.js
Normal file
@@ -0,0 +1,333 @@
|
||||
/* ansi_up.js
|
||||
* author : Dru Nelson
|
||||
* license : MIT
|
||||
* http://github.com/drudru/ansi_up
|
||||
*/
|
||||
(function (factory) {
|
||||
var v;
|
||||
if (typeof module === "object" && typeof module.exports === "object") {
|
||||
v = factory(require, exports);
|
||||
if ("undefined" !== typeof v) module.exports = v;
|
||||
}
|
||||
else if ("function" === typeof define && define.amd) {
|
||||
define(["require", "exports"], factory);
|
||||
}
|
||||
else {
|
||||
var req, exp = {};
|
||||
v = factory(req, exp);
|
||||
window.AnsiUp = exp.default;
|
||||
}
|
||||
})(function (require, exports) {
|
||||
|
||||
"use strict";
|
||||
function rgx(tmplObj) {
|
||||
var subst = [];
|
||||
for (var _i = 1; _i < arguments.length; _i++) {
|
||||
subst[_i - 1] = arguments[_i];
|
||||
}
|
||||
var regexText = tmplObj.raw[0];
|
||||
var wsrgx = /^\s+|\s+\n|\s+#[\s\S]+?\n/gm;
|
||||
var txt2 = regexText.replace(wsrgx, '');
|
||||
return new RegExp(txt2, 'm');
|
||||
}
|
||||
var AnsiUp = (function () {
|
||||
function AnsiUp() {
|
||||
this.VERSION = "2.0.0";
|
||||
this.ansi_colors = [
|
||||
[
|
||||
{ rgb: [0, 0, 0], class_name: "ansi-black" },
|
||||
{ rgb: [187, 0, 0], class_name: "ansi-red" },
|
||||
{ rgb: [0, 187, 0], class_name: "ansi-green" },
|
||||
{ rgb: [187, 187, 0], class_name: "ansi-yellow" },
|
||||
{ rgb: [0, 0, 187], class_name: "ansi-blue" },
|
||||
{ rgb: [187, 0, 187], class_name: "ansi-magenta" },
|
||||
{ rgb: [0, 187, 187], class_name: "ansi-cyan" },
|
||||
{ rgb: [255, 255, 255], class_name: "ansi-white" }
|
||||
],
|
||||
[
|
||||
{ rgb: [85, 85, 85], class_name: "ansi-bright-black" },
|
||||
{ rgb: [255, 85, 85], class_name: "ansi-bright-red" },
|
||||
{ rgb: [0, 255, 0], class_name: "ansi-bright-green" },
|
||||
{ rgb: [255, 255, 85], class_name: "ansi-bright-yellow" },
|
||||
{ rgb: [85, 85, 255], class_name: "ansi-bright-blue" },
|
||||
{ rgb: [255, 85, 255], class_name: "ansi-bright-magenta" },
|
||||
{ rgb: [85, 255, 255], class_name: "ansi-bright-cyan" },
|
||||
{ rgb: [255, 255, 255], class_name: "ansi-bright-white" }
|
||||
]
|
||||
];
|
||||
this.htmlFormatter = {
|
||||
transform: function (fragment, instance) {
|
||||
var txt = fragment.text;
|
||||
if (txt.length === 0)
|
||||
return txt;
|
||||
if (instance._escape_for_html)
|
||||
txt = instance.old_escape_for_html(txt);
|
||||
if (!fragment.bright && fragment.fg === null && fragment.bg === null)
|
||||
return txt;
|
||||
var styles = [];
|
||||
var classes = [];
|
||||
var fg = fragment.fg;
|
||||
var bg = fragment.bg;
|
||||
if (fg === null && fragment.bright)
|
||||
fg = instance.ansi_colors[1][7];
|
||||
if (!instance._use_classes) {
|
||||
if (fg)
|
||||
styles.push("color:rgb(" + fg.rgb.join(',') + ")");
|
||||
if (bg)
|
||||
styles.push("background-color:rgb(" + bg.rgb + ")");
|
||||
}
|
||||
else {
|
||||
if (fg) {
|
||||
if (fg.class_name !== 'truecolor') {
|
||||
classes.push(fg.class_name + "-fg");
|
||||
}
|
||||
else {
|
||||
styles.push("color:rgb(" + fg.rgb.join(',') + ")");
|
||||
}
|
||||
}
|
||||
if (bg) {
|
||||
if (bg.class_name !== 'truecolor') {
|
||||
classes.push(bg.class_name + "-bg");
|
||||
}
|
||||
else {
|
||||
styles.push("background-color:rgb(" + bg.rgb.join(',') + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
var class_string = '';
|
||||
var style_string = '';
|
||||
if (classes.length)
|
||||
class_string = " class=\"" + classes.join(' ') + "\"";
|
||||
if (styles.length)
|
||||
style_string = " style=\"" + styles.join(';') + "\"";
|
||||
return "<span" + class_string + style_string + ">" + txt + "</span>";
|
||||
},
|
||||
compose: function (segments, instance) {
|
||||
return segments.join("");
|
||||
}
|
||||
};
|
||||
this.textFormatter = {
|
||||
transform: function (fragment, instance) {
|
||||
return fragment.text;
|
||||
},
|
||||
compose: function (segments, instance) {
|
||||
return segments.join("");
|
||||
}
|
||||
};
|
||||
this.setup_256_palette();
|
||||
this._use_classes = false;
|
||||
this._escape_for_html = true;
|
||||
this.bright = false;
|
||||
this.fg = this.bg = null;
|
||||
this._buffer = '';
|
||||
}
|
||||
Object.defineProperty(AnsiUp.prototype, "use_classes", {
|
||||
get: function () {
|
||||
return this._use_classes;
|
||||
},
|
||||
set: function (arg) {
|
||||
this._use_classes = arg;
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(AnsiUp.prototype, "escape_for_html", {
|
||||
get: function () {
|
||||
return this._escape_for_html;
|
||||
},
|
||||
set: function (arg) {
|
||||
this._escape_for_html = arg;
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
AnsiUp.prototype.setup_256_palette = function () {
|
||||
var _this = this;
|
||||
this.palette_256 = [];
|
||||
this.ansi_colors.forEach(function (palette) {
|
||||
palette.forEach(function (rec) {
|
||||
_this.palette_256.push(rec);
|
||||
});
|
||||
});
|
||||
var levels = [0, 95, 135, 175, 215, 255];
|
||||
for (var r = 0; r < 6; ++r) {
|
||||
for (var g = 0; g < 6; ++g) {
|
||||
for (var b = 0; b < 6; ++b) {
|
||||
var col = { rgb: [levels[r], levels[g], levels[b]], class_name: 'truecolor' };
|
||||
this.palette_256.push(col);
|
||||
}
|
||||
}
|
||||
}
|
||||
var grey_level = 8;
|
||||
for (var i = 0; i < 24; ++i, grey_level += 10) {
|
||||
var gry = { rgb: [grey_level, grey_level, grey_level], class_name: 'truecolor' };
|
||||
this.palette_256.push(gry);
|
||||
}
|
||||
};
|
||||
AnsiUp.prototype.old_escape_for_html = function (txt) {
|
||||
return txt.replace(/[&<>]/gm, function (str) {
|
||||
if (str === "&")
|
||||
return "&";
|
||||
if (str === "<")
|
||||
return "<";
|
||||
if (str === ">")
|
||||
return ">";
|
||||
});
|
||||
};
|
||||
AnsiUp.prototype.old_linkify = function (txt) {
|
||||
return txt.replace(/(https?:\/\/[^\s]+)/gm, function (str) {
|
||||
return "<a href=\"" + str + "\">" + str + "</a>";
|
||||
});
|
||||
};
|
||||
AnsiUp.prototype.detect_incomplete_ansi = function (txt) {
|
||||
return !(/.*?[\x40-\x7e]/.test(txt));
|
||||
};
|
||||
AnsiUp.prototype.detect_incomplete_link = function (txt) {
|
||||
var found = false;
|
||||
for (var i = txt.length - 1; i > 0; i--) {
|
||||
if (/\s|\x1B/.test(txt[i])) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
if (/(https?:\/\/[^\s]+)/.test(txt))
|
||||
return 0;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
var prefix = txt.substr(i + 1, 4);
|
||||
if (prefix.length === 0)
|
||||
return -1;
|
||||
if ("http".indexOf(prefix) === 0)
|
||||
return (i + 1);
|
||||
};
|
||||
AnsiUp.prototype.ansi_to = function (txt, formatter) {
|
||||
var pkt = this._buffer + txt;
|
||||
this._buffer = '';
|
||||
var raw_text_pkts = pkt.split(/\x1B\[/);
|
||||
if (raw_text_pkts.length === 1)
|
||||
raw_text_pkts.push('');
|
||||
this.handle_incomplete_sequences(raw_text_pkts);
|
||||
var first_chunk = this.with_state(raw_text_pkts.shift());
|
||||
var blocks = new Array(raw_text_pkts.length);
|
||||
for (var i = 0, len = raw_text_pkts.length; i < len; ++i) {
|
||||
blocks[i] = (formatter.transform(this.process_ansi(raw_text_pkts[i]), this));
|
||||
}
|
||||
if (first_chunk.text.length > 0)
|
||||
blocks.unshift(formatter.transform(first_chunk, this));
|
||||
return formatter.compose(blocks, this);
|
||||
};
|
||||
AnsiUp.prototype.ansi_to_html = function (txt) {
|
||||
return this.ansi_to(txt, this.htmlFormatter);
|
||||
};
|
||||
AnsiUp.prototype.ansi_to_text = function (txt) {
|
||||
return this.ansi_to(txt, this.textFormatter);
|
||||
};
|
||||
AnsiUp.prototype.with_state = function (text) {
|
||||
return { bright: this.bright, fg: this.fg, bg: this.bg, text: text };
|
||||
};
|
||||
AnsiUp.prototype.handle_incomplete_sequences = function (chunks) {
|
||||
var last_chunk = chunks[chunks.length - 1];
|
||||
if ((last_chunk.length > 0) && this.detect_incomplete_ansi(last_chunk)) {
|
||||
this._buffer = "\x1B[" + last_chunk;
|
||||
chunks.pop();
|
||||
chunks.push('');
|
||||
}
|
||||
else {
|
||||
if (last_chunk.slice(-1) === "\x1B") {
|
||||
this._buffer = "\x1B";
|
||||
console.log("raw", chunks);
|
||||
chunks.pop();
|
||||
chunks.push(last_chunk.substr(0, last_chunk.length - 1));
|
||||
console.log(chunks);
|
||||
console.log(last_chunk);
|
||||
}
|
||||
if (chunks.length === 2 &&
|
||||
chunks[1] === "" &&
|
||||
chunks[0].slice(-1) === "\x1B") {
|
||||
this._buffer = "\x1B";
|
||||
last_chunk = chunks.shift();
|
||||
chunks.unshift(last_chunk.substr(0, last_chunk.length - 1));
|
||||
}
|
||||
}
|
||||
};
|
||||
AnsiUp.prototype.process_ansi = function (block) {
|
||||
if (!this._sgr_regex) {
|
||||
this._sgr_regex = (_a = ["\n ^ # beginning of line\n ([!<-?]?) # a private-mode char (!, <, =, >, ?)\n ([d;]*) # any digits or semicolons\n ([ -/]? # an intermediate modifier\n [@-~]) # the command\n ([sS]*) # any text following this CSI sequence\n "], _a.raw = ["\n ^ # beginning of line\n ([!\\x3c-\\x3f]?) # a private-mode char (!, <, =, >, ?)\n ([\\d;]*) # any digits or semicolons\n ([\\x20-\\x2f]? # an intermediate modifier\n [\\x40-\\x7e]) # the command\n ([\\s\\S]*) # any text following this CSI sequence\n "], rgx(_a));
|
||||
}
|
||||
var matches = block.match(this._sgr_regex);
|
||||
if (!matches) {
|
||||
return this.with_state(block);
|
||||
}
|
||||
var orig_txt = matches[4];
|
||||
if (matches[1] !== '' || matches[3] !== 'm') {
|
||||
return this.with_state(orig_txt);
|
||||
}
|
||||
var sgr_cmds = matches[2].split(';');
|
||||
while (sgr_cmds.length > 0) {
|
||||
var sgr_cmd_str = sgr_cmds.shift();
|
||||
var num = parseInt(sgr_cmd_str, 10);
|
||||
if (isNaN(num) || num === 0) {
|
||||
this.fg = this.bg = null;
|
||||
this.bright = false;
|
||||
}
|
||||
else if (num === 1) {
|
||||
this.bright = true;
|
||||
}
|
||||
else if (num === 39) {
|
||||
this.fg = null;
|
||||
}
|
||||
else if (num === 49) {
|
||||
this.bg = null;
|
||||
}
|
||||
else if ((num >= 30) && (num < 38)) {
|
||||
var bidx = this.bright ? 1 : 0;
|
||||
this.fg = this.ansi_colors[bidx][(num - 30)];
|
||||
}
|
||||
else if ((num >= 90) && (num < 98)) {
|
||||
this.fg = this.ansi_colors[1][(num - 90)];
|
||||
}
|
||||
else if ((num >= 40) && (num < 48)) {
|
||||
this.bg = this.ansi_colors[0][(num - 40)];
|
||||
}
|
||||
else if ((num >= 100) && (num < 108)) {
|
||||
this.bg = this.ansi_colors[1][(num - 100)];
|
||||
}
|
||||
else if (num === 38 || num === 48) {
|
||||
if (sgr_cmds.length > 0) {
|
||||
var is_foreground = (num === 38);
|
||||
var mode_cmd = sgr_cmds.shift();
|
||||
if (mode_cmd === '5' && sgr_cmds.length > 0) {
|
||||
var palette_index = parseInt(sgr_cmds.shift(), 10);
|
||||
if (palette_index >= 0 && palette_index <= 255) {
|
||||
if (is_foreground)
|
||||
this.fg = this.palette_256[palette_index];
|
||||
else
|
||||
this.bg = this.palette_256[palette_index];
|
||||
}
|
||||
}
|
||||
if (mode_cmd === '2' && sgr_cmds.length > 2) {
|
||||
var r = parseInt(sgr_cmds.shift(), 10);
|
||||
var g = parseInt(sgr_cmds.shift(), 10);
|
||||
var b = parseInt(sgr_cmds.shift(), 10);
|
||||
if ((r >= 0 && r <= 255) && (g >= 0 && g <= 255) && (b >= 0 && b <= 255)) {
|
||||
var c = { rgb: [r, g, b], class_name: 'truecolor' };
|
||||
if (is_foreground)
|
||||
this.fg = c;
|
||||
else
|
||||
this.bg = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.with_state(orig_txt);
|
||||
var _a;
|
||||
};
|
||||
return AnsiUp;
|
||||
}());
|
||||
//# sourceMappingURL=ansi_up.js.map
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.default = AnsiUp;
|
||||
});
|
||||
@@ -29,10 +29,177 @@ var NvLineChart = React.createClass({
|
||||
this.d3ChartData.datum(this.props.datum);
|
||||
this.d3ChartData.transition().duration(500).call(this.chart);
|
||||
nv.utils.windowResize(this.chart.update);
|
||||
},
|
||||
componentWillUnmount: function() {
|
||||
var nvtooltip = document.querySelector('div[class~="nvtooltip"]');
|
||||
if (nvtooltip !== null) {
|
||||
nvtooltip.parentElement.removeChild(nvtooltip)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var Tabs = React.createClass({
|
||||
onTabClick: function(idx){
|
||||
this.setState({ activeTab: idx });
|
||||
},
|
||||
/*--------------------*/
|
||||
getInitialState: function(){return{
|
||||
activeTab: 0
|
||||
}},
|
||||
render: function(){
|
||||
var _this = this;
|
||||
|
||||
var tabsElm = [];
|
||||
this.props.tabs.forEach(function(title, i){
|
||||
tabsElm.push(ce('li', (i === _this.state.activeTab ? {className: 'active'} : null),
|
||||
ce('a', {href: '#tab'+(i+1), onClick: _this.onTabClick.bind(_this, i)}, title)));
|
||||
});
|
||||
|
||||
var showElement = this.props.children.map(function(child, i){
|
||||
return ce('div', (i !== _this.state.activeTab ? {style: {display: 'none'}} : null), child);
|
||||
});
|
||||
|
||||
return(ce('div', null,
|
||||
ce('ul', {className: 'nav nav-tabs', id: 'tabs'}, tabsElm),
|
||||
showElement
|
||||
))
|
||||
},
|
||||
componentDidUpdate: function() {
|
||||
if (this.props.stateCallback !== null) {
|
||||
this.props.stateCallback(this.state.activeTab);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var ScrollingContent = React.createClass({
|
||||
totalHeight: 0,
|
||||
ownHeight: 0,
|
||||
scrollRatio: 0,
|
||||
lastPageY: 0,
|
||||
updateScrollParams: function () {
|
||||
this.totalHeight = this.refs.content.scrollHeight;
|
||||
this.ownHeight = this.refs.content.clientHeight;
|
||||
this.scrollRatio = this.ownHeight / this.totalHeight;
|
||||
if (isNaN(this.scrollRatio)) this.scrollRatio = 0;
|
||||
|
||||
this.refs.scroll.style.height = this.scrollRatio * 100 + "%";
|
||||
this.refs.scroll.style.top = (this.refs.content.scrollTop / this.totalHeight) * 100 + "%";
|
||||
},
|
||||
toggleSelect: function (value, event) { return value; },
|
||||
handleScrollMouseDown: function (event) {
|
||||
this.lastPageY = event.pageY;
|
||||
document.body.classList.add('scroll-grabbed');
|
||||
this.refs.scroll.classList.add('scroll-grabbed');
|
||||
|
||||
document.onselectstart = this.toggleSelect.bind(null, false);
|
||||
document.addEventListener('mousemove', this.handleScrollMouseMove);
|
||||
document.addEventListener('mouseup', this.handleScrollMouseUp);
|
||||
},
|
||||
handleScrollMouseMove: function (event) {
|
||||
var delta = event.pageY - this.lastPageY;
|
||||
this.lastPageY = event.pageY;
|
||||
|
||||
var _this = this;
|
||||
window.requestAnimationFrame(function(){
|
||||
_this.refs.content.scrollTop += delta / _this.scrollRatio;
|
||||
_this.refs.scroll.style.top = (_this.refs.content.scrollTop / _this.totalHeight) * 100 + "%";
|
||||
});
|
||||
},
|
||||
handleScrollMouseUp: function (event) {
|
||||
document.body.classList.remove('scroll-grabbed');
|
||||
this.refs.scroll.classList.remove('scroll-grabbed');
|
||||
|
||||
document.onselectstart = this.toggleSelect.bind(null, true);
|
||||
document.removeEventListener('mousemove', this.handleScrollMouseMove);
|
||||
document.removeEventListener('mouseup', this.handleScrollMouseUp);
|
||||
},
|
||||
/*--------------------*/
|
||||
render: function () {
|
||||
return (
|
||||
ce('div', {className: this.props.className},
|
||||
ce('div', {className: 'wrapper'},
|
||||
ce('div', {className: 'content', ref: 'content'}, this.props.children)
|
||||
),
|
||||
ce('div', {className: 'scroll', ref: 'scroll'})
|
||||
)
|
||||
);
|
||||
},
|
||||
componentDidMount: function () {
|
||||
this.updateScrollParams();
|
||||
this.refs.scroll.addEventListener('mousedown', this.handleScrollMouseDown);
|
||||
|
||||
var _this = this;
|
||||
this.refs.content.addEventListener('scroll', function(){
|
||||
window.requestAnimationFrame(function(){
|
||||
_this.refs.scroll.style.top = (_this.refs.content.scrollTop / _this.totalHeight) * 100 + "%";
|
||||
});
|
||||
});
|
||||
},
|
||||
componentDidUpdate: function () {
|
||||
this.updateScrollParams();
|
||||
}
|
||||
});
|
||||
|
||||
var WebConsole = React.createClass({
|
||||
ws: null,
|
||||
connect: function(){
|
||||
if (this.ws !== null) return;
|
||||
var _this = this;
|
||||
|
||||
this.ws = new WebSocket("ws://127.0.0.1:8770"); //FIXME указывать ip:port из настроек
|
||||
this.ws.onopen = function(){ console.debug('WS: open...'); };
|
||||
this.ws.onclose = function(){ console.debug('WS: close...'); };
|
||||
this.ws.onerror = function(e){ console.debug('WS: error'); console.error(e); };
|
||||
this.ws.onmessage = function(event){
|
||||
_this.setState({ lines: _this.state.lines.concat([event.data]) }); //TODO необходимо ограничить кол-во строк
|
||||
};
|
||||
},
|
||||
disconnect: function() {
|
||||
if (this.ws === null) return;
|
||||
this.ws.close();
|
||||
this.ws = null;
|
||||
},
|
||||
focusInput: function() {
|
||||
this.refs.input.focus();
|
||||
},
|
||||
handleKeyInput: function(event) {
|
||||
if (event.key === 'Enter') {
|
||||
console.debug("send command '" + this.refs.input.value + "'");
|
||||
this.ws.send(':'+this.refs.input.value);
|
||||
this.refs.input.value = '';
|
||||
}
|
||||
},
|
||||
/*--------------------*/
|
||||
getInitialState: function(){return{
|
||||
lines: []
|
||||
}},
|
||||
render: function(){
|
||||
var ansi_up = new AnsiUp;
|
||||
|
||||
return(
|
||||
ce('div', {id: 'webconsole'},
|
||||
ce(ScrollingContent, {className: 'output'},
|
||||
this.state.lines.map(function(line){
|
||||
return ce('p', {dangerouslySetInnerHTML: {__html: ansi_up.ansi_to_html(line)}});
|
||||
})
|
||||
),
|
||||
ce('input', {ref: 'input', 'onKeyPress': this.handleKeyInput})
|
||||
)
|
||||
)
|
||||
},
|
||||
componentWillUnmount: function(){
|
||||
this.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
var ServerInfo = React.createClass({
|
||||
tabStateWebConsole: function(state) {
|
||||
if (state === 1) {
|
||||
this.refs.webconsole.connect();
|
||||
this.refs.webconsole.focusInput();
|
||||
}
|
||||
},
|
||||
/*--------------------*/
|
||||
getInitialState: function(){return {
|
||||
title: null,
|
||||
data: []
|
||||
@@ -44,12 +211,15 @@ var ServerInfo = React.createClass({
|
||||
return(
|
||||
ce('div', null,
|
||||
ce('h2', {style: {'margin-top': '0px'}}, this.state.title),
|
||||
ce(Tabs, {tabs: ['Онлайн', 'Консоль'], stateCallback: this.tabStateWebConsole},
|
||||
ce(NvLineChart, {datum: [{
|
||||
key: 'Online players',
|
||||
color: '#37d668',
|
||||
area: true,
|
||||
values: this.state.data
|
||||
}]})
|
||||
}]}),
|
||||
ce(WebConsole, {ref: 'webconsole'})
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -60,6 +60,13 @@ var ContentModule = React.createClass({
|
||||
function(){ _this.setState({nvScriptReady: -5}); console.error('d3 - error'); }
|
||||
);
|
||||
|
||||
loadScript("/mcsmanager/ansi_up.js",
|
||||
function(){ console.debug('ansi_up - ok'); },
|
||||
function(){ console.debug('ansi_up - error'); }
|
||||
);
|
||||
|
||||
loadStyle("/mcsmanager/moduleStyle.css");
|
||||
|
||||
this.requestServerList();
|
||||
},
|
||||
render: function(){
|
||||
|
||||
57
mcserver-manager/src/main/resources/moduleStyle.css
Normal file
57
mcserver-manager/src/main/resources/moduleStyle.css
Normal file
@@ -0,0 +1,57 @@
|
||||
#webconsole .output {
|
||||
background-color: #1e1e1e;
|
||||
color: #eee;
|
||||
min-height: 500px;
|
||||
height: 1px;
|
||||
padding: 8px;
|
||||
font-family: monospace;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#webconsole .output .wrapper {
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#webconsole .output .wrapper .content {
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
right: -18px;
|
||||
margin-left: -18px;
|
||||
}
|
||||
|
||||
#webconsole .output .wrapper .content p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#webconsole .output .scroll {
|
||||
width: 9px;
|
||||
background: #f00;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 21.0836%;
|
||||
cursor: -webkit-grab;
|
||||
cursor: -moz-grab;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.scroll-grabbed,
|
||||
.scroll-grabbed * {
|
||||
cursor: -webkit-grabbing !important;
|
||||
cursor: -moz-grabbing !important;
|
||||
}
|
||||
|
||||
#webconsole input {
|
||||
background-color: #1e1e1e;
|
||||
background-image: url('data:image/svg+xml;utf-8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="30"><text x="7" y="24" style="font-size: 1.5em; font-family: monospace" fill="#ffffff">></text></svg>');
|
||||
background-repeat: no-repeat;
|
||||
color: #eee;
|
||||
border: none;
|
||||
padding: 8px 8px 8px 1.5em;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#webconsole input:focus {
|
||||
outline: none;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
group = 'asys'
|
||||
version = '0.18.3-SNAPSHOT'
|
||||
version = '0.18.4-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ASys: Web interface</title>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.7/yeti/bootstrap.min.css">
|
||||
<style>
|
||||
|
||||
Reference in New Issue
Block a user