init
@@ -0,0 +1,146 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.fengfei</groupId>
|
||||
<artifactId>yxwlkjnwct</artifactId>
|
||||
<version>0.1</version>
|
||||
</parent>
|
||||
<artifactId>proxy-server</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>proxy-server</name>
|
||||
<url>http://maven.apache.org</url>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.fengfei</groupId>
|
||||
<artifactId>proxy-common</artifactId>
|
||||
<version>0.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.fengfei</groupId>
|
||||
<artifactId>proxy-protocol</artifactId>
|
||||
<version>0.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<finalName>proxy-server-${project.version}</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>*.properties</exclude>
|
||||
<exclude>*.sh</exclude>
|
||||
<exclude>*.bat</exclude>
|
||||
<exclude>*.jks</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>2.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-dependencies</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>../distribution/proxy-server-${project.version}/lib</outputDirectory>
|
||||
<overWriteReleases>false</overWriteReleases>
|
||||
<overWriteSnapshots>false</overWriteSnapshots>
|
||||
<overWriteIfNewer>true</overWriteIfNewer>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>2.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-config</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<encoding>UTF-8</encoding>
|
||||
<outputDirectory>../distribution/proxy-server-${project.version}/conf</outputDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<includes>
|
||||
<include>*.properties</include>
|
||||
<include>*.json</include>
|
||||
<include>*.jks</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>copy-webpages</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<encoding>UTF-8</encoding>
|
||||
<outputDirectory>../distribution/proxy-server-${project.version}/webpages</outputDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>webpages</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>copy-sh</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<encoding>UTF-8</encoding>
|
||||
<outputDirectory>../distribution/proxy-server-${project.version}/bin</outputDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<includes>
|
||||
<include>*.sh</include>
|
||||
<include>*.bat</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>copy-dist-jar</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<encoding>UTF-8</encoding>
|
||||
<outputDirectory>../distribution/proxy-server-${project.version}/lib</outputDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>target</directory>
|
||||
<includes>
|
||||
<include>*.jar</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,292 @@
|
||||
package org.fengfei.lanproxy.server;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.fengfei.lanproxy.protocol.Constants;
|
||||
import org.fengfei.lanproxy.server.config.ProxyConfig;
|
||||
import org.fengfei.lanproxy.server.config.ProxyConfig.ConfigChangedListener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.util.AttributeKey;
|
||||
|
||||
/**
|
||||
* 代理服务连接管理(代理客户端连接+用户请求连接)
|
||||
*
|
||||
* @author fengfei
|
||||
*
|
||||
*/
|
||||
public class ProxyChannelManager {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(ProxyChannelManager.class);
|
||||
|
||||
private static final AttributeKey<Map<String, Channel>> USER_CHANNELS = AttributeKey.newInstance("user_channels");
|
||||
|
||||
private static final AttributeKey<String> REQUEST_LAN_INFO = AttributeKey.newInstance("request_lan_info");
|
||||
|
||||
private static final AttributeKey<List<Integer>> CHANNEL_PORT = AttributeKey.newInstance("channel_port");
|
||||
|
||||
private static final AttributeKey<String> CHANNEL_CLIENT_KEY = AttributeKey.newInstance("channel_client_key");
|
||||
|
||||
private static Map<Integer, Channel> portCmdChannelMapping = new ConcurrentHashMap<Integer, Channel>();
|
||||
|
||||
private static Map<String, Channel> cmdChannels = new ConcurrentHashMap<String, Channel>();
|
||||
|
||||
static {
|
||||
ProxyConfig.getInstance().addConfigChangedListener(new ConfigChangedListener() {
|
||||
|
||||
/**
|
||||
* 代理配置发生变化时回调
|
||||
*/
|
||||
@Override
|
||||
public synchronized void onChanged() {
|
||||
Iterator<Entry<String, Channel>> ite = cmdChannels.entrySet().iterator();
|
||||
while (ite.hasNext()) {
|
||||
Channel proxyChannel = ite.next().getValue();
|
||||
String clientKey = proxyChannel.attr(CHANNEL_CLIENT_KEY).get();
|
||||
|
||||
// 去除已经去掉的clientKey配置
|
||||
Set<String> clientKeySet = ProxyConfig.getInstance().getClientKeySet();
|
||||
if (!clientKeySet.contains(clientKey)) {
|
||||
removeCmdChannel(proxyChannel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (proxyChannel.isActive()) {
|
||||
List<Integer> inetPorts = new ArrayList<Integer>(ProxyConfig.getInstance().getClientInetPorts(clientKey));
|
||||
Set<Integer> inetPortSet = new HashSet<Integer>(inetPorts);
|
||||
List<Integer> channelInetPorts = new ArrayList<Integer>(proxyChannel.attr(CHANNEL_PORT).get());
|
||||
|
||||
synchronized (portCmdChannelMapping) {
|
||||
|
||||
// 移除旧的连接映射关系
|
||||
for (int chanelInetPort : channelInetPorts) {
|
||||
Channel channel = portCmdChannelMapping.get(chanelInetPort);
|
||||
if (channel == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 判断是否是同一个连接对象,有可能之前已经更换成其他client的连接了
|
||||
if (proxyChannel == channel) {
|
||||
if (!inetPortSet.contains(chanelInetPort)) {
|
||||
|
||||
// 移除新配置中不包含的端口
|
||||
portCmdChannelMapping.remove(chanelInetPort);
|
||||
proxyChannel.attr(CHANNEL_PORT).get().remove(new Integer(chanelInetPort));
|
||||
} else {
|
||||
|
||||
// 端口已经在改proxyChannel中使用了
|
||||
inetPorts.remove(new Integer(chanelInetPort));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 将新配置中增加的外网端口写入到映射配置中
|
||||
for (int inetPort : inetPorts) {
|
||||
portCmdChannelMapping.put(inetPort, proxyChannel);
|
||||
proxyChannel.attr(CHANNEL_PORT).get().add(inetPort);
|
||||
}
|
||||
|
||||
checkAndClearUserChannels(proxyChannel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ite = cmdChannels.entrySet().iterator();
|
||||
while (ite.hasNext()) {
|
||||
Entry<String, Channel> entry = ite.next();
|
||||
Channel proxyChannel = entry.getValue();
|
||||
logger.info("proxyChannel config, {}, {}, {} ,{}", entry.getKey(), proxyChannel, getUserChannels(proxyChannel).size(), proxyChannel.attr(CHANNEL_PORT).get());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测连接配置是否与当前配置一致,不一致则关闭
|
||||
*
|
||||
* @param proxyChannel
|
||||
*/
|
||||
private void checkAndClearUserChannels(Channel proxyChannel) {
|
||||
Map<String, Channel> userChannels = getUserChannels(proxyChannel);
|
||||
Iterator<Entry<String, Channel>> userChannelIte = userChannels.entrySet().iterator();
|
||||
while (userChannelIte.hasNext()) {
|
||||
Entry<String, Channel> entry = userChannelIte.next();
|
||||
Channel userChannel = entry.getValue();
|
||||
String requestLanInfo = getUserChannelRequestLanInfo(userChannel);
|
||||
InetSocketAddress sa = (InetSocketAddress) userChannel.localAddress();
|
||||
String lanInfo = ProxyConfig.getInstance().getLanInfo(sa.getPort());
|
||||
|
||||
// 判断当前配置中对应外网端口的lan信息是否与正在运行的连接中的lan信息是否一致
|
||||
if (lanInfo == null || !lanInfo.equals(requestLanInfo)) {
|
||||
userChannel.close();
|
||||
|
||||
// ConcurrentHashMap不会报ConcurrentModificationException异常
|
||||
userChannels.remove(entry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加代理服务器端口与代理控制客户端连接的映射关系
|
||||
*
|
||||
* @param ports
|
||||
* @param channel
|
||||
*/
|
||||
public static void addCmdChannel(List<Integer> ports, String clientKey, Channel channel) {
|
||||
|
||||
if (ports == null) {
|
||||
throw new IllegalArgumentException("port can not be null");
|
||||
}
|
||||
|
||||
// 客户端(proxy-client)相对较少,这里同步的比较重
|
||||
// 保证服务器对外端口与客户端到服务器的连接关系在临界情况时调用removeChannel(Channel channel)时不出问题
|
||||
synchronized (portCmdChannelMapping) {
|
||||
for (int port : ports) {
|
||||
portCmdChannelMapping.put(port, channel);
|
||||
}
|
||||
}
|
||||
|
||||
channel.attr(CHANNEL_PORT).set(ports);
|
||||
channel.attr(CHANNEL_CLIENT_KEY).set(clientKey);
|
||||
channel.attr(USER_CHANNELS).set(new ConcurrentHashMap<String, Channel>());
|
||||
cmdChannels.put(clientKey, channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* 代理客户端连接断开后清除关系
|
||||
*
|
||||
* @param channel
|
||||
*/
|
||||
public static void removeCmdChannel(Channel channel) {
|
||||
logger.warn("channel closed, clear user channels, {}", channel);
|
||||
if (channel.attr(CHANNEL_PORT).get() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String clientKey = channel.attr(CHANNEL_CLIENT_KEY).get();
|
||||
Channel channel0 = cmdChannels.remove(clientKey);
|
||||
if (channel != channel0) {
|
||||
cmdChannels.put(clientKey, channel);
|
||||
}
|
||||
|
||||
List<Integer> ports = channel.attr(CHANNEL_PORT).get();
|
||||
for (int port : ports) {
|
||||
Channel proxyChannel = portCmdChannelMapping.remove(port);
|
||||
if (proxyChannel == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 在执行断连之前新的连接已经连上来了
|
||||
if (proxyChannel != channel) {
|
||||
portCmdChannelMapping.put(port, proxyChannel);
|
||||
}
|
||||
}
|
||||
|
||||
if (channel.isActive()) {
|
||||
logger.info("disconnect proxy channel {}", channel);
|
||||
channel.close();
|
||||
}
|
||||
|
||||
Map<String, Channel> userChannels = getUserChannels(channel);
|
||||
Iterator<String> ite = userChannels.keySet().iterator();
|
||||
while (ite.hasNext()) {
|
||||
Channel userChannel = userChannels.get(ite.next());
|
||||
if (userChannel.isActive()) {
|
||||
userChannel.close();
|
||||
logger.info("disconnect user channel {}", userChannel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Channel getCmdChannel(Integer port) {
|
||||
return portCmdChannelMapping.get(port);
|
||||
}
|
||||
|
||||
public static Channel getCmdChannel(String clientKey) {
|
||||
return cmdChannels.get(clientKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加用户连接与代理客户端连接关系
|
||||
*
|
||||
* @param proxyChannel
|
||||
* @param userId
|
||||
* @param userChannel
|
||||
*/
|
||||
public static void addUserChannelToCmdChannel(Channel cmdChannel, String userId, Channel userChannel) {
|
||||
InetSocketAddress sa = (InetSocketAddress) userChannel.localAddress();
|
||||
String lanInfo = ProxyConfig.getInstance().getLanInfo(sa.getPort());
|
||||
userChannel.attr(Constants.USER_ID).set(userId);
|
||||
userChannel.attr(REQUEST_LAN_INFO).set(lanInfo);
|
||||
cmdChannel.attr(USER_CHANNELS).get().put(userId, userChannel);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除用户连接与代理客户端连接关系
|
||||
*
|
||||
* @param proxyChannel
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
public static Channel removeUserChannelFromCmdChannel(Channel cmdChannel, String userId) {
|
||||
if (cmdChannel.attr(USER_CHANNELS).get() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
synchronized (cmdChannel) {
|
||||
return cmdChannel.attr(USER_CHANNELS).get().remove(userId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据代理客户端连接与用户编号获取用户连接
|
||||
*
|
||||
* @param proxyChannel
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
public static Channel getUserChannel(Channel cmdChannel, String userId) {
|
||||
return cmdChannel.attr(USER_CHANNELS).get().get(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户编号
|
||||
*
|
||||
* @param userChannel
|
||||
* @return
|
||||
*/
|
||||
public static String getUserChannelUserId(Channel userChannel) {
|
||||
return userChannel.attr(Constants.USER_ID).get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户请求的内网IP端口信息
|
||||
*
|
||||
* @param userChannel
|
||||
* @return
|
||||
*/
|
||||
public static String getUserChannelRequestLanInfo(Channel userChannel) {
|
||||
return userChannel.attr(REQUEST_LAN_INFO).get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取代理控制客户端连接绑定的所有用户连接
|
||||
*
|
||||
* @param cmdChannel
|
||||
* @return
|
||||
*/
|
||||
public static Map<String, Channel> getUserChannels(Channel cmdChannel) {
|
||||
return cmdChannel.attr(USER_CHANNELS).get();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package org.fengfei.lanproxy.server;
|
||||
|
||||
import java.net.BindException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
||||
import org.fengfei.lanproxy.common.Config;
|
||||
import org.fengfei.lanproxy.common.container.Container;
|
||||
import org.fengfei.lanproxy.common.container.ContainerHelper;
|
||||
import org.fengfei.lanproxy.protocol.IdleCheckHandler;
|
||||
import org.fengfei.lanproxy.protocol.ProxyMessageDecoder;
|
||||
import org.fengfei.lanproxy.protocol.ProxyMessageEncoder;
|
||||
import org.fengfei.lanproxy.server.config.ProxyConfig;
|
||||
import org.fengfei.lanproxy.server.config.ProxyConfig.ConfigChangedListener;
|
||||
import org.fengfei.lanproxy.server.config.web.WebConfigContainer;
|
||||
import org.fengfei.lanproxy.server.handlers.ServerChannelHandler;
|
||||
import org.fengfei.lanproxy.server.handlers.UserChannelHandler;
|
||||
import org.fengfei.lanproxy.server.metrics.handler.BytesMetricsHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
|
||||
public class ProxyServerContainer implements Container, ConfigChangedListener {
|
||||
|
||||
/**
|
||||
* max packet is 6M.
|
||||
*/
|
||||
private static final int MAX_FRAME_LENGTH = 6 * 1024 * 1024;
|
||||
|
||||
private static final int LENGTH_FIELD_OFFSET = 0;
|
||||
|
||||
private static final int LENGTH_FIELD_LENGTH = 4;
|
||||
|
||||
private static final int INITIAL_BYTES_TO_STRIP = 0;
|
||||
|
||||
private static final int LENGTH_ADJUSTMENT = 0;
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(ProxyServerContainer.class);
|
||||
|
||||
private NioEventLoopGroup serverWorkerGroup;
|
||||
|
||||
private NioEventLoopGroup serverBossGroup;
|
||||
|
||||
public ProxyServerContainer() {
|
||||
|
||||
serverBossGroup = new NioEventLoopGroup();
|
||||
serverWorkerGroup = new NioEventLoopGroup();
|
||||
|
||||
ProxyConfig.getInstance().addConfigChangedListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||
bootstrap.group(serverBossGroup, serverWorkerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) throws Exception {
|
||||
ch.pipeline().addLast(new ProxyMessageDecoder(MAX_FRAME_LENGTH, LENGTH_FIELD_OFFSET, LENGTH_FIELD_LENGTH, LENGTH_ADJUSTMENT, INITIAL_BYTES_TO_STRIP));
|
||||
ch.pipeline().addLast(new ProxyMessageEncoder());
|
||||
ch.pipeline().addLast(new IdleCheckHandler(IdleCheckHandler.READ_IDLE_TIME, IdleCheckHandler.WRITE_IDLE_TIME, 0));
|
||||
ch.pipeline().addLast(new ServerChannelHandler());
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
bootstrap.bind(ProxyConfig.getInstance().getServerBind(), ProxyConfig.getInstance().getServerPort()).get();
|
||||
logger.info("proxy server start on port " + ProxyConfig.getInstance().getServerPort());
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
if (Config.getInstance().getBooleanValue("server.ssl.enable", false)) {
|
||||
String host = Config.getInstance().getStringValue("server.ssl.bind", "0.0.0.0");
|
||||
int port = Config.getInstance().getIntValue("server.ssl.port");
|
||||
initializeSSLTCPTransport(host, port, new SslContextCreator().initSSLContext());
|
||||
}
|
||||
|
||||
startUserPort();
|
||||
|
||||
}
|
||||
|
||||
private void initializeSSLTCPTransport(String host, int port, final SSLContext sslContext) {
|
||||
ServerBootstrap b = new ServerBootstrap();
|
||||
b.group(serverBossGroup, serverWorkerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) throws Exception {
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
try {
|
||||
pipeline.addLast("ssl", createSslHandler(sslContext, Config.getInstance().getBooleanValue("server.ssl.needsClientAuth", false)));
|
||||
ch.pipeline().addLast(new ProxyMessageDecoder(MAX_FRAME_LENGTH, LENGTH_FIELD_OFFSET, LENGTH_FIELD_LENGTH, LENGTH_ADJUSTMENT, INITIAL_BYTES_TO_STRIP));
|
||||
ch.pipeline().addLast(new ProxyMessageEncoder());
|
||||
ch.pipeline().addLast(new IdleCheckHandler(IdleCheckHandler.READ_IDLE_TIME, IdleCheckHandler.WRITE_IDLE_TIME, 0));
|
||||
ch.pipeline().addLast(new ServerChannelHandler());
|
||||
} catch (Throwable th) {
|
||||
logger.error("Severe error during pipeline creation", th);
|
||||
throw th;
|
||||
}
|
||||
}
|
||||
});
|
||||
try {
|
||||
|
||||
// Bind and start to accept incoming connections.
|
||||
ChannelFuture f = b.bind(host, port);
|
||||
f.sync();
|
||||
logger.info("proxy ssl server start on port {}", port);
|
||||
} catch (InterruptedException ex) {
|
||||
logger.error("An interruptedException was caught while initializing server", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void startUserPort() {
|
||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||
bootstrap.group(serverBossGroup, serverWorkerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) throws Exception {
|
||||
ch.pipeline().addFirst(new BytesMetricsHandler());
|
||||
ch.pipeline().addLast(new UserChannelHandler());
|
||||
}
|
||||
});
|
||||
|
||||
List<Integer> ports = ProxyConfig.getInstance().getUserPorts();
|
||||
for (int port : ports) {
|
||||
try {
|
||||
bootstrap.bind(port).get();
|
||||
logger.info("bind user port " + port);
|
||||
} catch (Exception ex) {
|
||||
|
||||
// BindException表示该端口已经绑定过
|
||||
if (!(ex.getCause() instanceof BindException)) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged() {
|
||||
startUserPort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
serverBossGroup.shutdownGracefully();
|
||||
serverWorkerGroup.shutdownGracefully();
|
||||
}
|
||||
|
||||
private ChannelHandler createSslHandler(SSLContext sslContext, boolean needsClientAuth) {
|
||||
SSLEngine sslEngine = sslContext.createSSLEngine();
|
||||
sslEngine.setUseClientMode(false);
|
||||
if (needsClientAuth) {
|
||||
sslEngine.setNeedClientAuth(true);
|
||||
}
|
||||
|
||||
return new SslHandler(sslEngine);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
ContainerHelper.start(Arrays.asList(new Container[] { new ProxyServerContainer(), new WebConfigContainer() }));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package org.fengfei.lanproxy.server;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.CertificateException;
|
||||
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
|
||||
import org.fengfei.lanproxy.common.Config;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class SslContextCreator {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(SslContextCreator.class);
|
||||
|
||||
public SSLContext initSSLContext() {
|
||||
logger.info("Checking SSL configuration properties...");
|
||||
final String jksPath = Config.getInstance().getStringValue("server.ssl.jksPath");
|
||||
logger.info("Initializing SSL context. KeystorePath = {}.", jksPath);
|
||||
if (jksPath == null || jksPath.isEmpty()) {
|
||||
// key_store_password or key_manager_password are empty
|
||||
logger.warn("The keystore path is null or empty. The SSL context won't be initialized.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// if we have the port also the jks then keyStorePassword and
|
||||
// keyManagerPassword
|
||||
// has to be defined
|
||||
final String keyStorePassword = Config.getInstance().getStringValue("server.ssl.keyStorePassword");
|
||||
final String keyManagerPassword = Config.getInstance().getStringValue("server.ssl.keyManagerPassword");
|
||||
if (keyStorePassword == null || keyStorePassword.isEmpty()) {
|
||||
|
||||
// key_store_password or key_manager_password are empty
|
||||
logger.warn("The keystore password is null or empty. The SSL context won't be initialized.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (keyManagerPassword == null || keyManagerPassword.isEmpty()) {
|
||||
|
||||
// key_manager_password or key_manager_password are empty
|
||||
logger.warn("The key manager password is null or empty. The SSL context won't be initialized.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// if client authentification is enabled a trustmanager needs to be
|
||||
// added to the ServerContext
|
||||
boolean needsClientAuth = Config.getInstance().getBooleanValue("server.ssl.needsClientAuth", false);
|
||||
|
||||
try {
|
||||
logger.info("Loading keystore. KeystorePath = {}.", jksPath);
|
||||
InputStream jksInputStream = jksDatastore(jksPath);
|
||||
SSLContext serverContext = SSLContext.getInstance("TLS");
|
||||
final KeyStore ks = KeyStore.getInstance("JKS");
|
||||
ks.load(jksInputStream, keyStorePassword.toCharArray());
|
||||
logger.info("Initializing key manager...");
|
||||
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
kmf.init(ks, keyManagerPassword.toCharArray());
|
||||
TrustManager[] trustManagers = null;
|
||||
if (needsClientAuth) {
|
||||
logger.warn(
|
||||
"Client authentication is enabled. The keystore will be used as a truststore. KeystorePath = {}.",
|
||||
jksPath);
|
||||
// use keystore as truststore, as server needs to trust
|
||||
// certificates signed by the
|
||||
// server certificates
|
||||
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
tmf.init(ks);
|
||||
trustManagers = tmf.getTrustManagers();
|
||||
}
|
||||
|
||||
// init sslContext
|
||||
logger.info("Initializing SSL context...");
|
||||
serverContext.init(kmf.getKeyManagers(), trustManagers, null);
|
||||
logger.info("The SSL context has been initialized successfully.");
|
||||
|
||||
return serverContext;
|
||||
} catch (NoSuchAlgorithmException | UnrecoverableKeyException | CertificateException | KeyStoreException
|
||||
| KeyManagementException | IOException ex) {
|
||||
logger.error("Unable to initialize SSL context. Cause = {}, errorMessage = {}.", ex.getCause(),
|
||||
ex.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private InputStream jksDatastore(String jksPath) throws FileNotFoundException {
|
||||
URL jksUrl = getClass().getClassLoader().getResource(jksPath);
|
||||
if (jksUrl != null) {
|
||||
logger.info("Starting with jks at {}, jks normal {}", jksUrl.toExternalForm(), jksUrl);
|
||||
return getClass().getClassLoader().getResourceAsStream(jksPath);
|
||||
}
|
||||
|
||||
logger.warn("No keystore has been found in the bundled resources. Scanning filesystem...");
|
||||
File jksFile = new File(jksPath);
|
||||
if (jksFile.exists()) {
|
||||
logger.info("Loading external keystore. Url = {}.", jksFile.getAbsolutePath());
|
||||
return new FileInputStream(jksFile);
|
||||
}
|
||||
|
||||
logger.warn("The keystore file does not exist. Url = {}.", jksFile.getAbsolutePath());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
package org.fengfei.lanproxy.server.config;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.fengfei.lanproxy.common.Config;
|
||||
import org.fengfei.lanproxy.common.JsonUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
/**
|
||||
* server config
|
||||
*
|
||||
* @author fengfei
|
||||
*
|
||||
*/
|
||||
public class ProxyConfig implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 配置文件为config.json */
|
||||
public static final String CONFIG_FILE;
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(ProxyConfig.class);
|
||||
|
||||
static {
|
||||
|
||||
// 代理配置信息存放在用户根目录下
|
||||
String dataPath = System.getProperty("user.home") + "/" + ".lanproxy/";
|
||||
File file = new File(dataPath);
|
||||
if (!file.isDirectory()) {
|
||||
file.mkdir();
|
||||
}
|
||||
|
||||
CONFIG_FILE = dataPath + "/config.json";
|
||||
}
|
||||
|
||||
/** 代理服务器绑定主机host */
|
||||
private String serverBind;
|
||||
|
||||
/** 代理服务器与代理客户端通信端口 */
|
||||
private Integer serverPort;
|
||||
|
||||
/** 配置服务绑定主机host */
|
||||
private String configServerBind;
|
||||
|
||||
/** 配置服务端口 */
|
||||
private Integer configServerPort;
|
||||
|
||||
/** 配置服务管理员用户名 */
|
||||
private String configAdminUsername;
|
||||
|
||||
/** 配置服务管理员密码 */
|
||||
private String configAdminPassword;
|
||||
|
||||
/** 代理客户端,支持多个客户端 */
|
||||
private List<Client> clients;
|
||||
|
||||
/** 更新配置后保证在其他线程即时生效 */
|
||||
private static ProxyConfig instance = new ProxyConfig();;
|
||||
|
||||
/** 代理服务器为各个代理客户端(key)开启对应的端口列表(value) */
|
||||
private volatile Map<String, List<Integer>> clientInetPortMapping = new HashMap<String, List<Integer>>();
|
||||
|
||||
/** 代理服务器上的每个对外端口(key)对应的代理客户端背后的真实服务器信息(value) */
|
||||
private volatile Map<Integer, String> inetPortLanInfoMapping = new HashMap<Integer, String>();
|
||||
|
||||
/** 配置变化监听器 */
|
||||
private List<ConfigChangedListener> configChangedListeners = new ArrayList<ConfigChangedListener>();
|
||||
|
||||
private ProxyConfig() {
|
||||
|
||||
// 代理服务器主机和端口配置初始化
|
||||
this.serverPort = Config.getInstance().getIntValue("server.port");
|
||||
this.serverBind = Config.getInstance().getStringValue("server.bind", "0.0.0.0");
|
||||
|
||||
// 配置服务器主机和端口配置初始化
|
||||
this.configServerPort = Config.getInstance().getIntValue("config.server.port");
|
||||
this.configServerBind = Config.getInstance().getStringValue("config.server.bind", "0.0.0.0");
|
||||
|
||||
// 配置服务器管理员登录认证信息
|
||||
this.configAdminUsername = Config.getInstance().getStringValue("config.admin.username");
|
||||
this.configAdminPassword = Config.getInstance().getStringValue("config.admin.password");
|
||||
|
||||
logger.info(
|
||||
"config init serverBind {}, serverPort {}, configServerBind {}, configServerPort {}, configAdminUsername {}, configAdminPassword {}",
|
||||
serverBind, serverPort, configServerBind, configServerPort, configAdminUsername, configAdminPassword);
|
||||
|
||||
update(null);
|
||||
}
|
||||
|
||||
public Integer getServerPort() {
|
||||
return this.serverPort;
|
||||
}
|
||||
|
||||
public String getServerBind() {
|
||||
return serverBind;
|
||||
}
|
||||
|
||||
public void setServerBind(String serverBind) {
|
||||
this.serverBind = serverBind;
|
||||
}
|
||||
|
||||
public String getConfigServerBind() {
|
||||
return configServerBind;
|
||||
}
|
||||
|
||||
public void setConfigServerBind(String configServerBind) {
|
||||
this.configServerBind = configServerBind;
|
||||
}
|
||||
|
||||
public Integer getConfigServerPort() {
|
||||
return configServerPort;
|
||||
}
|
||||
|
||||
public void setConfigServerPort(Integer configServerPort) {
|
||||
this.configServerPort = configServerPort;
|
||||
}
|
||||
|
||||
public String getConfigAdminUsername() {
|
||||
return configAdminUsername;
|
||||
}
|
||||
|
||||
public void setConfigAdminUsername(String configAdminUsername) {
|
||||
this.configAdminUsername = configAdminUsername;
|
||||
}
|
||||
|
||||
public String getConfigAdminPassword() {
|
||||
return configAdminPassword;
|
||||
}
|
||||
|
||||
public void setConfigAdminPassword(String configAdminPassword) {
|
||||
this.configAdminPassword = configAdminPassword;
|
||||
}
|
||||
|
||||
public void setServerPort(Integer serverPort) {
|
||||
this.serverPort = serverPort;
|
||||
}
|
||||
|
||||
public List<Client> getClients() {
|
||||
return clients;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析配置文件
|
||||
*/
|
||||
public void update(String proxyMappingConfigJson) {
|
||||
|
||||
File file = new File(CONFIG_FILE);
|
||||
try {
|
||||
if (proxyMappingConfigJson == null && file.exists()) {
|
||||
InputStream in = new FileInputStream(file);
|
||||
byte[] buf = new byte[1024];
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
int readIndex;
|
||||
while ((readIndex = in.read(buf)) != -1) {
|
||||
out.write(buf, 0, readIndex);
|
||||
}
|
||||
|
||||
in.close();
|
||||
proxyMappingConfigJson = new String(out.toByteArray(), Charset.forName("UTF-8"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
List<Client> clients = JsonUtil.json2object(proxyMappingConfigJson, new TypeToken<List<Client>>() {
|
||||
});
|
||||
if (clients == null) {
|
||||
clients = new ArrayList<Client>();
|
||||
}
|
||||
|
||||
Map<String, List<Integer>> clientInetPortMapping = new HashMap<String, List<Integer>>();
|
||||
Map<Integer, String> inetPortLanInfoMapping = new HashMap<Integer, String>();
|
||||
|
||||
// 构造端口映射关系
|
||||
for (Client client : clients) {
|
||||
String clientKey = client.getClientKey();
|
||||
if (clientInetPortMapping.containsKey(clientKey)) {
|
||||
throw new IllegalArgumentException("密钥同时作为客户端标识,不能重复: " + clientKey);
|
||||
}
|
||||
List<ClientProxyMapping> mappings = client.getProxyMappings();
|
||||
List<Integer> ports = new ArrayList<Integer>();
|
||||
clientInetPortMapping.put(clientKey, ports);
|
||||
for (ClientProxyMapping mapping : mappings) {
|
||||
Integer port = mapping.getInetPort();
|
||||
ports.add(port);
|
||||
if (inetPortLanInfoMapping.containsKey(port)) {
|
||||
throw new IllegalArgumentException("一个公网端口只能映射一个后端信息,不能重复: " + port);
|
||||
}
|
||||
|
||||
inetPortLanInfoMapping.put(port, mapping.getLan());
|
||||
}
|
||||
}
|
||||
|
||||
// 替换之前的配置关系
|
||||
this.clientInetPortMapping = clientInetPortMapping;
|
||||
this.inetPortLanInfoMapping = inetPortLanInfoMapping;
|
||||
this.clients = clients;
|
||||
|
||||
if (proxyMappingConfigJson != null) {
|
||||
try {
|
||||
FileOutputStream out = new FileOutputStream(file);
|
||||
out.write(proxyMappingConfigJson.getBytes(Charset.forName("UTF-8")));
|
||||
out.flush();
|
||||
out.close();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
notifyconfigChangedListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置更新通知
|
||||
*/
|
||||
private void notifyconfigChangedListeners() {
|
||||
List<ConfigChangedListener> changedListeners = new ArrayList<ConfigChangedListener>(configChangedListeners);
|
||||
for (ConfigChangedListener changedListener : changedListeners) {
|
||||
changedListener.onChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加配置变化监听器
|
||||
*
|
||||
* @param configChangedListener
|
||||
*/
|
||||
public void addConfigChangedListener(ConfigChangedListener configChangedListener) {
|
||||
configChangedListeners.add(configChangedListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除配置变化监听器
|
||||
*
|
||||
* @param configChangedListener
|
||||
*/
|
||||
public void removeConfigChangedListener(ConfigChangedListener configChangedListener) {
|
||||
configChangedListeners.remove(configChangedListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取代理客户端对应的代理服务器端口
|
||||
*
|
||||
* @param clientKey
|
||||
* @return
|
||||
*/
|
||||
public List<Integer> getClientInetPorts(String clientKey) {
|
||||
return clientInetPortMapping.get(clientKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有的clientKey
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Set<String> getClientKeySet() {
|
||||
return clientInetPortMapping.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据代理服务器端口获取后端服务器代理信息
|
||||
*
|
||||
* @param port
|
||||
* @return
|
||||
*/
|
||||
public String getLanInfo(Integer port) {
|
||||
return inetPortLanInfoMapping.get(port);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回需要绑定在代理服务器的端口(用于用户请求)
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public List<Integer> getUserPorts() {
|
||||
List<Integer> ports = new ArrayList<Integer>();
|
||||
Iterator<Integer> ite = inetPortLanInfoMapping.keySet().iterator();
|
||||
while (ite.hasNext()) {
|
||||
ports.add(ite.next());
|
||||
}
|
||||
|
||||
return ports;
|
||||
}
|
||||
|
||||
public static ProxyConfig getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 代理客户端
|
||||
*
|
||||
* @author fengfei
|
||||
*
|
||||
*/
|
||||
public static class Client implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 客户端备注名称 */
|
||||
private String name;
|
||||
|
||||
/** 代理客户端唯一标识key */
|
||||
private String clientKey;
|
||||
|
||||
/** 代理客户端与其后面的真实服务器映射关系 */
|
||||
private List<ClientProxyMapping> proxyMappings;
|
||||
|
||||
private int status;
|
||||
|
||||
public String getClientKey() {
|
||||
return clientKey;
|
||||
}
|
||||
|
||||
public void setClientKey(String clientKey) {
|
||||
this.clientKey = clientKey;
|
||||
}
|
||||
|
||||
public List<ClientProxyMapping> getProxyMappings() {
|
||||
return proxyMappings;
|
||||
}
|
||||
|
||||
public void setProxyMappings(List<ClientProxyMapping> proxyMappings) {
|
||||
this.proxyMappings = proxyMappings;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(int status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 代理客户端与其后面真实服务器映射关系
|
||||
*
|
||||
* @author fengfei
|
||||
*
|
||||
*/
|
||||
public static class ClientProxyMapping {
|
||||
|
||||
/** 代理服务器端口 */
|
||||
private Integer inetPort;
|
||||
|
||||
/** 需要代理的网络信息(代理客户端能够访问),格式 192.168.1.99:80 (必须带端口) */
|
||||
private String lan;
|
||||
|
||||
/** 备注名称 */
|
||||
private String name;
|
||||
|
||||
public Integer getInetPort() {
|
||||
return inetPort;
|
||||
}
|
||||
|
||||
public void setInetPort(Integer inetPort) {
|
||||
this.inetPort = inetPort;
|
||||
}
|
||||
|
||||
public String getLan() {
|
||||
return lan;
|
||||
}
|
||||
|
||||
public void setLan(String lan) {
|
||||
this.lan = lan;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置更新回调
|
||||
*
|
||||
* @author fengfei
|
||||
*
|
||||
*/
|
||||
public static interface ConfigChangedListener {
|
||||
|
||||
void onChanged();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package org.fengfei.lanproxy.server.config.web;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.fengfei.lanproxy.server.config.web.exception.ContextException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
|
||||
/**
|
||||
* 接口路由管理
|
||||
*
|
||||
* @author fengfei
|
||||
*
|
||||
*/
|
||||
public class ApiRoute {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(ApiRoute.class);
|
||||
|
||||
/** 接口路由 */
|
||||
private static Map<String, RequestHandler> routes = new ConcurrentHashMap<String, RequestHandler>();
|
||||
|
||||
/** 拦截器,初始化后不会在变化 */
|
||||
private static List<RequestMiddleware> middlewares = new ArrayList<RequestMiddleware>();
|
||||
|
||||
/**
|
||||
* 增加接口请求路由
|
||||
*
|
||||
* @param uri
|
||||
* @param requestHandler
|
||||
*/
|
||||
public static void addRoute(String uri, RequestHandler requestHandler) {
|
||||
if (routes.containsKey(uri)) {
|
||||
throw new IllegalArgumentException("Duplicate uri:" + uri);
|
||||
}
|
||||
|
||||
logger.info("add route {}", uri);
|
||||
routes.put(uri, requestHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加拦截器
|
||||
*
|
||||
* @param requestMiddleware
|
||||
*/
|
||||
public static void addMiddleware(RequestMiddleware requestMiddleware) {
|
||||
if (middlewares.contains(requestMiddleware)) {
|
||||
throw new IllegalArgumentException("Duplicate RequestMiddleware:" + requestMiddleware);
|
||||
}
|
||||
|
||||
logger.info("add requestMiddleware {}", requestMiddleware);
|
||||
middlewares.add(requestMiddleware);
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求执行
|
||||
*
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
public static ResponseInfo run(FullHttpRequest request) {
|
||||
try {
|
||||
|
||||
// 拦截器中如果不能通过以异常的方式进行反馈
|
||||
for (RequestMiddleware middleware : middlewares) {
|
||||
middleware.preRequest(request);
|
||||
}
|
||||
|
||||
URI uri = new URI(request.getUri());
|
||||
RequestHandler handler = routes.get(uri.getPath());
|
||||
ResponseInfo responseInfo = null;
|
||||
if (handler != null) {
|
||||
responseInfo = handler.request(request);
|
||||
} else {
|
||||
responseInfo = ResponseInfo.build(ResponseInfo.CODE_API_NOT_FOUND, "api not found");
|
||||
}
|
||||
|
||||
return responseInfo;
|
||||
} catch (Exception ex) {
|
||||
if (ex instanceof ContextException) {
|
||||
return ResponseInfo.build(((ContextException) ex).getCode(), ex.getMessage());
|
||||
}
|
||||
|
||||
logger.error("request error", ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package org.fengfei.lanproxy.server.config.web;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.fengfei.lanproxy.common.JsonUtil;
|
||||
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.DefaultFileRegion;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.DefaultHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpHeaders.Names;
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.HttpVersion;
|
||||
import io.netty.handler.codec.http.LastHttpContent;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
import io.netty.handler.stream.ChunkedNioFile;
|
||||
|
||||
public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
|
||||
|
||||
private static final String PAGE_FOLDER = System.getProperty("app.home", System.getProperty("user.dir"))
|
||||
+ "/webpages";
|
||||
|
||||
private static final String SERVER_VS = "LPS-0.1";
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
|
||||
|
||||
// GET返回页面;POST请求接口
|
||||
if (request.getMethod() != HttpMethod.POST) {
|
||||
outputPages(ctx, request);
|
||||
return;
|
||||
}
|
||||
|
||||
ResponseInfo responseInfo = ApiRoute.run(request);
|
||||
|
||||
// 错误码规则:除100取整为http状态码
|
||||
outputContent(ctx, request, responseInfo.getCode() / 100, JsonUtil.object2json(responseInfo),
|
||||
"Application/json;charset=utf-8");
|
||||
}
|
||||
|
||||
private void outputContent(ChannelHandlerContext ctx, FullHttpRequest request, int code, String content,
|
||||
String mimeType) {
|
||||
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.valueOf(code),
|
||||
Unpooled.wrappedBuffer(content.getBytes(Charset.forName("UTF-8"))));
|
||||
response.headers().set(Names.CONTENT_TYPE, mimeType);
|
||||
response.headers().set(Names.CONTENT_LENGTH, response.content().readableBytes());
|
||||
response.headers().set(Names.SERVER, SERVER_VS);
|
||||
ChannelFuture future = ctx.writeAndFlush(response);
|
||||
if (!HttpHeaders.isKeepAlive(request)) {
|
||||
future.addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出静态资源数据
|
||||
*
|
||||
* @param ctx
|
||||
* @param request
|
||||
* @throws Exception
|
||||
*/
|
||||
private void outputPages(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
|
||||
HttpResponseStatus status = HttpResponseStatus.OK;
|
||||
URI uri = new URI(request.getUri());
|
||||
String uriPath = uri.getPath();
|
||||
uriPath = uriPath.equals("/") ? "/index.html" : uriPath;
|
||||
String path = PAGE_FOLDER + uriPath;
|
||||
File rfile = new File(path);
|
||||
if (rfile.isDirectory()) {
|
||||
path = path + "/index.html";
|
||||
rfile = new File(path);
|
||||
}
|
||||
|
||||
if (!rfile.exists()) {
|
||||
status = HttpResponseStatus.NOT_FOUND;
|
||||
outputContent(ctx, request, status.code(), status.toString(), "text/html");
|
||||
return;
|
||||
}
|
||||
|
||||
if (HttpHeaders.is100ContinueExpected(request)) {
|
||||
send100Continue(ctx);
|
||||
}
|
||||
|
||||
String mimeType = MimeType.getMimeType(MimeType.parseSuffix(path));
|
||||
long length = 0;
|
||||
RandomAccessFile raf = null;
|
||||
try {
|
||||
raf = new RandomAccessFile(rfile, "r");
|
||||
length = raf.length();
|
||||
} finally {
|
||||
if (length < 0 && raf != null) {
|
||||
raf.close();
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse response = new DefaultHttpResponse(request.getProtocolVersion(), status);
|
||||
response.headers().set(HttpHeaders.Names.CONTENT_TYPE, mimeType);
|
||||
boolean keepAlive = HttpHeaders.isKeepAlive(request);
|
||||
if (keepAlive) {
|
||||
response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, length);
|
||||
response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
|
||||
}
|
||||
|
||||
response.headers().set(Names.SERVER, SERVER_VS);
|
||||
ctx.write(response);
|
||||
|
||||
if (ctx.pipeline().get(SslHandler.class) == null) {
|
||||
ctx.write(new DefaultFileRegion(raf.getChannel(), 0, length));
|
||||
} else {
|
||||
ctx.write(new ChunkedNioFile(raf.getChannel()));
|
||||
}
|
||||
|
||||
ChannelFuture future = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
|
||||
if (!keepAlive) {
|
||||
future.addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
}
|
||||
|
||||
private static void send100Continue(ChannelHandlerContext ctx) {
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE);
|
||||
ctx.writeAndFlush(response);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
package org.fengfei.lanproxy.server.config.web;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class MimeType {
|
||||
|
||||
final static Pattern pattern = Pattern.compile("\\S*[?]\\S*");
|
||||
|
||||
private static Map<String, String> h = new HashMap<String, String>();
|
||||
static {
|
||||
h.put("", "application/octet-stream");
|
||||
h.put("323", "text/h323");
|
||||
h.put("acx", "application/internet-property-stream");
|
||||
h.put("ai", "application/postscript");
|
||||
h.put("aif", "audio/x-aiff");
|
||||
h.put("aifc", "audio/x-aiff");
|
||||
h.put("aiff", "audio/x-aiff");
|
||||
h.put("asf", "video/x-ms-asf");
|
||||
h.put("asr", "video/x-ms-asf");
|
||||
h.put("asx", "video/x-ms-asf");
|
||||
h.put("au", "audio/basic");
|
||||
h.put("avi", "video/x-msvideo");
|
||||
h.put("axs", "application/olescript");
|
||||
h.put("bas", "text/plain");
|
||||
h.put("bcpio", "application/x-bcpio");
|
||||
h.put("bin", "application/octet-stream");
|
||||
h.put("bmp", "image/bmp");
|
||||
h.put("c", "text/plain");
|
||||
h.put("cat", "application/vnd.ms-pkiseccat");
|
||||
h.put("cdf", "application/x-cdf");
|
||||
h.put("cer", "application/x-x509-ca-cert");
|
||||
h.put("class", "application/octet-stream");
|
||||
h.put("clp", "application/x-msclip");
|
||||
h.put("cmx", "image/x-cmx");
|
||||
h.put("cod", "image/cis-cod");
|
||||
h.put("cpio", "application/x-cpio");
|
||||
h.put("crd", "application/x-mscardfile");
|
||||
h.put("crl", "application/pkix-crl");
|
||||
h.put("crt", "application/x-x509-ca-cert");
|
||||
h.put("csh", "application/x-csh");
|
||||
h.put("css", "text/css");
|
||||
h.put("dcr", "application/x-director");
|
||||
h.put("der", "application/x-x509-ca-cert");
|
||||
h.put("dir", "application/x-director");
|
||||
h.put("dll", "application/x-msdownload");
|
||||
h.put("dms", "application/octet-stream");
|
||||
h.put("doc", "application/msword");
|
||||
h.put("dot", "application/msword");
|
||||
h.put("dvi", "application/x-dvi");
|
||||
h.put("dxr", "application/x-director");
|
||||
h.put("eps", "application/postscript");
|
||||
h.put("etx", "text/x-setext");
|
||||
h.put("evy", "application/envoy");
|
||||
h.put("exe", "application/octet-stream");
|
||||
h.put("fif", "application/fractals");
|
||||
h.put("flr", "x-world/x-vrml");
|
||||
h.put("gif", "image/gif");
|
||||
h.put("gtar", "application/x-gtar");
|
||||
h.put("gz", "application/x-gzip");
|
||||
h.put("h", "text/plain");
|
||||
h.put("hdf", "applicatin/x-hdf");
|
||||
h.put("hlp", "application/winhlp");
|
||||
h.put("hqx", "application/mac-binhex40");
|
||||
h.put("hta", "application/hta");
|
||||
h.put("htc", "text/x-component");
|
||||
h.put("htm", "text/html");
|
||||
h.put("html", "text/html");
|
||||
h.put("htt", "text/webviewhtml");
|
||||
h.put("ico", "image/x-icon");
|
||||
h.put("ief", "image/ief");
|
||||
h.put("iii", "application/x-iphone");
|
||||
h.put("ins", "application/x-internet-signup");
|
||||
h.put("isp", "application/x-internet-signup");
|
||||
h.put("jfif", "image/pipeg");
|
||||
h.put("jpe", "image/jpeg");
|
||||
h.put("jpeg", "image/jpeg");
|
||||
h.put("jpg", "image/jpeg");
|
||||
h.put("js", "application/x-javascript");
|
||||
h.put("latex", "application/x-latex");
|
||||
h.put("lha", "application/octet-stream");
|
||||
h.put("lsf", "video/x-la-asf");
|
||||
h.put("lsx", "video/x-la-asf");
|
||||
h.put("lzh", "application/octet-stream");
|
||||
h.put("m13", "application/x-msmediaview");
|
||||
h.put("m14", "application/x-msmediaview");
|
||||
h.put("m3u", "audio/x-mpegurl");
|
||||
h.put("man", "application/x-troff-man");
|
||||
h.put("mdb", "application/x-msaccess");
|
||||
h.put("me", "application/x-troff-me");
|
||||
h.put("mht", "message/rfc822");
|
||||
h.put("mhtml", "message/rfc822");
|
||||
h.put("mid", "audio/mid");
|
||||
h.put("mny", "application/x-msmoney");
|
||||
h.put("mov", "video/quicktime");
|
||||
h.put("movie", "video/x-sgi-movie");
|
||||
h.put("mp2", "video/mpeg");
|
||||
h.put("mp3", "audio/mpeg");
|
||||
h.put("mpa", "video/mpeg");
|
||||
h.put("mpe", "video/mpeg");
|
||||
h.put("mpeg", "video/mpeg");
|
||||
h.put("mpg", "video/mpeg");
|
||||
h.put("mpp", "application/vnd.ms-project");
|
||||
h.put("mpv2", "video/mpeg");
|
||||
h.put("ms", "application/x-troff-ms");
|
||||
h.put("mvb", "application/x-msmediaview");
|
||||
h.put("nws", "message/rfc822");
|
||||
h.put("oda", "application/oda");
|
||||
h.put("p10", "application/pkcs10");
|
||||
h.put("p12", "application/x-pkcs12");
|
||||
h.put("p7b", "application/x-pkcs7-certificates");
|
||||
h.put("p7c", "application/x-pkcs7-mime");
|
||||
h.put("p7m", "application/x-pkcs7-mime");
|
||||
h.put("p7r", "application/x-pkcs7-certreqresp");
|
||||
h.put("p7s", "application/x-pkcs7-signature");
|
||||
h.put("pbm", "image/x-portable-bitmap");
|
||||
h.put("pdf", "application/pdf");
|
||||
h.put("pfx", "application/x-pkcs12");
|
||||
h.put("pgm", "image/x-portable-graymap");
|
||||
h.put("pko", "application/ynd.ms-pkipko");
|
||||
h.put("pma", "application/x-perfmon");
|
||||
h.put("pmc", "application/x-perfmon");
|
||||
h.put("pml", "application/x-perfmon");
|
||||
h.put("pmr", "application/x-perfmon");
|
||||
h.put("pmw", "application/x-perfmon");
|
||||
h.put("pnm", "image/x-portable-anymap");
|
||||
h.put("pot,", "application/vnd.ms-powerpoint");
|
||||
h.put("ppm", "image/x-portable-pixmap");
|
||||
h.put("pps", "application/vnd.ms-powerpoint");
|
||||
h.put("ppt", "application/vnd.ms-powerpoint");
|
||||
h.put("prf", "application/pics-rules");
|
||||
h.put("ps", "application/postscript");
|
||||
h.put("pub", "application/x-mspublisher");
|
||||
h.put("qt", "video/quicktime");
|
||||
h.put("ra", "audio/x-pn-realaudio");
|
||||
h.put("ram", "audio/x-pn-realaudio");
|
||||
h.put("ras", "image/x-cmu-raster");
|
||||
h.put("rgb", "image/x-rgb");
|
||||
h.put("rmi", "audio/mid");
|
||||
h.put("roff", "application/x-troff");
|
||||
h.put("rtf", "application/rtf");
|
||||
h.put("rtx", "text/richtext");
|
||||
h.put("scd", "application/x-msschedule");
|
||||
h.put("sct", "text/scriptlet");
|
||||
h.put("setpay", "application/set-payment-initiation");
|
||||
h.put("setreg", "application/set-registration-initiation");
|
||||
h.put("sh", "application/x-sh");
|
||||
h.put("shar", "application/x-shar");
|
||||
h.put("sit", "application/x-stuffit");
|
||||
h.put("snd", "audio/basic");
|
||||
h.put("spc", "application/x-pkcs7-certificates");
|
||||
h.put("spl", "application/futuresplash");
|
||||
h.put("src", "application/x-wais-source");
|
||||
h.put("sst", "application/vnd.ms-pkicertstore");
|
||||
h.put("stl", "application/vnd.ms-pkistl");
|
||||
h.put("stm", "text/html");
|
||||
h.put("svg", "image/svg+xml");
|
||||
h.put("sv4cpio", "application/x-sv4cpio");
|
||||
h.put("sv4crc", "application/x-sv4crc");
|
||||
h.put("swf", "application/x-shockwave-flash");
|
||||
h.put("t", "application/x-troff");
|
||||
h.put("tar", "application/x-tar");
|
||||
h.put("tcl", "application/x-tcl");
|
||||
h.put("tex", "application/x-tex");
|
||||
h.put("texi", "application/x-texinfo");
|
||||
h.put("texinfo", "application/x-texinfo");
|
||||
h.put("tgz", "application/x-compressed");
|
||||
h.put("tif", "image/tiff");
|
||||
h.put("tiff", "image/tiff");
|
||||
h.put("tr", "application/x-troff");
|
||||
h.put("trm", "application/x-msterminal");
|
||||
h.put("tsv", "text/tab-separated-values");
|
||||
h.put("txt", "text/plain");
|
||||
h.put("uls", "text/iuls");
|
||||
h.put("ustar", "application/x-ustar");
|
||||
h.put("vcf", "text/x-vcard");
|
||||
h.put("vrml", "x-world/x-vrml");
|
||||
h.put("wav", "audio/x-wav");
|
||||
h.put("wcm", "application/vnd.ms-works");
|
||||
h.put("wdb", "application/vnd.ms-works");
|
||||
h.put("wks", "application/vnd.ms-works");
|
||||
h.put("wmf", "application/x-msmetafile");
|
||||
h.put("wps", "application/vnd.ms-works");
|
||||
h.put("wri", "application/x-mswrite");
|
||||
h.put("wrl", "x-world/x-vrml");
|
||||
h.put("wrz", "x-world/x-vrml");
|
||||
h.put("xaf", "x-world/x-vrml");
|
||||
h.put("xbm", "image/x-xbitmap");
|
||||
h.put("xla", "application/vnd.ms-excel");
|
||||
h.put("xlc", "application/vnd.ms-excel");
|
||||
h.put("xlm", "application/vnd.ms-excel");
|
||||
h.put("xls", "application/vnd.ms-excel");
|
||||
h.put("xlt", "application/vnd.ms-excel");
|
||||
h.put("xlw", "application/vnd.ms-excel");
|
||||
h.put("xof", "x-world/x-vrml");
|
||||
h.put("xpm", "image/x-xpixmap");
|
||||
h.put("xwd", "image/x-xwindowdump");
|
||||
h.put("z", "application/x-compress");
|
||||
h.put("zip", "application/zip");
|
||||
}
|
||||
|
||||
public static String getMimeType(String docType) {
|
||||
String mime = h.get(docType);
|
||||
if (mime == null) {
|
||||
mime = "application/octet-stream";
|
||||
}
|
||||
return mime;
|
||||
}
|
||||
|
||||
public static String parseSuffix(String url) {
|
||||
try {
|
||||
Matcher matcher = pattern.matcher(url);
|
||||
String[] spUrl = url.toString().split("/");
|
||||
int len = spUrl.length;
|
||||
String endUrl = spUrl[len - 1];
|
||||
if (matcher.find()) {
|
||||
String[] spEndUrl = endUrl.split("\\?");
|
||||
endUrl = spEndUrl[0];
|
||||
}
|
||||
String[] endUrlArr = endUrl.split("\\.");
|
||||
return endUrlArr[endUrlArr.length - 1];
|
||||
} catch (Exception e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.fengfei.lanproxy.server.config.web;
|
||||
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
|
||||
/**
|
||||
* 接口请求处理
|
||||
*
|
||||
* @author fengfei
|
||||
*
|
||||
*/
|
||||
public interface RequestHandler {
|
||||
|
||||
/**
|
||||
* 请求处理
|
||||
*
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
ResponseInfo request(FullHttpRequest request);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.fengfei.lanproxy.server.config.web;
|
||||
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
|
||||
/**
|
||||
* 请求拦截器
|
||||
*
|
||||
* @author fengfei
|
||||
*
|
||||
*/
|
||||
public interface RequestMiddleware {
|
||||
|
||||
/**
|
||||
* 请求预处理
|
||||
*
|
||||
* @param request
|
||||
*/
|
||||
void preRequest(FullHttpRequest request);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package org.fengfei.lanproxy.server.config.web;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class ResponseInfo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static final int CODE_OK = 20000;
|
||||
|
||||
public static final int CODE_INVILID_PARAMS = 40000;
|
||||
|
||||
public static final int CODE_UNAUTHORIZED = 40100;
|
||||
|
||||
public static final int CODE_API_NOT_FOUND = 40400;
|
||||
|
||||
public static final int CODE_SYSTEM_ERROR = 50000;
|
||||
|
||||
private int code;
|
||||
|
||||
private String message;
|
||||
|
||||
private Object data;
|
||||
|
||||
public static ResponseInfo build(int code, String message, Object data) {
|
||||
return new ResponseInfo(code, message, data);
|
||||
}
|
||||
|
||||
public static ResponseInfo build(int code, String message) {
|
||||
return new ResponseInfo(code, message);
|
||||
}
|
||||
|
||||
public static ResponseInfo build(Object data) {
|
||||
return new ResponseInfo(data);
|
||||
}
|
||||
|
||||
private ResponseInfo(int code, String message, Object data) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
private ResponseInfo(int code, String message) {
|
||||
this(code, message, null);
|
||||
}
|
||||
|
||||
private ResponseInfo(Object data) {
|
||||
this(ResponseInfo.CODE_OK, "success", data);
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public Object getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(Object data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package org.fengfei.lanproxy.server.config.web;
|
||||
|
||||
import org.fengfei.lanproxy.common.container.Container;
|
||||
import org.fengfei.lanproxy.server.config.ProxyConfig;
|
||||
import org.fengfei.lanproxy.server.config.web.routes.RouteConfig;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
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.stream.ChunkedWriteHandler;
|
||||
|
||||
public class WebConfigContainer implements Container {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(WebConfigContainer.class);
|
||||
|
||||
private NioEventLoopGroup serverWorkerGroup;
|
||||
|
||||
private NioEventLoopGroup serverBossGroup;
|
||||
|
||||
public WebConfigContainer() {
|
||||
|
||||
// 配置管理,并发处理很小,使用单线程处理网络事件
|
||||
serverBossGroup = new NioEventLoopGroup(1);
|
||||
serverWorkerGroup = new NioEventLoopGroup(1);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
ServerBootstrap httpServerBootstrap = new ServerBootstrap();
|
||||
httpServerBootstrap.group(serverBossGroup, serverWorkerGroup).channel(NioServerSocketChannel.class)
|
||||
.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) throws Exception {
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
|
||||
pipeline.addLast(new HttpServerCodec());
|
||||
pipeline.addLast(new HttpObjectAggregator(64 * 1024));
|
||||
pipeline.addLast(new ChunkedWriteHandler());
|
||||
pipeline.addLast(new HttpRequestHandler());
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
httpServerBootstrap.bind(ProxyConfig.getInstance().getConfigServerBind(),
|
||||
ProxyConfig.getInstance().getConfigServerPort()).get();
|
||||
logger.info("http server start on port " + ProxyConfig.getInstance().getConfigServerPort());
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
RouteConfig.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
serverBossGroup.shutdownGracefully();
|
||||
serverWorkerGroup.shutdownGracefully();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.fengfei.lanproxy.server.config.web.exception;
|
||||
|
||||
public class ContextException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private int code;
|
||||
|
||||
public ContextException(int code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public ContextException(int code) {
|
||||
super();
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package org.fengfei.lanproxy.server.config.web.routes;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.fengfei.lanproxy.common.JsonUtil;
|
||||
import org.fengfei.lanproxy.server.ProxyChannelManager;
|
||||
import org.fengfei.lanproxy.server.config.ProxyConfig;
|
||||
import org.fengfei.lanproxy.server.config.ProxyConfig.Client;
|
||||
import org.fengfei.lanproxy.server.config.web.ApiRoute;
|
||||
import org.fengfei.lanproxy.server.config.web.RequestHandler;
|
||||
import org.fengfei.lanproxy.server.config.web.RequestMiddleware;
|
||||
import org.fengfei.lanproxy.server.config.web.ResponseInfo;
|
||||
import org.fengfei.lanproxy.server.config.web.exception.ContextException;
|
||||
import org.fengfei.lanproxy.server.metrics.MetricsCollector;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
|
||||
/**
|
||||
* 接口实现
|
||||
*
|
||||
* @author fengfei
|
||||
*
|
||||
*/
|
||||
public class RouteConfig {
|
||||
|
||||
protected static final String AUTH_COOKIE_KEY = "token";
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(RouteConfig.class);
|
||||
|
||||
/** 管理员不能同时在多个地方登录 */
|
||||
private static String token;
|
||||
|
||||
public static void init() {
|
||||
|
||||
ApiRoute.addMiddleware(new RequestMiddleware() {
|
||||
|
||||
@Override
|
||||
public void preRequest(FullHttpRequest request) {
|
||||
String cookieHeader = request.headers().get(HttpHeaders.Names.COOKIE);
|
||||
boolean authenticated = false;
|
||||
if (cookieHeader != null) {
|
||||
String[] cookies = cookieHeader.split(";");
|
||||
for (String cookie : cookies) {
|
||||
String[] cookieArr = cookie.split("=");
|
||||
if (AUTH_COOKIE_KEY.equals(cookieArr[0].trim())) {
|
||||
if (cookieArr.length == 2 && cookieArr[1].equals(token)) {
|
||||
authenticated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String auth = request.headers().get(HttpHeaders.Names.AUTHORIZATION);
|
||||
if (!authenticated && auth != null) {
|
||||
String[] authArr = auth.split(" ");
|
||||
if (authArr.length == 2 && authArr[0].equals(ProxyConfig.getInstance().getConfigAdminUsername()) && authArr[1].equals(ProxyConfig.getInstance().getConfigAdminPassword())) {
|
||||
authenticated = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!request.getUri().equals("/login") && !authenticated) {
|
||||
throw new ContextException(ResponseInfo.CODE_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
logger.info("handle request for api {}", request.getUri());
|
||||
}
|
||||
});
|
||||
|
||||
// 获取配置详细信息
|
||||
ApiRoute.addRoute("/config/detail", new RequestHandler() {
|
||||
|
||||
@Override
|
||||
public ResponseInfo request(FullHttpRequest request) {
|
||||
List<Client> clients = ProxyConfig.getInstance().getClients();
|
||||
for (Client client : clients) {
|
||||
Channel channel = ProxyChannelManager.getCmdChannel(client.getClientKey());
|
||||
if (channel != null) {
|
||||
client.setStatus(1);// online
|
||||
} else {
|
||||
client.setStatus(0);// offline
|
||||
}
|
||||
}
|
||||
return ResponseInfo.build(ProxyConfig.getInstance().getClients());
|
||||
}
|
||||
});
|
||||
|
||||
// 更新配置
|
||||
ApiRoute.addRoute("/config/update", new RequestHandler() {
|
||||
|
||||
@Override
|
||||
public ResponseInfo request(FullHttpRequest request) {
|
||||
byte[] buf = new byte[request.content().readableBytes()];
|
||||
request.content().readBytes(buf);
|
||||
String config = new String(buf, Charset.forName("UTF-8"));
|
||||
List<Client> clients = JsonUtil.json2object(config, new TypeToken<List<Client>>() {
|
||||
});
|
||||
if (clients == null) {
|
||||
return ResponseInfo.build(ResponseInfo.CODE_INVILID_PARAMS, "Error json config");
|
||||
}
|
||||
|
||||
try {
|
||||
ProxyConfig.getInstance().update(config);
|
||||
} catch (Exception ex) {
|
||||
logger.error("config update error", ex);
|
||||
return ResponseInfo.build(ResponseInfo.CODE_INVILID_PARAMS, ex.getMessage());
|
||||
}
|
||||
|
||||
return ResponseInfo.build(ResponseInfo.CODE_OK, "success");
|
||||
}
|
||||
});
|
||||
|
||||
ApiRoute.addRoute("/login", new RequestHandler() {
|
||||
|
||||
@Override
|
||||
public ResponseInfo request(FullHttpRequest request) {
|
||||
byte[] buf = new byte[request.content().readableBytes()];
|
||||
request.content().readBytes(buf);
|
||||
String config = new String(buf);
|
||||
Map<String, String> loginParams = JsonUtil.json2object(config, new TypeToken<Map<String, String>>() {
|
||||
});
|
||||
if (loginParams == null) {
|
||||
return ResponseInfo.build(ResponseInfo.CODE_INVILID_PARAMS, "Error login info");
|
||||
}
|
||||
|
||||
String username = loginParams.get("username");
|
||||
String password = loginParams.get("password");
|
||||
if (username == null || password == null) {
|
||||
return ResponseInfo.build(ResponseInfo.CODE_INVILID_PARAMS, "Error username or password");
|
||||
}
|
||||
|
||||
if (username.equals(ProxyConfig.getInstance().getConfigAdminUsername()) && password.equals(ProxyConfig.getInstance().getConfigAdminPassword())) {
|
||||
token = UUID.randomUUID().toString().replace("-", "");
|
||||
return ResponseInfo.build(token);
|
||||
}
|
||||
|
||||
return ResponseInfo.build(ResponseInfo.CODE_INVILID_PARAMS, "Error username or password");
|
||||
}
|
||||
});
|
||||
|
||||
ApiRoute.addRoute("/logout", new RequestHandler() {
|
||||
|
||||
@Override
|
||||
public ResponseInfo request(FullHttpRequest request) {
|
||||
token = null;
|
||||
return ResponseInfo.build(ResponseInfo.CODE_OK, "success");
|
||||
}
|
||||
});
|
||||
|
||||
ApiRoute.addRoute("/metrics/get", new RequestHandler() {
|
||||
|
||||
@Override
|
||||
public ResponseInfo request(FullHttpRequest request) {
|
||||
return ResponseInfo.build(MetricsCollector.getAllMetrics());
|
||||
}
|
||||
});
|
||||
|
||||
ApiRoute.addRoute("/metrics/getandreset", new RequestHandler() {
|
||||
|
||||
@Override
|
||||
public ResponseInfo request(FullHttpRequest request) {
|
||||
return ResponseInfo.build(MetricsCollector.getAndResetAllMetrics());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
package org.fengfei.lanproxy.server.handlers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.fengfei.lanproxy.protocol.Constants;
|
||||
import org.fengfei.lanproxy.protocol.ProxyMessage;
|
||||
import org.fengfei.lanproxy.server.ProxyChannelManager;
|
||||
import org.fengfei.lanproxy.server.config.ProxyConfig;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author fengfei
|
||||
*
|
||||
*/
|
||||
public class ServerChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(ServerChannelHandler.class);
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception {
|
||||
logger.debug("ProxyMessage received {}", proxyMessage.getType());
|
||||
switch (proxyMessage.getType()) {
|
||||
case ProxyMessage.TYPE_HEARTBEAT:
|
||||
handleHeartbeatMessage(ctx, proxyMessage);
|
||||
break;
|
||||
case ProxyMessage.C_TYPE_AUTH:
|
||||
handleAuthMessage(ctx, proxyMessage);
|
||||
break;
|
||||
case ProxyMessage.TYPE_CONNECT:
|
||||
handleConnectMessage(ctx, proxyMessage);
|
||||
break;
|
||||
case ProxyMessage.TYPE_DISCONNECT:
|
||||
handleDisconnectMessage(ctx, proxyMessage);
|
||||
break;
|
||||
case ProxyMessage.P_TYPE_TRANSFER:
|
||||
handleTransferMessage(ctx, proxyMessage);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTransferMessage(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
|
||||
Channel userChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
|
||||
if (userChannel != null) {
|
||||
ByteBuf buf = ctx.alloc().buffer(proxyMessage.getData().length);
|
||||
buf.writeBytes(proxyMessage.getData());
|
||||
userChannel.writeAndFlush(buf);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDisconnectMessage(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
|
||||
String clientKey = ctx.channel().attr(Constants.CLIENT_KEY).get();
|
||||
|
||||
// 代理连接没有连上服务器由控制连接发送用户端断开连接消息
|
||||
if (clientKey == null) {
|
||||
String userId = proxyMessage.getUri();
|
||||
Channel userChannel = ProxyChannelManager.removeUserChannelFromCmdChannel(ctx.channel(), userId);
|
||||
if (userChannel != null) {
|
||||
// 数据发送完成后再关闭连接,解决http1.0数据传输问题
|
||||
userChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Channel cmdChannel = ProxyChannelManager.getCmdChannel(clientKey);
|
||||
if (cmdChannel == null) {
|
||||
logger.warn("ConnectMessage:error cmd channel key {}", ctx.channel().attr(Constants.CLIENT_KEY).get());
|
||||
return;
|
||||
}
|
||||
|
||||
Channel userChannel = ProxyChannelManager.removeUserChannelFromCmdChannel(cmdChannel, ctx.channel().attr(Constants.USER_ID).get());
|
||||
if (userChannel != null) {
|
||||
// 数据发送完成后再关闭连接,解决http1.0数据传输问题
|
||||
userChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
|
||||
ctx.channel().attr(Constants.NEXT_CHANNEL).remove();
|
||||
ctx.channel().attr(Constants.CLIENT_KEY).remove();
|
||||
ctx.channel().attr(Constants.USER_ID).remove();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleConnectMessage(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
|
||||
String uri = proxyMessage.getUri();
|
||||
if (uri == null) {
|
||||
ctx.channel().close();
|
||||
logger.warn("ConnectMessage:null uri");
|
||||
return;
|
||||
}
|
||||
|
||||
String[] tokens = uri.split("@");
|
||||
if (tokens.length != 2) {
|
||||
ctx.channel().close();
|
||||
logger.warn("ConnectMessage:error uri");
|
||||
return;
|
||||
}
|
||||
|
||||
Channel cmdChannel = ProxyChannelManager.getCmdChannel(tokens[1]);
|
||||
if (cmdChannel == null) {
|
||||
ctx.channel().close();
|
||||
logger.warn("ConnectMessage:error cmd channel key {}", tokens[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
Channel userChannel = ProxyChannelManager.getUserChannel(cmdChannel, tokens[0]);
|
||||
if (userChannel != null) {
|
||||
ctx.channel().attr(Constants.USER_ID).set(tokens[0]);
|
||||
ctx.channel().attr(Constants.CLIENT_KEY).set(tokens[1]);
|
||||
ctx.channel().attr(Constants.NEXT_CHANNEL).set(userChannel);
|
||||
userChannel.attr(Constants.NEXT_CHANNEL).set(ctx.channel());
|
||||
// 代理客户端与后端服务器连接成功,修改用户连接为可读状态
|
||||
userChannel.config().setOption(ChannelOption.AUTO_READ, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleHeartbeatMessage(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
|
||||
ProxyMessage heartbeatMessage = new ProxyMessage();
|
||||
heartbeatMessage.setSerialNumber(heartbeatMessage.getSerialNumber());
|
||||
heartbeatMessage.setType(ProxyMessage.TYPE_HEARTBEAT);
|
||||
logger.debug("response heartbeat message {}", ctx.channel());
|
||||
ctx.channel().writeAndFlush(heartbeatMessage);
|
||||
}
|
||||
|
||||
private void handleAuthMessage(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
|
||||
String clientKey = proxyMessage.getUri();
|
||||
List<Integer> ports = ProxyConfig.getInstance().getClientInetPorts(clientKey);
|
||||
if (ports == null) {
|
||||
logger.info("error clientKey {}, {}", clientKey, ctx.channel());
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
|
||||
Channel channel = ProxyChannelManager.getCmdChannel(clientKey);
|
||||
if (channel != null) {
|
||||
logger.warn("exist channel for key {}, {}", clientKey, channel);
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("set port => channel, {}, {}, {}", clientKey, ports, ctx.channel());
|
||||
ProxyChannelManager.addCmdChannel(ports, clientKey, ctx.channel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
|
||||
Channel userChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
|
||||
if (userChannel != null) {
|
||||
userChannel.config().setOption(ChannelOption.AUTO_READ, ctx.channel().isWritable());
|
||||
}
|
||||
|
||||
super.channelWritabilityChanged(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
Channel userChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
|
||||
if (userChannel != null && userChannel.isActive()) {
|
||||
String clientKey = ctx.channel().attr(Constants.CLIENT_KEY).get();
|
||||
String userId = ctx.channel().attr(Constants.USER_ID).get();
|
||||
Channel cmdChannel = ProxyChannelManager.getCmdChannel(clientKey);
|
||||
if (cmdChannel != null) {
|
||||
ProxyChannelManager.removeUserChannelFromCmdChannel(cmdChannel, userId);
|
||||
} else {
|
||||
logger.warn("null cmdChannel, clientKey is {}", clientKey);
|
||||
}
|
||||
|
||||
// 数据发送完成后再关闭连接,解决http1.0数据传输问题
|
||||
userChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
|
||||
userChannel.close();
|
||||
} else {
|
||||
ProxyChannelManager.removeCmdChannel(ctx.channel());
|
||||
}
|
||||
|
||||
super.channelInactive(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
logger.error("exception caught", cause);
|
||||
super.exceptionCaught(ctx, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package org.fengfei.lanproxy.server.handlers;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.fengfei.lanproxy.protocol.Constants;
|
||||
import org.fengfei.lanproxy.protocol.ProxyMessage;
|
||||
import org.fengfei.lanproxy.server.ProxyChannelManager;
|
||||
import org.fengfei.lanproxy.server.config.ProxyConfig;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
|
||||
/**
|
||||
* 处理服务端 channel.
|
||||
*/
|
||||
public class UserChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
|
||||
|
||||
private static AtomicLong userIdProducer = new AtomicLong(0);
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
|
||||
// 当出现异常就关闭连接
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) throws Exception {
|
||||
|
||||
// 通知代理客户端
|
||||
Channel userChannel = ctx.channel();
|
||||
Channel proxyChannel = userChannel.attr(Constants.NEXT_CHANNEL).get();
|
||||
if (proxyChannel == null) {
|
||||
|
||||
// 该端口还没有代理客户端
|
||||
ctx.channel().close();
|
||||
} else {
|
||||
byte[] bytes = new byte[buf.readableBytes()];
|
||||
buf.readBytes(bytes);
|
||||
String userId = ProxyChannelManager.getUserChannelUserId(userChannel);
|
||||
ProxyMessage proxyMessage = new ProxyMessage();
|
||||
proxyMessage.setType(ProxyMessage.P_TYPE_TRANSFER);
|
||||
proxyMessage.setUri(userId);
|
||||
proxyMessage.setData(bytes);
|
||||
proxyChannel.writeAndFlush(proxyMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
Channel userChannel = ctx.channel();
|
||||
InetSocketAddress sa = (InetSocketAddress) userChannel.localAddress();
|
||||
Channel cmdChannel = ProxyChannelManager.getCmdChannel(sa.getPort());
|
||||
|
||||
if (cmdChannel == null) {
|
||||
|
||||
// 该端口还没有代理客户端
|
||||
ctx.channel().close();
|
||||
} else {
|
||||
String userId = newUserId();
|
||||
String lanInfo = ProxyConfig.getInstance().getLanInfo(sa.getPort());
|
||||
// 用户连接到代理服务器时,设置用户连接不可读,等待代理后端服务器连接成功后再改变为可读状态
|
||||
userChannel.config().setOption(ChannelOption.AUTO_READ, false);
|
||||
ProxyChannelManager.addUserChannelToCmdChannel(cmdChannel, userId, userChannel);
|
||||
ProxyMessage proxyMessage = new ProxyMessage();
|
||||
proxyMessage.setType(ProxyMessage.TYPE_CONNECT);
|
||||
proxyMessage.setUri(userId);
|
||||
proxyMessage.setData(lanInfo.getBytes());
|
||||
cmdChannel.writeAndFlush(proxyMessage);
|
||||
}
|
||||
|
||||
super.channelActive(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
|
||||
// 通知代理客户端
|
||||
Channel userChannel = ctx.channel();
|
||||
InetSocketAddress sa = (InetSocketAddress) userChannel.localAddress();
|
||||
Channel cmdChannel = ProxyChannelManager.getCmdChannel(sa.getPort());
|
||||
if (cmdChannel == null) {
|
||||
|
||||
// 该端口还没有代理客户端
|
||||
ctx.channel().close();
|
||||
} else {
|
||||
|
||||
// 用户连接断开,从控制连接中移除
|
||||
String userId = ProxyChannelManager.getUserChannelUserId(userChannel);
|
||||
ProxyChannelManager.removeUserChannelFromCmdChannel(cmdChannel, userId);
|
||||
Channel proxyChannel = userChannel.attr(Constants.NEXT_CHANNEL).get();
|
||||
if (proxyChannel != null && proxyChannel.isActive()) {
|
||||
proxyChannel.attr(Constants.NEXT_CHANNEL).remove();
|
||||
proxyChannel.attr(Constants.CLIENT_KEY).remove();
|
||||
proxyChannel.attr(Constants.USER_ID).remove();
|
||||
|
||||
proxyChannel.config().setOption(ChannelOption.AUTO_READ, true);
|
||||
// 通知客户端,用户连接已经断开
|
||||
ProxyMessage proxyMessage = new ProxyMessage();
|
||||
proxyMessage.setType(ProxyMessage.TYPE_DISCONNECT);
|
||||
proxyMessage.setUri(userId);
|
||||
proxyChannel.writeAndFlush(proxyMessage);
|
||||
}
|
||||
}
|
||||
|
||||
super.channelInactive(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
|
||||
|
||||
// 通知代理客户端
|
||||
Channel userChannel = ctx.channel();
|
||||
InetSocketAddress sa = (InetSocketAddress) userChannel.localAddress();
|
||||
Channel cmdChannel = ProxyChannelManager.getCmdChannel(sa.getPort());
|
||||
if (cmdChannel == null) {
|
||||
|
||||
// 该端口还没有代理客户端
|
||||
ctx.channel().close();
|
||||
} else {
|
||||
Channel proxyChannel = userChannel.attr(Constants.NEXT_CHANNEL).get();
|
||||
if (proxyChannel != null) {
|
||||
proxyChannel.config().setOption(ChannelOption.AUTO_READ, userChannel.isWritable());
|
||||
}
|
||||
}
|
||||
|
||||
super.channelWritabilityChanged(ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为用户连接产生ID
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static String newUserId() {
|
||||
return String.valueOf(userIdProducer.incrementAndGet());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package org.fengfei.lanproxy.server.metrics;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class Metrics implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private int port;
|
||||
|
||||
private long readBytes;
|
||||
|
||||
private long wroteBytes;
|
||||
|
||||
private long readMsgs;
|
||||
|
||||
private long wroteMsgs;
|
||||
|
||||
private int channels;
|
||||
|
||||
private long timestamp;
|
||||
|
||||
public long getReadBytes() {
|
||||
return readBytes;
|
||||
}
|
||||
|
||||
public void setReadBytes(long readBytes) {
|
||||
this.readBytes = readBytes;
|
||||
}
|
||||
|
||||
public long getWroteBytes() {
|
||||
return wroteBytes;
|
||||
}
|
||||
|
||||
public void setWroteBytes(long wroteBytes) {
|
||||
this.wroteBytes = wroteBytes;
|
||||
}
|
||||
|
||||
public long getReadMsgs() {
|
||||
return readMsgs;
|
||||
}
|
||||
|
||||
public void setReadMsgs(long readMsgs) {
|
||||
this.readMsgs = readMsgs;
|
||||
}
|
||||
|
||||
public long getWroteMsgs() {
|
||||
return wroteMsgs;
|
||||
}
|
||||
|
||||
public void setWroteMsgs(long wroteMsgs) {
|
||||
this.wroteMsgs = wroteMsgs;
|
||||
}
|
||||
|
||||
public int getChannels() {
|
||||
return channels;
|
||||
}
|
||||
|
||||
public void setChannels(int channels) {
|
||||
this.channels = channels;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package org.fengfei.lanproxy.server.metrics;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public class MetricsCollector {
|
||||
|
||||
private static Map<Integer, MetricsCollector> metricsCollectors = new ConcurrentHashMap<Integer, MetricsCollector>();
|
||||
|
||||
private Integer port;
|
||||
|
||||
private AtomicLong readBytes = new AtomicLong();
|
||||
|
||||
private AtomicLong wroteBytes = new AtomicLong();
|
||||
|
||||
private AtomicLong readMsgs = new AtomicLong();
|
||||
|
||||
private AtomicLong wroteMsgs = new AtomicLong();
|
||||
|
||||
private AtomicInteger channels = new AtomicInteger();
|
||||
|
||||
private MetricsCollector() {
|
||||
}
|
||||
|
||||
public static MetricsCollector getCollector(Integer port) {
|
||||
MetricsCollector collector = metricsCollectors.get(port);
|
||||
if (collector == null) {
|
||||
synchronized (metricsCollectors) {
|
||||
collector = metricsCollectors.get(port);
|
||||
if (collector == null) {
|
||||
collector = new MetricsCollector();
|
||||
collector.setPort(port);
|
||||
metricsCollectors.put(port, collector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return collector;
|
||||
}
|
||||
|
||||
public static List<Metrics> getAndResetAllMetrics() {
|
||||
List<Metrics> allMetrics = new ArrayList<Metrics>();
|
||||
Iterator<Entry<Integer, MetricsCollector>> ite = metricsCollectors.entrySet().iterator();
|
||||
while (ite.hasNext()) {
|
||||
allMetrics.add(ite.next().getValue().getAndResetMetrics());
|
||||
}
|
||||
|
||||
return allMetrics;
|
||||
}
|
||||
|
||||
public static List<Metrics> getAllMetrics() {
|
||||
List<Metrics> allMetrics = new ArrayList<Metrics>();
|
||||
Iterator<Entry<Integer, MetricsCollector>> ite = metricsCollectors.entrySet().iterator();
|
||||
while (ite.hasNext()) {
|
||||
allMetrics.add(ite.next().getValue().getMetrics());
|
||||
}
|
||||
|
||||
return allMetrics;
|
||||
}
|
||||
|
||||
public Metrics getAndResetMetrics() {
|
||||
Metrics metrics = new Metrics();
|
||||
metrics.setChannels(channels.get());
|
||||
metrics.setPort(port);
|
||||
metrics.setReadBytes(readBytes.getAndSet(0));
|
||||
metrics.setWroteBytes(wroteBytes.getAndSet(0));
|
||||
metrics.setTimestamp(System.currentTimeMillis());
|
||||
metrics.setReadMsgs(readMsgs.getAndSet(0));
|
||||
metrics.setWroteMsgs(wroteMsgs.getAndSet(0));
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
public Metrics getMetrics() {
|
||||
Metrics metrics = new Metrics();
|
||||
metrics.setChannels(channels.get());
|
||||
metrics.setPort(port);
|
||||
metrics.setReadBytes(readBytes.get());
|
||||
metrics.setWroteBytes(wroteBytes.get());
|
||||
metrics.setTimestamp(System.currentTimeMillis());
|
||||
metrics.setReadMsgs(readMsgs.get());
|
||||
metrics.setWroteMsgs(wroteMsgs.get());
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
public void incrementReadBytes(long bytes) {
|
||||
readBytes.addAndGet(bytes);
|
||||
}
|
||||
|
||||
public void incrementWroteBytes(long bytes) {
|
||||
wroteBytes.addAndGet(bytes);
|
||||
}
|
||||
|
||||
public void incrementReadMsgs(long msgs) {
|
||||
readMsgs.addAndGet(msgs);
|
||||
}
|
||||
|
||||
public void incrementWroteMsgs(long msgs) {
|
||||
wroteMsgs.addAndGet(msgs);
|
||||
}
|
||||
|
||||
public AtomicInteger getChannels() {
|
||||
return channels;
|
||||
}
|
||||
|
||||
public Integer getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(Integer port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.fengfei.lanproxy.server.metrics.handler;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import org.fengfei.lanproxy.server.metrics.MetricsCollector;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
|
||||
public class BytesMetricsHandler extends ChannelDuplexHandler {
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
InetSocketAddress sa = (InetSocketAddress) ctx.channel().localAddress();
|
||||
MetricsCollector metricsCollector = MetricsCollector.getCollector(sa.getPort());
|
||||
metricsCollector.incrementReadBytes(((ByteBuf) msg).readableBytes());
|
||||
metricsCollector.incrementReadMsgs(1);
|
||||
ctx.fireChannelRead(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||
InetSocketAddress sa = (InetSocketAddress) ctx.channel().localAddress();
|
||||
MetricsCollector metricsCollector = MetricsCollector.getCollector(sa.getPort());
|
||||
metricsCollector.incrementWroteBytes(((ByteBuf) msg).readableBytes());
|
||||
metricsCollector.incrementWroteMsgs(1);
|
||||
super.write(ctx, msg, promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
InetSocketAddress sa = (InetSocketAddress) ctx.channel().localAddress();
|
||||
MetricsCollector.getCollector(sa.getPort()).getChannels().incrementAndGet();
|
||||
super.channelActive(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
InetSocketAddress sa = (InetSocketAddress) ctx.channel().localAddress();
|
||||
MetricsCollector.getCollector(sa.getPort()).getChannels().decrementAndGet();
|
||||
super.channelInactive(ctx);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
server.bind=0.0.0.0
|
||||
server.port=4900
|
||||
# 服务启动接收请求端口4900
|
||||
|
||||
# ssl是否开启,默认不开启
|
||||
server.ssl.enable=false
|
||||
server.ssl.bind=0.0.0.0
|
||||
server.ssl.port=4993
|
||||
server.ssl.jksPath=test.jks
|
||||
server.ssl.keyStorePassword=123456
|
||||
server.ssl.keyManagerPassword=123456
|
||||
server.ssl.needsClientAuth=false
|
||||
|
||||
# 8090服务端启动访问端口,账号密码
|
||||
config.server.bind=0.0.0.0
|
||||
config.server.port=8090
|
||||
config.admin.username=admin
|
||||
config.admin.password=qq755141
|
||||
@@ -0,0 +1,13 @@
|
||||
log4j.rootLogger=info,R
|
||||
|
||||
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n
|
||||
|
||||
log4j.appender.R=org.apache.log4j.DailyRollingFileAppender
|
||||
log4j.appender.R.DatePattern='_'yyyy-MM-dd'.log'
|
||||
log4j.appender.R.File=${app.home}/logs/server.log
|
||||
log4j.appender.R.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.R.layout.ConversionPattern=%d %p [%c] - <%m>%n
|
||||
|
||||
log4j.logger.io.netty=warn
|
||||
@@ -0,0 +1,11 @@
|
||||
@echo off & setlocal enabledelayedexpansion
|
||||
title lanproxy-server
|
||||
cd %~dp0
|
||||
|
||||
set LIB_JARS=""
|
||||
cd ..\lib
|
||||
for %%i in (*) do set LIB_JARS=!LIB_JARS!;..\lib\%%i
|
||||
cd ..\bin
|
||||
|
||||
java -Dapp.home=../ -Xms64m -Xmx1024m -classpath ..\conf;%LIB_JARS% org.fengfei.lanproxy.server.ProxyServerContainer
|
||||
goto end
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
cd `dirname $0`
|
||||
cd ..
|
||||
DEPLOY_DIR=`pwd`
|
||||
CONF_DIR=$DEPLOY_DIR/conf
|
||||
LOGS_DIR=$DEPLOY_DIR/logs
|
||||
|
||||
APP_MAINCLASS=org.fengfei.lanproxy.server.ProxyServerContainer
|
||||
|
||||
PIDS=`ps -ef | grep -v grep | grep "$CONF_DIR" |awk '{print $2}'`
|
||||
if [ -n "$PIDS" ]; then
|
||||
echo "ERROR: already started!"
|
||||
echo "PID: $PIDS"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d $LOGS_DIR ]; then
|
||||
mkdir $LOGS_DIR
|
||||
fi
|
||||
STDOUT_FILE=$LOGS_DIR/stdout.log
|
||||
CLOG_FILE=$LOGS_DIR/gc.log
|
||||
|
||||
LIB_DIR=$DEPLOY_DIR/lib
|
||||
LIB_JARS=`ls $LIB_DIR|grep .jar|awk '{print "'$LIB_DIR'/"$0}'| xargs | sed "s/ /:/g"`
|
||||
|
||||
JAVA_OPTS=" -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true "
|
||||
JAVA_DEBUG_OPTS=""
|
||||
if [ "$1" = "debug" ]; then
|
||||
JAVA_DEBUG_OPTS=" -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n "
|
||||
fi
|
||||
JAVA_JMX_OPTS=""
|
||||
if [ "$1" = "jmx" ]; then
|
||||
JAVA_JMX_OPTS=" -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false "
|
||||
fi
|
||||
|
||||
JAVA_MEM_OPTS=""
|
||||
#JAVA_MEM_OPTS="-server -Xms5120M -Xmx5120M -Xmn1024M -Xnoclassgc -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled -XX:+CMSParallelRemarkEnabled -XX:CMSInitiatingOccupancyFraction=80 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+PrintClassHistogram -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -Xloggc:$CLOG_FILE"
|
||||
echo -e "Starting the proxy server ...\c"
|
||||
nohup java -Dapp.home=$DEPLOY_DIR $JAVA_OPTS $JAVA_MEM_OPTS $JAVA_DEBUG_OPTS $JAVA_JMX_OPTS -classpath $CONF_DIR:$LIB_JARS $APP_MAINCLASS >$STDOUT_FILE 2>&1 &
|
||||
sleep 1
|
||||
echo "started"
|
||||
PIDS=`ps -ef | grep java | grep "$DEPLOY_DIR" | awk '{print $2}'`
|
||||
echo "PID: $PIDS"
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
cd `dirname $0`
|
||||
BIN_DIR=`pwd`
|
||||
cd ..
|
||||
DEPLOY_DIR=`pwd`
|
||||
LOGS_DIR=$DEPLOY_DIR/logs
|
||||
if [ ! -d $LOGS_DIR ]; then
|
||||
mkdir $LOGS_DIR
|
||||
fi
|
||||
STDOUT_FILE=$LOGS_DIR/stdout.log
|
||||
|
||||
PID=`ps -ef | grep -v grep | grep "$DEPLOY_DIR/conf" | awk '{print $2}'`
|
||||
echo "PID: $PID"
|
||||
if [ -z "$PID" ]; then
|
||||
echo "ERROR: The proxy server does not started!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "Stopping the proxy server ...\c"
|
||||
kill $PID > $STDOUT_FILE 2>&1
|
||||
|
||||
COUNT=0
|
||||
while [ $COUNT -lt 1 ]; do
|
||||
echo -e ".\c"
|
||||
sleep 1
|
||||
COUNT=1
|
||||
PID_EXIST=`ps -f -p $PID | grep java`
|
||||
if [ -n "$PID_EXIST" ]; then
|
||||
COUNT=0
|
||||
fi
|
||||
done
|
||||
|
||||
echo "stopped"
|
||||
echo "PID: $PID"
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.fengfei.lanproxy.server.test;
|
||||
|
||||
import org.fengfei.lanproxy.server.ProxyServerContainer;
|
||||
|
||||
public class ServerMainTest {
|
||||
|
||||
public static void main(String[] args) {
|
||||
ProxyServerContainer.main(args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
server.bind=0.0.0.0
|
||||
server.port=4900
|
||||
|
||||
server.ssl.enable=true
|
||||
server.ssl.bind=0.0.0.0
|
||||
server.ssl.port=4993
|
||||
server.ssl.jksPath=test.jks
|
||||
server.ssl.keyStorePassword=123456
|
||||
server.ssl.keyManagerPassword=123456
|
||||
server.ssl.needsClientAuth=false
|
||||
|
||||
config.server.bind=0.0.0.0
|
||||
config.server.port=8099
|
||||
config.admin.username=admin
|
||||
config.admin.password=admin
|
||||
@@ -0,0 +1,7 @@
|
||||
log4j.rootLogger=info,stdout
|
||||
|
||||
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n
|
||||
|
||||
#log4j.logger.io.netty=warn
|
||||
@@ -0,0 +1,18 @@
|
||||
server.bind=0.0.0.0
|
||||
server.port=4900
|
||||
# 服务启动接收请求端口4900
|
||||
|
||||
# ssl是否开启,默认不开启
|
||||
server.ssl.enable=false
|
||||
server.ssl.bind=0.0.0.0
|
||||
server.ssl.port=4993
|
||||
server.ssl.jksPath=test.jks
|
||||
server.ssl.keyStorePassword=123456
|
||||
server.ssl.keyManagerPassword=123456
|
||||
server.ssl.needsClientAuth=false
|
||||
|
||||
# 8090服务端启动访问端口,账号密码
|
||||
config.server.bind=0.0.0.0
|
||||
config.server.port=8090
|
||||
config.admin.username=admin
|
||||
config.admin.password=qq755141
|
||||
@@ -0,0 +1,13 @@
|
||||
log4j.rootLogger=info,R
|
||||
|
||||
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n
|
||||
|
||||
log4j.appender.R=org.apache.log4j.DailyRollingFileAppender
|
||||
log4j.appender.R.DatePattern='_'yyyy-MM-dd'.log'
|
||||
log4j.appender.R.File=${app.home}/logs/server.log
|
||||
log4j.appender.R.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.R.layout.ConversionPattern=%d %p [%c] - <%m>%n
|
||||
|
||||
log4j.logger.io.netty=warn
|
||||
@@ -0,0 +1,11 @@
|
||||
@echo off & setlocal enabledelayedexpansion
|
||||
title lanproxy-server
|
||||
cd %~dp0
|
||||
|
||||
set LIB_JARS=""
|
||||
cd ..\lib
|
||||
for %%i in (*) do set LIB_JARS=!LIB_JARS!;..\lib\%%i
|
||||
cd ..\bin
|
||||
|
||||
java -Dapp.home=../ -Xms64m -Xmx1024m -classpath ..\conf;%LIB_JARS% org.fengfei.lanproxy.server.ProxyServerContainer
|
||||
goto end
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
cd `dirname $0`
|
||||
cd ..
|
||||
DEPLOY_DIR=`pwd`
|
||||
CONF_DIR=$DEPLOY_DIR/conf
|
||||
LOGS_DIR=$DEPLOY_DIR/logs
|
||||
|
||||
APP_MAINCLASS=org.fengfei.lanproxy.server.ProxyServerContainer
|
||||
|
||||
PIDS=`ps -ef | grep -v grep | grep "$CONF_DIR" |awk '{print $2}'`
|
||||
if [ -n "$PIDS" ]; then
|
||||
echo "ERROR: already started!"
|
||||
echo "PID: $PIDS"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d $LOGS_DIR ]; then
|
||||
mkdir $LOGS_DIR
|
||||
fi
|
||||
STDOUT_FILE=$LOGS_DIR/stdout.log
|
||||
CLOG_FILE=$LOGS_DIR/gc.log
|
||||
|
||||
LIB_DIR=$DEPLOY_DIR/lib
|
||||
LIB_JARS=`ls $LIB_DIR|grep .jar|awk '{print "'$LIB_DIR'/"$0}'| xargs | sed "s/ /:/g"`
|
||||
|
||||
JAVA_OPTS=" -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true "
|
||||
JAVA_DEBUG_OPTS=""
|
||||
if [ "$1" = "debug" ]; then
|
||||
JAVA_DEBUG_OPTS=" -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n "
|
||||
fi
|
||||
JAVA_JMX_OPTS=""
|
||||
if [ "$1" = "jmx" ]; then
|
||||
JAVA_JMX_OPTS=" -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false "
|
||||
fi
|
||||
|
||||
JAVA_MEM_OPTS=""
|
||||
#JAVA_MEM_OPTS="-server -Xms5120M -Xmx5120M -Xmn1024M -Xnoclassgc -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled -XX:+CMSParallelRemarkEnabled -XX:CMSInitiatingOccupancyFraction=80 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+PrintClassHistogram -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -Xloggc:$CLOG_FILE"
|
||||
echo -e "Starting the proxy server ...\c"
|
||||
nohup java -Dapp.home=$DEPLOY_DIR $JAVA_OPTS $JAVA_MEM_OPTS $JAVA_DEBUG_OPTS $JAVA_JMX_OPTS -classpath $CONF_DIR:$LIB_JARS $APP_MAINCLASS >$STDOUT_FILE 2>&1 &
|
||||
sleep 1
|
||||
echo "started"
|
||||
PIDS=`ps -ef | grep java | grep "$DEPLOY_DIR" | awk '{print $2}'`
|
||||
echo "PID: $PIDS"
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
cd `dirname $0`
|
||||
BIN_DIR=`pwd`
|
||||
cd ..
|
||||
DEPLOY_DIR=`pwd`
|
||||
LOGS_DIR=$DEPLOY_DIR/logs
|
||||
if [ ! -d $LOGS_DIR ]; then
|
||||
mkdir $LOGS_DIR
|
||||
fi
|
||||
STDOUT_FILE=$LOGS_DIR/stdout.log
|
||||
|
||||
PID=`ps -ef | grep -v grep | grep "$DEPLOY_DIR/conf" | awk '{print $2}'`
|
||||
echo "PID: $PID"
|
||||
if [ -z "$PID" ]; then
|
||||
echo "ERROR: The proxy server does not started!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "Stopping the proxy server ...\c"
|
||||
kill $PID > $STDOUT_FILE 2>&1
|
||||
|
||||
COUNT=0
|
||||
while [ $COUNT -lt 1 ]; do
|
||||
echo -e ".\c"
|
||||
sleep 1
|
||||
COUNT=1
|
||||
PID_EXIST=`ps -f -p $PID | grep java`
|
||||
if [ -n "$PID_EXIST" ]; then
|
||||
COUNT=0
|
||||
fi
|
||||
done
|
||||
|
||||
echo "stopped"
|
||||
echo "PID: $PID"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#Generated by Maven
|
||||
#Wed Jul 05 11:03:48 CST 2023
|
||||
groupId=org.fengfei
|
||||
artifactId=proxy-server
|
||||
version=0.1
|
||||
@@ -0,0 +1,36 @@
|
||||
org\fengfei\lanproxy\server\config\web\HttpRequestHandler.class
|
||||
org\fengfei\lanproxy\server\config\web\RequestMiddleware.class
|
||||
org\fengfei\lanproxy\server\config\web\ResponseInfo.class
|
||||
org\fengfei\lanproxy\server\config\web\WebConfigContainer$1.class
|
||||
org\fengfei\lanproxy\server\config\ProxyConfig$1.class
|
||||
org\fengfei\lanproxy\server\config\web\routes\RouteConfig$1.class
|
||||
org\fengfei\lanproxy\server\config\web\routes\RouteConfig$3$1.class
|
||||
org\fengfei\lanproxy\server\config\ProxyConfig$Client.class
|
||||
org\fengfei\lanproxy\server\ProxyServerContainer.class
|
||||
org\fengfei\lanproxy\server\SslContextCreator.class
|
||||
org\fengfei\lanproxy\server\metrics\handler\BytesMetricsHandler.class
|
||||
org\fengfei\lanproxy\server\config\web\routes\RouteConfig$3.class
|
||||
org\fengfei\lanproxy\server\ProxyServerContainer$2.class
|
||||
org\fengfei\lanproxy\server\config\web\exception\ContextException.class
|
||||
org\fengfei\lanproxy\server\config\web\ApiRoute.class
|
||||
org\fengfei\lanproxy\server\config\web\routes\RouteConfig$5.class
|
||||
org\fengfei\lanproxy\server\metrics\Metrics.class
|
||||
org\fengfei\lanproxy\server\config\web\RequestHandler.class
|
||||
org\fengfei\lanproxy\server\handlers\ServerChannelHandler.class
|
||||
org\fengfei\lanproxy\server\config\ProxyConfig.class
|
||||
org\fengfei\lanproxy\server\config\web\routes\RouteConfig$2.class
|
||||
org\fengfei\lanproxy\server\config\web\routes\RouteConfig$4$1.class
|
||||
org\fengfei\lanproxy\server\config\web\WebConfigContainer.class
|
||||
org\fengfei\lanproxy\server\ProxyChannelManager.class
|
||||
org\fengfei\lanproxy\server\ProxyServerContainer$3.class
|
||||
org\fengfei\lanproxy\server\config\ProxyConfig$ConfigChangedListener.class
|
||||
org\fengfei\lanproxy\server\config\web\routes\RouteConfig$7.class
|
||||
org\fengfei\lanproxy\server\handlers\UserChannelHandler.class
|
||||
org\fengfei\lanproxy\server\ProxyChannelManager$1.class
|
||||
org\fengfei\lanproxy\server\ProxyServerContainer$1.class
|
||||
org\fengfei\lanproxy\server\config\ProxyConfig$ClientProxyMapping.class
|
||||
org\fengfei\lanproxy\server\config\web\routes\RouteConfig$4.class
|
||||
org\fengfei\lanproxy\server\config\web\MimeType.class
|
||||
org\fengfei\lanproxy\server\metrics\MetricsCollector.class
|
||||
org\fengfei\lanproxy\server\config\web\routes\RouteConfig.class
|
||||
org\fengfei\lanproxy\server\config\web\routes\RouteConfig$6.class
|
||||
@@ -0,0 +1,18 @@
|
||||
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\metrics\MetricsCollector.java
|
||||
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\config\web\RequestMiddleware.java
|
||||
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\handlers\ServerChannelHandler.java
|
||||
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\metrics\handler\BytesMetricsHandler.java
|
||||
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\config\ProxyConfig.java
|
||||
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\config\web\RequestHandler.java
|
||||
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\metrics\Metrics.java
|
||||
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\config\web\routes\RouteConfig.java
|
||||
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\handlers\UserChannelHandler.java
|
||||
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\config\web\ApiRoute.java
|
||||
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\config\web\MimeType.java
|
||||
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\ProxyChannelManager.java
|
||||
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\config\web\exception\ContextException.java
|
||||
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\config\web\HttpRequestHandler.java
|
||||
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\config\web\ResponseInfo.java
|
||||
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\config\web\WebConfigContainer.java
|
||||
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\SslContextCreator.java
|
||||
E:\yx\lanproxy-0.1\proxy-server\src\main\java\org\fengfei\lanproxy\server\ProxyServerContainer.java
|
||||
@@ -0,0 +1 @@
|
||||
org\fengfei\lanproxy\server\test\ServerMainTest.class
|
||||
@@ -0,0 +1 @@
|
||||
E:\yx\lanproxy-0.1\proxy-server\src\test\java\org\fengfei\lanproxy\server\test\ServerMainTest.java
|
||||
@@ -0,0 +1,15 @@
|
||||
server.bind=0.0.0.0
|
||||
server.port=4900
|
||||
|
||||
server.ssl.enable=true
|
||||
server.ssl.bind=0.0.0.0
|
||||
server.ssl.port=4993
|
||||
server.ssl.jksPath=test.jks
|
||||
server.ssl.keyStorePassword=123456
|
||||
server.ssl.keyManagerPassword=123456
|
||||
server.ssl.needsClientAuth=false
|
||||
|
||||
config.server.bind=0.0.0.0
|
||||
config.server.port=8099
|
||||
config.admin.username=admin
|
||||
config.admin.password=admin
|
||||
@@ -0,0 +1,7 @@
|
||||
log4j.rootLogger=info,stdout
|
||||
|
||||
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n
|
||||
|
||||
#log4j.logger.io.netty=warn
|
||||
@@ -0,0 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>404 - LanProxy</title>
|
||||
</head>
|
||||
<body>404
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,150 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>配置编辑</title>
|
||||
|
||||
<!-- load a custom version of Ace editor -->
|
||||
<script src="/ace/ace.js"></script>
|
||||
|
||||
<!-- load the minimalist version of JSONEditor, which doesn't have Ace embedded -->
|
||||
<link href="/jsoneditor/dist/jsoneditor.css" rel="stylesheet"
|
||||
type="text/css">
|
||||
<script src="/jsoneditor/dist/jsoneditor-minimalist.js"></script>
|
||||
|
||||
<style type="text/css">
|
||||
#jsoneditor {
|
||||
width: 98%;
|
||||
height: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.op {
|
||||
width: 98%;
|
||||
margin: 10px auto;
|
||||
}
|
||||
|
||||
.op a {
|
||||
float: right;
|
||||
}
|
||||
|
||||
body, html {
|
||||
font-family: "DejaVu Sans", sans-serif;
|
||||
}
|
||||
|
||||
p, li {
|
||||
width: 98%;
|
||||
font-size: 10.5pt;
|
||||
}
|
||||
|
||||
code {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
div.jsoneditor {
|
||||
border: 1px solid #50a3a2;
|
||||
}
|
||||
|
||||
div.jsoneditor-menu {
|
||||
background-color: #50a3a2;
|
||||
border-bottom: 1px solid #50a3a2;
|
||||
}
|
||||
|
||||
button {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
outline: 0;
|
||||
background-color: #50a3a2;
|
||||
border: 0;
|
||||
padding: 8px 15px;
|
||||
color: white;
|
||||
border-radius: 3px;
|
||||
width: 180px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
-webkit-transition-duration: 0.25s;
|
||||
transition-duration: 0.25s;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="jsoneditor"></div>
|
||||
<div class="op">
|
||||
<button class="update-btn">提交更新</button>
|
||||
<a href="javascript:;" class="logout">退出登录</a>
|
||||
</div>
|
||||
<script src="/jquery/jquery-3.1.1.min.js"></script>
|
||||
<script>
|
||||
// create the editor
|
||||
var container = document.getElementById('jsoneditor');
|
||||
var options = {
|
||||
mode : 'code',
|
||||
ace : ace
|
||||
};
|
||||
var editor = new JSONEditor(container, options, {});
|
||||
|
||||
$.ajax({
|
||||
type : "POST",
|
||||
url : "/config/detail",
|
||||
contentType : "application/json; charset=utf-8",
|
||||
data : JSON.stringify(editor.get()),
|
||||
dataType : "json",
|
||||
success : function(data) {
|
||||
editor.set(data.data);
|
||||
},
|
||||
error : function(e) {
|
||||
if (e.responseJSON.code == 40100) {
|
||||
location.href = "/";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$(".update-btn").click(function() {
|
||||
$.ajax({
|
||||
type : "POST",
|
||||
url : "/config/update",
|
||||
contentType : "application/json; charset=utf-8",
|
||||
data : JSON.stringify(editor.get()),
|
||||
dataType : "json",
|
||||
success : function(data) {
|
||||
if (data.code == 20000) {
|
||||
alert("配置更新成功");
|
||||
window.location.href = "/config.html";
|
||||
}
|
||||
},
|
||||
error : function(e) {
|
||||
if (e.responseJSON.code == 40100) {
|
||||
location.href = "/";
|
||||
} else {
|
||||
alert(e.responseJSON.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(".logout").click(function() {
|
||||
$.ajax({
|
||||
type : "POST",
|
||||
url : "/logout",
|
||||
contentType : "application/json; charset=utf-8",
|
||||
data : JSON.stringify(editor.get()),
|
||||
dataType : "json",
|
||||
success : function(data) {
|
||||
if (data.code == 20000) {
|
||||
window.location.href = "/";
|
||||
}
|
||||
},
|
||||
error : function(e) {
|
||||
if (e.responseJSON.code == 40100) {
|
||||
location.href = "/";
|
||||
} else {
|
||||
alert(e.responseJSON.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,57 @@
|
||||
login=Sign in
|
||||
logout=Sign out
|
||||
username=Username
|
||||
password=Password
|
||||
|
||||
title=yxwlkj - help you expose a local server behind a NAT or firewall to the internet
|
||||
|
||||
menu.client.list=Client list
|
||||
menu.client.add=New client
|
||||
menu.client.config=Client config
|
||||
menu.client.statistics=Statistics
|
||||
|
||||
public.confirm.delete=Delete?
|
||||
public.options=Options
|
||||
public.edit=Edit
|
||||
public.submit=Submit
|
||||
public.delete=Delete
|
||||
public.ok=Ok
|
||||
public.cancel=Cancel
|
||||
public.back=Back
|
||||
public.notice.updatesuccess=Update successfully
|
||||
public.notice.addsuccess=Add successfully
|
||||
public.tips=Tips
|
||||
|
||||
client.list=Client list
|
||||
client.name=Client name
|
||||
client.key=Client key
|
||||
client.add=New client
|
||||
client.name.placeholder=
|
||||
client.key.placeholder=
|
||||
client.randomkey=Generate a random key
|
||||
client.notice.inputname=Client name is required
|
||||
client.notice.inputkey=Client key is required
|
||||
client.notice.addsuccess=New client has been added successfully
|
||||
client.edit=Edit client
|
||||
client.status=Status
|
||||
client.status.online=Up
|
||||
client.status.offline=Down
|
||||
|
||||
lan.proxyconfig=Proxy config
|
||||
lan.addnewconfig=New proxy config
|
||||
lan.editconfig=Edit proxy config
|
||||
lan.name=Proxy name
|
||||
lan.inetport=Internet port
|
||||
lan.inetport.placeholder=
|
||||
lan.ip=Backend ip
|
||||
lan.ip.placeholder=127.0.0.1:80
|
||||
lan.notice.inputname=Proxy config name is required
|
||||
lan.notice.inputinetport=Internet port is required
|
||||
lan.notice.inputlan=Backend server ip:port is required
|
||||
lan.notice.errorlan=Error backend info, should be ip:port
|
||||
lan.notice.errorport=Error port
|
||||
|
||||
statistics.inetport=Internet port
|
||||
statistics.inflow=Inflow data
|
||||
statistics.outflow=Outflow data
|
||||
statistics.channels=Current channels
|
||||
@@ -0,0 +1,57 @@
|
||||
login=登录
|
||||
logout=退出
|
||||
username=用户名
|
||||
password=密码
|
||||
|
||||
title=言逍网络科技-内网穿透工具
|
||||
|
||||
menu.client.list=客户端管理
|
||||
menu.client.add=添加客户端
|
||||
menu.client.config=配置管理
|
||||
menu.client.statistics=数据统计
|
||||
|
||||
public.confirm.delete=确定删除?
|
||||
public.options=操作
|
||||
public.submit=提交
|
||||
public.edit=编辑
|
||||
public.delete=删除
|
||||
public.ok=确定
|
||||
public.cancel=取消
|
||||
public.back=返回
|
||||
public.notice.updatesuccess=更新成功
|
||||
public.notice.addsuccess=添加成功
|
||||
public.tips=提示
|
||||
|
||||
client.list=客户端列表
|
||||
client.name=客户端名称
|
||||
client.key=客户端密钥
|
||||
client.add=添加客户端
|
||||
client.name.placeholder=请输入客户端名称
|
||||
client.key.placeholder=请输入客户端密钥
|
||||
client.randomkey=生成随机密钥
|
||||
client.notice.inputname=请输入客户端备注名称
|
||||
client.notice.inputkey=请输入客户端通信密钥
|
||||
client.notice.addsuccess=客户端添加成功
|
||||
client.edit=编辑客户端
|
||||
client.status=状态
|
||||
client.status.online=在线
|
||||
client.status.offline=离线
|
||||
|
||||
lan.proxyconfig=代理配置
|
||||
lan.addnewconfig=添加配置
|
||||
lan.editconfig=编辑配置
|
||||
lan.name=代理名称
|
||||
lan.inetport=公网端口
|
||||
lan.inetport.placeholder=请输入公网出口端口,请确保端口没有被其他程序占用
|
||||
lan.ip=后端IP端口
|
||||
lan.ip.placeholder=请输入后端代理信息,格式:127.0.0.1:80
|
||||
lan.notice.inputname=请输入代理信息备注名称
|
||||
lan.notice.inputinetport=请输入公网出口端口
|
||||
lan.notice.inputlan=请输入后端代理信息
|
||||
lan.notice.errorlan=后端代理信息格式错误
|
||||
lan.notice.errorport=端口错误
|
||||
|
||||
statistics.inetport=出口端口
|
||||
statistics.inflow=流入数据
|
||||
statistics.outflow=流出数据
|
||||
statistics.channels=当前连接
|
||||
@@ -0,0 +1,241 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>登录</title>
|
||||
<style type="text/css">
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Source Sans Pro', sans-serif;
|
||||
color: white;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
body ::-webkit-input-placeholder {
|
||||
/* WebKit browsers */
|
||||
font-family: 'Source Sans Pro', sans-serif;
|
||||
color: white;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
body :-moz-placeholder {
|
||||
/* Mozilla Firefox 4 to 18 */
|
||||
font-family: 'Source Sans Pro', sans-serif;
|
||||
color: white;
|
||||
opacity: 1;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
body ::-moz-placeholder {
|
||||
/* Mozilla Firefox 19+ */
|
||||
font-family: 'Source Sans Pro', sans-serif;
|
||||
color: white;
|
||||
opacity: 1;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
body :-ms-input-placeholder {
|
||||
/* Internet Explorer 10+ */
|
||||
font-family: 'Source Sans Pro', sans-serif;
|
||||
color: white;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
background: #393D49 !important;
|
||||
background: -webkit-linear-gradient(top left, #50a3a2 0%, #53e3a6 100%);
|
||||
background: linear-gradient(to bottom right, #50a3a2 0%, #53e3a6 100%);
|
||||
opacity: 0.8;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.wrapper.form-success .container h1 {
|
||||
-webkit-transform: translateY(85px);
|
||||
-ms-transform: translateY(85px);
|
||||
transform: translateY(85px);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 80px 0;
|
||||
height: 400px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.container h1 {
|
||||
font-size: 40px;
|
||||
-webkit-transition-duration: 1s;
|
||||
transition-duration: 1s;
|
||||
-webkit-transition-timing-function: ease-in-put;
|
||||
transition-timing-function: ease-in-put;
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
.form {
|
||||
padding: 20px 0;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.form input {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
outline: 0;
|
||||
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
width: 250px;
|
||||
border-radius: 3px;
|
||||
padding: 10px 15px;
|
||||
margin: 0 auto 10px auto;
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
color: white;
|
||||
-webkit-transition-duration: 0.25s;
|
||||
transition-duration: 0.25s;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.form input:hover {
|
||||
background-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.form input:focus {
|
||||
background-color: white;
|
||||
width: 300px;
|
||||
color: #53e3a6;
|
||||
}
|
||||
|
||||
.form button {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
outline: 0;
|
||||
background-color: white;
|
||||
border: 0;
|
||||
padding: 10px 15px;
|
||||
color: #53e3a6;
|
||||
border-radius: 3px;
|
||||
width: 250px;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
-webkit-transition-duration: 0.25s;
|
||||
transition-duration: 0.25s;
|
||||
}
|
||||
|
||||
.form button:hover {
|
||||
background-color: #f5f7f9;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 代码 开始 -->
|
||||
<div class="wrapper">
|
||||
<div class="container">
|
||||
<h1>言逍网络科技</h1>
|
||||
|
||||
<div class="form">
|
||||
<input type="text" class="username" placeholder="">
|
||||
<input type="password" class="password" placeholder="">
|
||||
<button type="submit" id="login-button"></button>
|
||||
</div>
|
||||
<h4>内网穿透后台</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="jquery/jquery-3.1.1.min.js" type="text/javascript"></script>
|
||||
<script src="jquery/jquery.i18n.properties.min.js" type="text/javascript"></script>
|
||||
<script>
|
||||
$('#login-button').click(function(event) {
|
||||
$.ajax({
|
||||
type : "POST",
|
||||
url : "/login",
|
||||
contentType : "application/json; charset=utf-8",
|
||||
data : JSON.stringify({
|
||||
username : $(".username").val(),
|
||||
password : $(".password").val()
|
||||
}),
|
||||
dataType : "json",
|
||||
success : function(data) {
|
||||
if (data.code == 20000) {
|
||||
setCookie("token", data.data);
|
||||
window.location.href = "/lanproxy-config/";
|
||||
}
|
||||
},
|
||||
error : function(e) {
|
||||
alert(e.responseJSON.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function setCookie(name, value) {
|
||||
var Days = 30;
|
||||
var exp = new Date();
|
||||
exp.setTime(exp.getTime() + Days * 24 * 60 * 60 * 1000);
|
||||
document.cookie = name + "=" + escape(value) + ";expires="
|
||||
+ exp.toGMTString();
|
||||
}
|
||||
|
||||
$(document).keyup(function(event) {
|
||||
if (event.keyCode == 13) {
|
||||
$("#login-button").trigger("click");
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 设置语言类型: 默认为英文
|
||||
*/
|
||||
var i18nLanguage = "en";
|
||||
|
||||
/*
|
||||
设置一下网站支持的语言种类
|
||||
zh-CN(中文简体)、en(英语)
|
||||
*/
|
||||
var webLanguage = [ 'zh-CN', 'en' ];
|
||||
|
||||
function initWebLanguage() {
|
||||
var navLanguage = navigator.language;
|
||||
//console.log("user set browser language is " + navLanguage);
|
||||
if (navLanguage) {
|
||||
var charSize = $.inArray(navLanguage, webLanguage);
|
||||
if (charSize > -1) {
|
||||
i18nLanguage = navLanguage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
initWebLanguage();
|
||||
|
||||
jQuery.i18n.properties({
|
||||
name : 'lang', //资源文件名称
|
||||
path : '/i18n/', //资源文件路径
|
||||
mode : 'map', //用Map的方式使用资源文件中的值
|
||||
language : i18nLanguage,
|
||||
encoding: 'UTF-8',
|
||||
callback : function() {//加载成功后设置显示内容
|
||||
$('title').html($.i18n.prop('login')+" - LanProxy");
|
||||
$('#login-button').html($.i18n.prop('login'));
|
||||
$(".username").attr("placeholder", $.i18n.prop('username'));
|
||||
$(".password").attr("placeholder", $.i18n.prop('password'));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<!-- 代码 结束 -->
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,9 @@
|
||||
(function(k){function t(a){a.debug&&(g("callbackIfComplete()"),g("totalFiles: "+a.totalFiles),g("filesLoaded: "+a.filesLoaded));a.async&&a.filesLoaded===a.totalFiles&&a.callback&&a.callback()}function n(a,d){d.debug&&g("loadAndParseFiles");null!==a&&0<a.length?u(a[0],d,function(){a.shift();n(a,d)}):t(d)}function u(a,d,b){d.debug&&(g("loadAndParseFile('"+a+"')"),g("totalFiles: "+d.totalFiles),g("filesLoaded: "+d.filesLoaded));null!==a&&"undefined"!==typeof a&&k.ajax({url:a,async:d.async,cache:d.cache,
|
||||
dataType:"text",success:function(h,c){d.debug&&(g("Succeeded in downloading "+a+"."),g(h));v(h,d);b()},error:function(h,c,e){d.debug&&g("Failed to download or parse "+a+". errorThrown: "+e);404===h.status&&--d.totalFiles;b()}})}function v(a,d){var b="",h=a.split(/\n/),c=/(\{\d+})/g,e=/\{(\d+)}/g,g=/(\\u.{4})/ig;h.forEach(function(a,w){a=a.trim();if(0<a.length&&"#"!=a.match("^#")){var l=a.split("=");if(0<l.length){for(var m=decodeURI(l[0]).trim(),f=1==l.length?"":l[1];"\\"===f.match(/\\$/);)f=f.substring(0,
|
||||
f.length-1),f+=h[++w].trimRight();for(var p=2;p<l.length;p++)f+="="+l[p];f=f.trim();if("map"==d.mode||"both"==d.mode)(l=f.match(g))&&l.forEach(function(a){f=f.replace(a,x(a))}),k.i18n.map[m]=f;if("vars"==d.mode||"both"==d.mode)if(f=f.replace(/"/g,'\\"'),y(m),c.test(f)){var n=!0,q="",r=[];f.split(c).forEach(function(a){!c.test(a)||0!==r.length&&-1!=r.indexOf(a)||(n||(q+=","),q+=a.replace(e,"v$1"),r.push(a),n=!1)});b+=m+"=function("+q+"){";m='"'+f.replace(e,'"+v$1+"')+'"';b+="return "+m+";};"}else b+=
|
||||
m+'="'+f+'";'}}});eval(b);d.filesLoaded+=1}function y(a){if(/\./.test(a)){var d="";a.split(/\./).forEach(function(a,h){0<h&&(d+=".");d+=a;eval("typeof "+d+' == "undefined"')&&eval(d+"={};")})}}function x(a){var d=[];a=parseInt(a.substr(2),16);0<=a&&a<Math.pow(2,16)&&d.push(a);return d.reduce(function(a,d){return a+String.fromCharCode(d)},"")}k.i18n={};k.i18n.map={};var g=function(a){window.console&&console.log("i18n::"+a)};k.i18n.properties=function(a){a=k.extend({name:"Messages",language:"",path:"",
|
||||
mode:"vars",cache:!1,debug:!1,encoding:"UTF-8",async:!1,callback:null},a);a.path.match(/\/$/)||(a.path+="/");a.language=this.normaliseLanguageCode(a.language);var d=a.name&&a.name.constructor===Array?a.name:[a.name];a.totalFiles=2*d.length+(5<=a.language.length?d.length:0);a.debug&&g("totalFiles: "+a.totalFiles);a.filesLoaded=0;d.forEach(function(b){var d=a.path+b+".properties";var c=a.language.substring(0,2);c=a.path+b+"_"+c+".properties";if(5<=a.language.length){var e=a.language.substring(0,5);
|
||||
b=a.path+b+"_"+e+".properties";d=[d,c,b]}else d=[d,c];n(d,a)});a.callback&&!a.async&&a.callback()};k.i18n.prop=function(a){var d,b=k.i18n.map[a];if(null===b)return"["+a+"]";var h;2==arguments.length&&k.isArray(arguments[1])&&(h=arguments[1]);var c;if("string"==typeof b){for(c=0;-1!=(c=b.indexOf("\\",c));)b="t"==b.charAt(c+1)?b.substring(0,c)+"\t"+b.substring(c++ +2):"r"==b.charAt(c+1)?b.substring(0,c)+"\r"+b.substring(c++ +2):"n"==b.charAt(c+1)?b.substring(0,c)+"\n"+b.substring(c++ +2):"f"==b.charAt(c+
|
||||
1)?b.substring(0,c)+"\f"+b.substring(c++ +2):"\\"==b.charAt(c+1)?b.substring(0,c)+"\\"+b.substring(c++ +2):b.substring(0,c)+b.substring(c+1);var e=[];for(c=0;c<b.length;)if("'"==b.charAt(c))if(c==b.length-1)b=b.substring(0,c);else if("'"==b.charAt(c+1))b=b.substring(0,c)+b.substring(++c);else{for(d=c+2;-1!=(d=b.indexOf("'",d));)if(d==b.length-1||"'"!=b.charAt(d+1)){b=b.substring(0,c)+b.substring(c+1,d)+b.substring(d+1);c=d-1;break}else b=b.substring(0,d)+b.substring(++d);-1==d&&(b=b.substring(0,c)+
|
||||
b.substring(c+1))}else if("{"==b.charAt(c))if(d=b.indexOf("}",c+1),-1==d)c++;else{var g=parseInt(b.substring(c+1,d));!isNaN(g)&&0<=g?(c=b.substring(0,c),""!==c&&e.push(c),e.push(g),c=0,b=b.substring(d+1)):c=d+1}else c++;""!==b&&e.push(b);b=e;k.i18n.map[a]=e}if(0===b.length)return"";if(1==b.length&&"string"==typeof b[0])return b[0];e="";c=0;for(d=b.length;c<d;c++)e="string"==typeof b[c]?e+b[c]:h&&b[c]<h.length?e+h[b[c]]:!h&&b[c]+1<arguments.length?e+arguments[b[c]+1]:e+("{"+b[c]+"}");return e};k.i18n.normaliseLanguageCode=
|
||||
function(a){if(!a||2>a.length)a=navigator.languages&&0<navigator.languages.length?navigator.languages[0]:navigator.language||navigator.userLanguage||"en";a=a.toLowerCase();a=a.replace(/-/,"_");3<a.length&&(a=a.substring(0,3)+a.substring(3).toUpperCase());return a}})(jQuery);
|
||||
@@ -0,0 +1,893 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="216"
|
||||
height="144"
|
||||
id="svg4136"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r"
|
||||
sodipodi:docname="jsoneditor-icons.svg">
|
||||
<title
|
||||
id="title6512">JSON Editor Icons</title>
|
||||
<metadata
|
||||
id="metadata4148">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title>JSON Editor Icons</dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs4146" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1028"
|
||||
id="namedview4144"
|
||||
showgrid="true"
|
||||
inkscape:zoom="4"
|
||||
inkscape:cx="97.217248"
|
||||
inkscape:cy="59.950227"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4136"
|
||||
showguides="false"
|
||||
borderlayer="false"
|
||||
inkscape:showpageshadow="true"
|
||||
showborder="true">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid4640"
|
||||
empspacing="24" />
|
||||
</sodipodi:namedview>
|
||||
<!-- Created with SVG-edit - http://svg-edit.googlecode.com/ -->
|
||||
<g
|
||||
id="g4394">
|
||||
<rect
|
||||
x="4"
|
||||
y="4"
|
||||
width="16"
|
||||
height="16"
|
||||
id="svg_1"
|
||||
style="fill:#1aae1c;fill-opacity:1;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
style="fill:#ec3f29;fill-opacity:0.94117647;stroke:none;stroke-width:0"
|
||||
x="28.000006"
|
||||
y="3.999995"
|
||||
width="16"
|
||||
height="16"
|
||||
id="svg_1-7" />
|
||||
<rect
|
||||
id="rect4165"
|
||||
height="16"
|
||||
width="16"
|
||||
y="3.999995"
|
||||
x="52.000004"
|
||||
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0"
|
||||
x="172.00002"
|
||||
y="3.9999852"
|
||||
width="16"
|
||||
height="16"
|
||||
id="rect4175" />
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0"
|
||||
x="196"
|
||||
y="3.999995"
|
||||
width="16"
|
||||
height="16"
|
||||
id="rect4175-3" />
|
||||
<g
|
||||
style="stroke:none"
|
||||
id="g4299">
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0"
|
||||
id="svg_1-1"
|
||||
height="1.9999986"
|
||||
width="9.9999924"
|
||||
y="10.999998"
|
||||
x="7.0000048" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0"
|
||||
id="svg_1-1-1"
|
||||
height="9.9999838"
|
||||
width="1.9999955"
|
||||
y="7.0000114"
|
||||
x="11.000005" />
|
||||
</g>
|
||||
<g
|
||||
style="stroke:none"
|
||||
transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,19.029435,12.000001)"
|
||||
id="g4299-3">
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0"
|
||||
id="svg_1-1-0"
|
||||
height="1.9999986"
|
||||
width="9.9999924"
|
||||
y="10.999998"
|
||||
x="7.0000048" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0"
|
||||
id="svg_1-1-1-9"
|
||||
height="9.9999838"
|
||||
width="1.9999955"
|
||||
y="7.0000114"
|
||||
x="11.000005" />
|
||||
</g>
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
|
||||
x="55.000004"
|
||||
y="7.0000048"
|
||||
width="6.9999909"
|
||||
height="6.9999905"
|
||||
id="svg_1-7-5" />
|
||||
<rect
|
||||
id="rect4354"
|
||||
height="6.9999905"
|
||||
width="6.9999909"
|
||||
y="10.00001"
|
||||
x="58"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#4c4c4c;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#3c80df;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.94117647"
|
||||
x="58.000004"
|
||||
y="10.000005"
|
||||
width="6.9999909"
|
||||
height="6.9999905"
|
||||
id="svg_1-7-5-7" />
|
||||
<g
|
||||
id="g4378">
|
||||
<rect
|
||||
id="svg_1-7-5-3"
|
||||
height="1.9999965"
|
||||
width="7.9999909"
|
||||
y="10.999999"
|
||||
x="198"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
|
||||
x="198"
|
||||
y="7.0000005"
|
||||
width="11.999995"
|
||||
height="1.9999946"
|
||||
id="rect4374" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
|
||||
x="198"
|
||||
y="14.999996"
|
||||
width="3.9999928"
|
||||
height="1.9999995"
|
||||
id="rect4376" />
|
||||
</g>
|
||||
<g
|
||||
id="g4383"
|
||||
transform="matrix(1,0,0,-1,-23.999995,23.999995)">
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
|
||||
x="198"
|
||||
y="10.999999"
|
||||
width="7.9999909"
|
||||
height="1.9999965"
|
||||
id="rect4385" />
|
||||
<rect
|
||||
id="rect4387"
|
||||
height="1.9999946"
|
||||
width="11.999995"
|
||||
y="7.0000005"
|
||||
x="198"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4389"
|
||||
height="1.9999995"
|
||||
width="3.9999928"
|
||||
y="14.999996"
|
||||
x="198"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
|
||||
</g>
|
||||
<rect
|
||||
y="3.9999199"
|
||||
x="76"
|
||||
height="16"
|
||||
width="16"
|
||||
id="rect3754-4"
|
||||
style="fill:#4c4c4c;fill-opacity:1;stroke:none" />
|
||||
<path
|
||||
sodipodi:nodetypes="cccccccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4351"
|
||||
d="m 85.10447,6.0157384 -0.0156,1.4063 c 3.02669,-0.2402 0.33008,3.6507996 2.48438,4.5780996 -2.18694,1.0938 0.49191,4.9069 -2.45313,4.5781 l -0.0156,1.4219 c 5.70828,0.559 1.03264,-5.1005 4.70313,-5.2656 l 0,-1.4063 c -3.61303,-0.027 1.11893,-5.7069996 -4.70313,-5.3124996 z"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
sodipodi:nodetypes="cccccccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4351-9"
|
||||
d="m 82.78125,5.9984384 0.0156,1.4063 c -3.02668,-0.2402 -0.33007,3.6506996 -2.48437,4.5780996 2.18694,1.0938 -0.49192,4.9069 2.45312,4.5781 l 0.0156,1.4219 c -5.70827,0.559 -1.03263,-5.1004 -4.70312,-5.2656 l 0,-1.4063 c 3.61303,-0.027 -1.11894,-5.7070996 4.70312,-5.3124996 z"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
y="3.9999199"
|
||||
x="100"
|
||||
height="16"
|
||||
width="16"
|
||||
id="rect3754-25"
|
||||
style="fill:#4c4c4c;fill-opacity:1;stroke:none" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path2987"
|
||||
d="m 103.719,5.6719384 0,12.7187996 3.03125,0 0,-1.5313 -1.34375,0 0,-9.6249996 1.375,0 0,-1.5625 z"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path2987-1"
|
||||
d="m 112.2185,5.6721984 0,12.7187996 -3.03125,0 0,-1.5313 1.34375,0 0,-9.6249996 -1.375,0 0,-1.5625 z"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none" />
|
||||
<rect
|
||||
y="3.9999199"
|
||||
x="124"
|
||||
height="16"
|
||||
width="16"
|
||||
id="rect3754-73"
|
||||
style="fill:#4c4c4c;fill-opacity:1;stroke:none" />
|
||||
<path
|
||||
sodipodi:nodetypes="ccccccccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3780"
|
||||
d="m 126.2824,17.602938 1.78957,0 1.14143,-2.8641 5.65364,0 1.14856,2.8641 1.76565,0 -4.78687,-11.1610996 -1.91903,0 z"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3782"
|
||||
d="m 129.72704,13.478838 4.60852,0.01 -2.30426,-5.5497996 z"
|
||||
style="fill:#4c4c4c;fill-opacity:1;stroke:none" />
|
||||
<rect
|
||||
y="3.9999199"
|
||||
x="148"
|
||||
height="16"
|
||||
width="16"
|
||||
id="rect3754-35"
|
||||
style="fill:#4c4c4c;fill-opacity:1;stroke:none" />
|
||||
<path
|
||||
sodipodi:nodetypes="ccccccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path5008-2"
|
||||
d="m 156.47655,5.8917384 0,2.1797 0.46093,2.3983996 1.82813,0 0.39844,-2.3983996 0,-2.1797 z"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none" />
|
||||
<path
|
||||
sodipodi:nodetypes="ccccccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path5008-2-8"
|
||||
d="m 152.51561,5.8906384 0,2.1797 0.46094,2.3983996 1.82812,0 0.39844,-2.3983996 0,-2.1797 z"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none" />
|
||||
</g>
|
||||
<rect
|
||||
x="4"
|
||||
y="27.999994"
|
||||
width="16"
|
||||
height="16"
|
||||
id="rect4432"
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0"
|
||||
x="28.000006"
|
||||
y="27.99999"
|
||||
width="16"
|
||||
height="16"
|
||||
id="rect4434" />
|
||||
<rect
|
||||
id="rect4436"
|
||||
height="16"
|
||||
width="16"
|
||||
y="27.99999"
|
||||
x="52.000004"
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:#000000;stroke-width:0" />
|
||||
<rect
|
||||
style="fill:#d3d3d3;stroke:#000000;stroke-width:0"
|
||||
x="172.00002"
|
||||
y="27.999981"
|
||||
width="16"
|
||||
height="16"
|
||||
id="rect4446" />
|
||||
<rect
|
||||
style="fill:#d3d3d3;stroke:#000000;stroke-width:0"
|
||||
x="196"
|
||||
y="27.99999"
|
||||
width="16"
|
||||
height="16"
|
||||
id="rect4448" />
|
||||
<g
|
||||
id="g4466"
|
||||
style="stroke:none"
|
||||
transform="translate(0,23.999995)">
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0"
|
||||
id="rect4468"
|
||||
height="1.9999986"
|
||||
width="9.9999924"
|
||||
y="10.999998"
|
||||
x="7.0000048" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0"
|
||||
id="rect4470"
|
||||
height="9.9999838"
|
||||
width="1.9999955"
|
||||
y="7.0000114"
|
||||
x="11.000005" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,19.029435,35.999996)"
|
||||
id="g4472"
|
||||
style="stroke:none">
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0"
|
||||
id="rect4474"
|
||||
height="1.9999986"
|
||||
width="9.9999924"
|
||||
y="10.999998"
|
||||
x="7.0000048" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0"
|
||||
id="rect4476"
|
||||
height="9.9999838"
|
||||
width="1.9999955"
|
||||
y="7.0000114"
|
||||
x="11.000005" />
|
||||
</g>
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
|
||||
x="55.000004"
|
||||
y="31"
|
||||
width="6.9999909"
|
||||
height="6.9999905"
|
||||
id="rect4478" />
|
||||
<rect
|
||||
id="rect4480"
|
||||
height="6.9999905"
|
||||
width="6.9999909"
|
||||
y="34.000008"
|
||||
x="58"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#d3d3d3;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#d3d3d3;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
x="58.000004"
|
||||
y="34.000004"
|
||||
width="6.9999909"
|
||||
height="6.9999905"
|
||||
id="rect4482" />
|
||||
<g
|
||||
id="g4484"
|
||||
transform="translate(0,23.999995)">
|
||||
<rect
|
||||
id="rect4486"
|
||||
height="1.9999965"
|
||||
width="7.9999909"
|
||||
y="10.999999"
|
||||
x="198"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
|
||||
x="198"
|
||||
y="7.0000005"
|
||||
width="11.999995"
|
||||
height="1.9999946"
|
||||
id="rect4488" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
|
||||
x="198"
|
||||
y="14.999996"
|
||||
width="3.9999928"
|
||||
height="1.9999995"
|
||||
id="rect4490" />
|
||||
</g>
|
||||
<g
|
||||
id="g4492"
|
||||
transform="matrix(1,0,0,-1,-23.999995,47.99999)">
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
|
||||
x="198"
|
||||
y="10.999999"
|
||||
width="7.9999909"
|
||||
height="1.9999965"
|
||||
id="rect4494" />
|
||||
<rect
|
||||
id="rect4496"
|
||||
height="1.9999946"
|
||||
width="11.999995"
|
||||
y="7.0000005"
|
||||
x="198"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4498"
|
||||
height="1.9999995"
|
||||
width="3.9999928"
|
||||
y="14.999996"
|
||||
x="198"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
|
||||
</g>
|
||||
<rect
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none"
|
||||
id="rect3754-8"
|
||||
width="16"
|
||||
height="16"
|
||||
x="76"
|
||||
y="27.99992" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 85.10448,30.015537 -0.0156,1.4063 c 3.02668,-0.2402 0.33007,3.6508 2.48438,4.5781 -2.18695,1.0938 0.49191,4.90688 -2.45313,4.57808 l -0.0156,1.4219 c 5.70827,0.559 1.03263,-5.10048 4.70313,-5.26558 l 0,-1.4063 c -3.61304,-0.027 1.11893,-5.707 -4.70313,-5.3125 z"
|
||||
id="path4351-1"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccccccc" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 82.78126,29.998237 0.0156,1.4063 c -3.02668,-0.2402 -0.33008,3.6507 -2.48438,4.5781 2.18694,1.0938 -0.49191,4.90688 2.45313,4.57808 l 0.0156,1.4219 c -5.70828,0.559 -1.03264,-5.10038 -4.70313,-5.26558 l 0,-1.4063 c 3.61303,-0.027 -1.11893,-5.7071 4.70313,-5.3125 z"
|
||||
id="path4351-9-5"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccccccc" />
|
||||
<rect
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none"
|
||||
id="rect3754-65"
|
||||
width="16"
|
||||
height="16"
|
||||
x="100"
|
||||
y="27.99992" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
d="m 103.719,29.671937 0,12.71878 3.03125,0 0,-1.5313 -1.34375,0 0,-9.62498 1.375,0 0,-1.5625 z"
|
||||
id="path2987-8"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
d="m 112.2185,29.671937 0,12.71878 -3.03125,0 0,-1.5313 1.34375,0 0,-9.62498 -1.375,0 0,-1.5625 z"
|
||||
id="path2987-1-9"
|
||||
inkscape:connector-curvature="0" />
|
||||
<rect
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none"
|
||||
id="rect3754-92"
|
||||
width="16"
|
||||
height="16"
|
||||
x="124"
|
||||
y="27.99992" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
d="m 126.2824,41.602917 1.78957,0 1.14143,-2.86408 5.65364,0 1.14856,2.86408 1.76565,0 -4.78687,-11.16108 -1.91902,0 z"
|
||||
id="path3780-9"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccccc" />
|
||||
<path
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none"
|
||||
d="m 129.72704,37.478837 4.60852,0.01 -2.30426,-5.5498 z"
|
||||
id="path3782-2"
|
||||
inkscape:connector-curvature="0" />
|
||||
<rect
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none"
|
||||
id="rect3754-47"
|
||||
width="16"
|
||||
height="16"
|
||||
x="148"
|
||||
y="27.99992" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
d="m 156.47656,29.891737 0,2.1797 0.46093,2.3984 1.82813,0 0.39844,-2.3984 0,-2.1797 z"
|
||||
id="path5008-2-1"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccc" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
d="m 152.51562,29.890637 0,2.1797 0.46094,2.3984 1.82812,0 0.39844,-2.3984 0,-2.1797 z"
|
||||
id="path5008-2-8-8"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccc" />
|
||||
<rect
|
||||
id="svg_1-7-2"
|
||||
height="1.9999961"
|
||||
width="11.999996"
|
||||
y="64"
|
||||
x="54"
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="svg_1-7-2-2"
|
||||
height="2.9999905"
|
||||
width="2.9999907"
|
||||
y="52"
|
||||
x="80.000008"
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
|
||||
x="85.000008"
|
||||
y="52"
|
||||
width="2.9999907"
|
||||
height="2.9999905"
|
||||
id="rect4561" />
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
|
||||
x="80.000008"
|
||||
y="58"
|
||||
width="2.9999907"
|
||||
height="2.9999905"
|
||||
id="rect4563" />
|
||||
<rect
|
||||
id="rect4565"
|
||||
height="2.9999905"
|
||||
width="2.9999907"
|
||||
y="58"
|
||||
x="85.000008"
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4567"
|
||||
height="2.9999905"
|
||||
width="2.9999907"
|
||||
y="64"
|
||||
x="80.000008"
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
|
||||
x="85.000008"
|
||||
y="64"
|
||||
width="2.9999907"
|
||||
height="2.9999905"
|
||||
id="rect4569" />
|
||||
<circle
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#4c4c4c;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
id="path4571"
|
||||
cx="110.06081"
|
||||
cy="57.939209"
|
||||
r="4.7438836" />
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
|
||||
x="116.64566"
|
||||
y="-31.79752"
|
||||
width="4.229713"
|
||||
height="6.4053884"
|
||||
id="rect4563-2"
|
||||
transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,0,0)" />
|
||||
<path
|
||||
style="fill:#4c4c4c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 125,56 138.77027,56.095 132,64 Z"
|
||||
id="path4613"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
sodipodi:nodetypes="cccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4615"
|
||||
d="M 149,64 162.77027,63.905 156,56 Z"
|
||||
style="fill:#4c4c4c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
|
||||
x="54"
|
||||
y="53"
|
||||
width="11.999996"
|
||||
height="1.9999961"
|
||||
id="rect4638" />
|
||||
<rect
|
||||
id="svg_1-7-2-24"
|
||||
height="1.9999957"
|
||||
width="12.99999"
|
||||
y="-56"
|
||||
x="53"
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
|
||||
transform="matrix(0,1,-1,0,0,0)" />
|
||||
<rect
|
||||
transform="matrix(0,1,-1,0,0,0)"
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
|
||||
x="53"
|
||||
y="-66"
|
||||
width="12.99999"
|
||||
height="1.9999957"
|
||||
id="rect4657" />
|
||||
<rect
|
||||
id="rect4659"
|
||||
height="0.99999291"
|
||||
width="11.999999"
|
||||
y="57"
|
||||
x="54"
|
||||
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
|
||||
x="54"
|
||||
y="88.000122"
|
||||
width="11.999996"
|
||||
height="1.9999961"
|
||||
id="rect4661" />
|
||||
<rect
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
|
||||
x="80.000008"
|
||||
y="76.000122"
|
||||
width="2.9999907"
|
||||
height="2.9999905"
|
||||
id="rect4663" />
|
||||
<rect
|
||||
id="rect4665"
|
||||
height="2.9999905"
|
||||
width="2.9999907"
|
||||
y="76.000122"
|
||||
x="85.000008"
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1" />
|
||||
<rect
|
||||
id="rect4667"
|
||||
height="2.9999905"
|
||||
width="2.9999907"
|
||||
y="82.000122"
|
||||
x="80.000008"
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1" />
|
||||
<rect
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
|
||||
x="85.000008"
|
||||
y="82.000122"
|
||||
width="2.9999907"
|
||||
height="2.9999905"
|
||||
id="rect4669" />
|
||||
<rect
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
|
||||
x="80.000008"
|
||||
y="88.000122"
|
||||
width="2.9999907"
|
||||
height="2.9999905"
|
||||
id="rect4671" />
|
||||
<rect
|
||||
id="rect4673"
|
||||
height="2.9999905"
|
||||
width="2.9999907"
|
||||
y="88.000122"
|
||||
x="85.000008"
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1" />
|
||||
<circle
|
||||
r="4.7438836"
|
||||
cy="81.939331"
|
||||
cx="110.06081"
|
||||
id="circle4675"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#d3d3d3;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,0,0)"
|
||||
id="rect4677"
|
||||
height="6.4053884"
|
||||
width="4.229713"
|
||||
y="-14.826816"
|
||||
x="133.6163"
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:#d3d3d3;stroke-width:0;stroke-opacity:1" />
|
||||
<path
|
||||
sodipodi:nodetypes="cccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4679"
|
||||
d="m 125,80.000005 13.77027,0.09499 L 132,87.999992 Z"
|
||||
style="fill:#d3d3d3;fill-opacity:1;fill-rule:evenodd;stroke:#d3d3d3;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:#d3d3d3;fill-opacity:1;fill-rule:evenodd;stroke:#d3d3d3;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 149,88.0002 162.77027,87.9052 156,80.0002 Z"
|
||||
id="path4681"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<rect
|
||||
id="rect4683"
|
||||
height="1.9999961"
|
||||
width="11.999996"
|
||||
y="77.000122"
|
||||
x="54"
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1" />
|
||||
<rect
|
||||
transform="matrix(0,1,-1,0,0,0)"
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
|
||||
x="77.000122"
|
||||
y="-56"
|
||||
width="12.99999"
|
||||
height="1.9999957"
|
||||
id="rect4685" />
|
||||
<rect
|
||||
id="rect4687"
|
||||
height="1.9999957"
|
||||
width="12.99999"
|
||||
y="-66"
|
||||
x="77.000122"
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
|
||||
transform="matrix(0,1,-1,0,0,0)" />
|
||||
<rect
|
||||
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
|
||||
x="54"
|
||||
y="81.000122"
|
||||
width="11.999999"
|
||||
height="0.99999291"
|
||||
id="rect4689" />
|
||||
<rect
|
||||
id="rect4761-1"
|
||||
height="1.9999945"
|
||||
width="15.99999"
|
||||
y="101"
|
||||
x="76.000008"
|
||||
style="fill:#ffffff;fill-opacity:0.80000007;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4761-0"
|
||||
height="1.9999945"
|
||||
width="15.99999"
|
||||
y="105"
|
||||
x="76.000008"
|
||||
style="fill:#ffffff;fill-opacity:0.80000007;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4761-7"
|
||||
height="1.9999945"
|
||||
width="9"
|
||||
y="109"
|
||||
x="76.000008"
|
||||
style="fill:#ffffff;fill-opacity:0.80000007;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4761-1-1"
|
||||
height="1.9999945"
|
||||
width="12"
|
||||
y="125"
|
||||
x="76.000008"
|
||||
style="fill:#ffffff;fill-opacity:0.80000007;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4761-1-1-4"
|
||||
height="1.9999945"
|
||||
width="10"
|
||||
y="137"
|
||||
x="76.000008"
|
||||
style="fill:#ffffff;fill-opacity:0.80000007;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4761-1-1-4-4"
|
||||
height="1.9999945"
|
||||
width="10"
|
||||
y="129"
|
||||
x="82"
|
||||
style="fill:#ffffff;fill-opacity:0.80000007;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4761-1-1-4-4-3"
|
||||
height="1.9999945"
|
||||
width="9"
|
||||
y="133"
|
||||
x="82"
|
||||
style="fill:#ffffff;fill-opacity:0.80000007;stroke:none;stroke-width:0" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.8;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.66157866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 36.398438,100.0254 c -0.423362,-0.013 -0.846847,0.01 -1.265626,0.062 -1.656562,0.2196 -3.244567,0.9739 -4.507812,2.2266 L 29,100.5991 l -2.324219,7.7129 7.826172,-1.9062 -1.804687,-1.9063 c 1.597702,-1.5308 4.048706,-1.8453 5.984375,-0.7207 1.971162,1.1452 2.881954,3.3975 2.308593,5.5508 -0.573361,2.1533 -2.533865,3.6953 -4.830078,3.6953 l 0,3.0742 c 3.550756,0 6.710442,-2.4113 7.650391,-5.9414 0.939949,-3.5301 -0.618463,-7.2736 -3.710938,-9.0703 -1.159678,-0.6738 -2.431087,-1.0231 -3.701171,-1.0625 z"
|
||||
id="path4138" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.8;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.66157866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 59.722656,99.9629 c -1.270084,0.039 -2.541493,0.3887 -3.701172,1.0625 -3.092475,1.7967 -4.650886,5.5402 -3.710937,9.0703 0.939949,3.5301 4.09768,5.9414 7.648437,5.9414 l 0,-3.0742 c -2.296214,0 -4.256717,-1.542 -4.830078,-3.6953 -0.573361,-2.1533 0.337432,-4.4056 2.308594,-5.5508 1.935731,-1.1246 4.38863,-0.8102 5.986326,0.7207 l -1.806638,1.9063 7.828128,1.9062 -2.32422,-7.7129 -1.62696,1.7168 c -1.26338,-1.2531 -2.848917,-2.0088 -4.505855,-2.2285 -0.418778,-0.055 -0.842263,-0.076 -1.265625,-0.062 z"
|
||||
id="path4138-1" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.966;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
d="m 10.5,100 0,2 -2.4999996,0 L 12,107 l 4,-5 -2.5,0 0,-2 -3,0 z"
|
||||
id="path3055-0-77" />
|
||||
<path
|
||||
style="opacity:0.8;fill:none;stroke:#ffffff;stroke-width:1.966;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 4.9850574,108.015 14.0298856,-0.03"
|
||||
id="path5244-5-0-5"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="opacity:0.8;fill:none;stroke:#ffffff;stroke-width:1.966;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 4.9849874,132.015 14.0298866,-0.03"
|
||||
id="path5244-5-0-5-8"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.4;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.66157866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 36.398438,123.9629 c -0.423362,-0.013 -0.846847,0.01 -1.265626,0.062 -1.656562,0.2196 -3.244567,0.9739 -4.507812,2.2266 L 29,124.5366 l -2.324219,7.7129 7.826172,-1.9062 -1.804687,-1.9063 c 1.597702,-1.5308 4.048706,-1.8453 5.984375,-0.7207 1.971162,1.1453 2.881954,3.3975 2.308593,5.5508 -0.573361,2.1533 -2.533864,3.6953 -4.830078,3.6953 l 0,3.0742 c 3.550757,0 6.710442,-2.4093 7.650391,-5.9394 0.939949,-3.5301 -0.618463,-7.2756 -3.710938,-9.0723 -1.159678,-0.6737 -2.431087,-1.0231 -3.701171,-1.0625 z"
|
||||
id="path4138-12" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.4;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.66157866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 59.722656,123.9629 c -1.270084,0.039 -2.541493,0.3888 -3.701172,1.0625 -3.092475,1.7967 -4.650886,5.5422 -3.710937,9.0723 0.939949,3.5301 4.09768,5.9394 7.648437,5.9394 l 0,-3.0742 c -2.296214,0 -4.256717,-1.542 -4.830078,-3.6953 -0.573361,-2.1533 0.337432,-4.4055 2.308594,-5.5508 1.935731,-1.1246 4.38863,-0.8102 5.986326,0.7207 l -1.806638,1.9063 7.828128,1.9062 -2.32422,-7.7129 -1.62696,1.7168 c -1.26338,-1.2531 -2.848917,-2.0088 -4.505855,-2.2285 -0.418778,-0.055 -0.842263,-0.076 -1.265625,-0.062 z"
|
||||
id="path4138-1-3" />
|
||||
<path
|
||||
id="path6191"
|
||||
d="m 10.5,116 0,-2 -2.4999996,0 L 12,109 l 4,5 -2.5,0 0,2 -3,0 z"
|
||||
style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.966;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.966;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
d="m 10.5,129 0,-2 -2.4999996,0 L 12,122 l 4,5 -2.5,0 0,2 -3,0 z"
|
||||
id="path6193" />
|
||||
<path
|
||||
id="path6195"
|
||||
d="m 10.5,135 0,2 -2.4999996,0 L 12,142 l 4,-5 -2.5,0 0,-2 -3,0 z"
|
||||
style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.966;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
sodipodi:type="star"
|
||||
style="fill:#4d4d4d;fill-opacity:0.90196078;stroke:#d3d3d3;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
id="path4500"
|
||||
sodipodi:sides="3"
|
||||
sodipodi:cx="11.55581"
|
||||
sodipodi:cy="60.073242"
|
||||
sodipodi:r1="5.1116104"
|
||||
sodipodi:r2="2.5558052"
|
||||
sodipodi:arg1="0"
|
||||
sodipodi:arg2="1.0471976"
|
||||
inkscape:flatsided="false"
|
||||
inkscape:rounded="0"
|
||||
inkscape:randomized="0"
|
||||
d="m 16.66742,60.073242 -3.833708,2.213392 -3.8337072,2.213393 0,-4.426785 0,-4.426784 3.8337082,2.213392 z"
|
||||
inkscape:transform-center-x="-1.2779026" />
|
||||
<path
|
||||
inkscape:transform-center-x="1.277902"
|
||||
d="m -31.500004,60.073242 -3.833708,2.213392 -3.833707,2.213393 0,-4.426785 0,-4.426784 3.833707,2.213392 z"
|
||||
inkscape:randomized="0"
|
||||
inkscape:rounded="0"
|
||||
inkscape:flatsided="false"
|
||||
sodipodi:arg2="1.0471976"
|
||||
sodipodi:arg1="0"
|
||||
sodipodi:r2="2.5558052"
|
||||
sodipodi:r1="5.1116104"
|
||||
sodipodi:cy="60.073242"
|
||||
sodipodi:cx="-36.611614"
|
||||
sodipodi:sides="3"
|
||||
id="path4502"
|
||||
style="fill:#4d4d4d;fill-opacity:0.90196078;stroke:#d3d3d3;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
sodipodi:type="star"
|
||||
transform="scale(-1,1)" />
|
||||
<path
|
||||
d="m 16.66742,60.073212 -3.833708,2.213392 -3.8337072,2.213392 0,-4.426784 0,-4.426785 3.8337082,2.213392 z"
|
||||
inkscape:randomized="0"
|
||||
inkscape:rounded="0"
|
||||
inkscape:flatsided="false"
|
||||
sodipodi:arg2="1.0471976"
|
||||
sodipodi:arg1="0"
|
||||
sodipodi:r2="2.5558052"
|
||||
sodipodi:r1="5.1116104"
|
||||
sodipodi:cy="60.073212"
|
||||
sodipodi:cx="11.55581"
|
||||
sodipodi:sides="3"
|
||||
id="path4504"
|
||||
style="fill:#4d4d4d;fill-opacity:0.90196078;stroke:#d3d3d3;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
sodipodi:type="star"
|
||||
transform="matrix(0,1,-1,0,72.0074,71.7877)"
|
||||
inkscape:transform-center-y="1.2779029" />
|
||||
<path
|
||||
inkscape:transform-center-y="-1.2779026"
|
||||
transform="matrix(0,-1,-1,0,96,96)"
|
||||
sodipodi:type="star"
|
||||
style="fill:#4d4d4d;fill-opacity:0.90196078;stroke:#d3d3d3;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
id="path4506"
|
||||
sodipodi:sides="3"
|
||||
sodipodi:cx="11.55581"
|
||||
sodipodi:cy="60.073212"
|
||||
sodipodi:r1="5.1116104"
|
||||
sodipodi:r2="2.5558052"
|
||||
sodipodi:arg1="0"
|
||||
sodipodi:arg2="1.0471976"
|
||||
inkscape:flatsided="false"
|
||||
inkscape:rounded="0"
|
||||
inkscape:randomized="0"
|
||||
d="m 16.66742,60.073212 -3.833708,2.213392 -3.8337072,2.213392 0,-4.426784 0,-4.426785 3.8337082,2.213392 z" />
|
||||
<path
|
||||
sodipodi:nodetypes="cccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4615-5"
|
||||
d="m 171.82574,65.174193 16.34854,0 -8.17427,-13.348454 z"
|
||||
style="fill:#fbb917;fill-opacity:1;fill-rule:evenodd;stroke:#fbb917;stroke-width:1.65161395;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 179,55 0,6 2,0 0,-6"
|
||||
id="path4300"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 179,62 0,2 2,0 0,-2"
|
||||
id="path4300-6"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 35 KiB |
@@ -0,0 +1,929 @@
|
||||
/* reset styling (prevent conflicts with bootstrap, materialize.css, etc.) */
|
||||
|
||||
div.jsoneditor .jsoneditor-search input {
|
||||
height: auto;
|
||||
border: inherit;
|
||||
}
|
||||
|
||||
div.jsoneditor .jsoneditor-search input:focus {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
div.jsoneditor table {
|
||||
border-collapse: collapse;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
div.jsoneditor td,
|
||||
div.jsoneditor th {
|
||||
padding: 0;
|
||||
display: table-cell;
|
||||
text-align: left;
|
||||
vertical-align: inherit;
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
|
||||
div.jsoneditor-field,
|
||||
div.jsoneditor-value,
|
||||
div.jsoneditor-readonly {
|
||||
border: 1px solid transparent;
|
||||
min-height: 16px;
|
||||
min-width: 32px;
|
||||
padding: 2px;
|
||||
margin: 1px;
|
||||
word-wrap: break-word;
|
||||
float: left;
|
||||
}
|
||||
|
||||
/* adjust margin of p elements inside editable divs, needed for Opera, IE */
|
||||
|
||||
div.jsoneditor-field p,
|
||||
div.jsoneditor-value p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.jsoneditor-value {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
div.jsoneditor-readonly {
|
||||
min-width: 16px;
|
||||
color: gray;
|
||||
}
|
||||
|
||||
div.jsoneditor-empty {
|
||||
border-color: lightgray;
|
||||
border-style: dashed;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
div.jsoneditor-field.jsoneditor-empty::after,
|
||||
div.jsoneditor-value.jsoneditor-empty::after {
|
||||
pointer-events: none;
|
||||
color: lightgray;
|
||||
font-size: 8pt;
|
||||
}
|
||||
|
||||
div.jsoneditor-field.jsoneditor-empty::after {
|
||||
content: "field";
|
||||
}
|
||||
|
||||
div.jsoneditor-value.jsoneditor-empty::after {
|
||||
content: "value";
|
||||
}
|
||||
|
||||
div.jsoneditor-value.jsoneditor-url,
|
||||
a.jsoneditor-value.jsoneditor-url {
|
||||
color: green;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a.jsoneditor-value.jsoneditor-url {
|
||||
display: inline-block;
|
||||
padding: 2px;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
a.jsoneditor-value.jsoneditor-url:hover,
|
||||
a.jsoneditor-value.jsoneditor-url:focus {
|
||||
color: #ee422e;
|
||||
}
|
||||
|
||||
div.jsoneditor td.jsoneditor-separator {
|
||||
padding: 3px 0;
|
||||
vertical-align: top;
|
||||
color: gray;
|
||||
}
|
||||
|
||||
div.jsoneditor-field[contenteditable=true]:focus,
|
||||
div.jsoneditor-field[contenteditable=true]:hover,
|
||||
div.jsoneditor-value[contenteditable=true]:focus,
|
||||
div.jsoneditor-value[contenteditable=true]:hover,
|
||||
div.jsoneditor-field.jsoneditor-highlight,
|
||||
div.jsoneditor-value.jsoneditor-highlight {
|
||||
background-color: #FFFFAB;
|
||||
border: 1px solid yellow;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
div.jsoneditor-field.jsoneditor-highlight-active,
|
||||
div.jsoneditor-field.jsoneditor-highlight-active:focus,
|
||||
div.jsoneditor-field.jsoneditor-highlight-active:hover,
|
||||
div.jsoneditor-value.jsoneditor-highlight-active,
|
||||
div.jsoneditor-value.jsoneditor-highlight-active:focus,
|
||||
div.jsoneditor-value.jsoneditor-highlight-active:hover {
|
||||
background-color: #ffee00;
|
||||
border: 1px solid #ffc700;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
div.jsoneditor-value.jsoneditor-string {
|
||||
color: #008000;
|
||||
}
|
||||
|
||||
div.jsoneditor-value.jsoneditor-object,
|
||||
div.jsoneditor-value.jsoneditor-array {
|
||||
min-width: 16px;
|
||||
color: #808080;
|
||||
}
|
||||
|
||||
div.jsoneditor-value.jsoneditor-number {
|
||||
color: #ee422e;
|
||||
}
|
||||
|
||||
div.jsoneditor-value.jsoneditor-boolean {
|
||||
color: #ff8c00;
|
||||
}
|
||||
|
||||
div.jsoneditor-value.jsoneditor-null {
|
||||
color: #004ED0;
|
||||
}
|
||||
|
||||
div.jsoneditor-value.jsoneditor-invalid {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
div.jsoneditor-tree button {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
background: transparent url("img/jsoneditor-icons.svg");
|
||||
}
|
||||
|
||||
div.jsoneditor-mode-view tr.jsoneditor-expandable td.jsoneditor-tree,
|
||||
div.jsoneditor-mode-form tr.jsoneditor-expandable td.jsoneditor-tree {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div.jsoneditor-tree button.jsoneditor-collapsed {
|
||||
background-position: 0 -48px;
|
||||
}
|
||||
|
||||
div.jsoneditor-tree button.jsoneditor-expanded {
|
||||
background-position: 0 -72px;
|
||||
}
|
||||
|
||||
div.jsoneditor-tree button.jsoneditor-contextmenu {
|
||||
background-position: -48px -72px;
|
||||
}
|
||||
|
||||
div.jsoneditor-tree button.jsoneditor-contextmenu:hover,
|
||||
div.jsoneditor-tree button.jsoneditor-contextmenu:focus,
|
||||
div.jsoneditor-tree button.jsoneditor-contextmenu.jsoneditor-selected,
|
||||
tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-contextmenu {
|
||||
background-position: -48px -48px;
|
||||
}
|
||||
|
||||
div.jsoneditor-tree *:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
div.jsoneditor-tree button:focus {
|
||||
/* TODO: nice outline for buttons with focus
|
||||
outline: #97B0F8 solid 2px;
|
||||
box-shadow: 0 0 8px #97B0F8;
|
||||
*/
|
||||
background-color: #f5f5f5;
|
||||
outline: #e5e5e5 solid 1px;
|
||||
}
|
||||
|
||||
div.jsoneditor-tree button.jsoneditor-invisible {
|
||||
visibility: hidden;
|
||||
background: none;
|
||||
}
|
||||
|
||||
div.jsoneditor {
|
||||
color: #1A1A1A;
|
||||
border: 1px solid #3883fa;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
div.jsoneditor-tree table.jsoneditor-tree {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.jsoneditor-outer {
|
||||
position: static;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: -35px 0 0 0;
|
||||
padding: 35px 0 0 0;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
textarea.jsoneditor-text,
|
||||
.ace-jsoneditor {
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
div.jsoneditor-tree {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
textarea.jsoneditor-text {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
outline-width: 0;
|
||||
border: none;
|
||||
background-color: white;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
tr.jsoneditor-highlight,
|
||||
tr.jsoneditor-selected {
|
||||
background-color: #e6e6e6;
|
||||
}
|
||||
|
||||
tr.jsoneditor-selected button.jsoneditor-dragarea,
|
||||
tr.jsoneditor-selected button.jsoneditor-contextmenu {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-dragarea,
|
||||
tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-contextmenu {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
div.jsoneditor-tree button.jsoneditor-dragarea {
|
||||
background: url("img/jsoneditor-icons.svg") -72px -72px;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
div.jsoneditor-tree button.jsoneditor-dragarea:hover,
|
||||
div.jsoneditor-tree button.jsoneditor-dragarea:focus,
|
||||
tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-dragarea {
|
||||
background-position: -72px -48px;
|
||||
}
|
||||
|
||||
div.jsoneditor tr,
|
||||
div.jsoneditor th,
|
||||
div.jsoneditor td {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.jsoneditor td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
div.jsoneditor td.jsoneditor-tree {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
div.jsoneditor-field,
|
||||
div.jsoneditor-value,
|
||||
div.jsoneditor td,
|
||||
div.jsoneditor th,
|
||||
div.jsoneditor textarea,
|
||||
.jsoneditor-schema-error {
|
||||
font-family: droid sans mono, consolas, monospace, courier new, courier, sans-serif;
|
||||
font-size: 10pt;
|
||||
color: #1A1A1A;
|
||||
}
|
||||
|
||||
/* popover */
|
||||
|
||||
.jsoneditor-schema-error {
|
||||
cursor: default;
|
||||
display: inline-block;
|
||||
/*font-family: arial, sans-serif;*/
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
div.jsoneditor-tree .jsoneditor-schema-error {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 0;
|
||||
margin: 0 4px 0 0;
|
||||
background: url("img/jsoneditor-icons.svg") -168px -48px;
|
||||
}
|
||||
|
||||
.jsoneditor-schema-error .jsoneditor-popover {
|
||||
background-color: #4c4c4c;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 0 5px rgba(0,0,0,0.4);
|
||||
color: #fff;
|
||||
display: none;
|
||||
padding: 7px 10px;
|
||||
position: absolute;
|
||||
width: 200px;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-above {
|
||||
bottom: 32px;
|
||||
left: -98px;
|
||||
}
|
||||
|
||||
.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-below {
|
||||
top: 32px;
|
||||
left: -98px;
|
||||
}
|
||||
|
||||
.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-left {
|
||||
top: -7px;
|
||||
right: 32px;
|
||||
}
|
||||
|
||||
.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-right {
|
||||
top: -7px;
|
||||
left: 32px;
|
||||
}
|
||||
|
||||
.jsoneditor-schema-error .jsoneditor-popover:before {
|
||||
border-right: 7px solid transparent;
|
||||
border-left: 7px solid transparent;
|
||||
content: '';
|
||||
display: block;
|
||||
left: 50%;
|
||||
margin-left: -7px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-above:before {
|
||||
border-top: 7px solid #4c4c4c;
|
||||
bottom: -7px;
|
||||
}
|
||||
|
||||
.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-below:before {
|
||||
border-bottom: 7px solid #4c4c4c;
|
||||
top: -7px;
|
||||
}
|
||||
|
||||
.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-left:before {
|
||||
border-left: 7px solid #4c4c4c;
|
||||
border-top: 7px solid transparent;
|
||||
border-bottom: 7px solid transparent;
|
||||
content: '';
|
||||
top: 19px;
|
||||
right: -14px;
|
||||
left: inherit;
|
||||
margin-left: inherit;
|
||||
margin-top: -7px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-right:before {
|
||||
border-right: 7px solid #4c4c4c;
|
||||
border-top: 7px solid transparent;
|
||||
border-bottom: 7px solid transparent;
|
||||
content: '';
|
||||
top: 19px;
|
||||
left: -14px;
|
||||
margin-left: inherit;
|
||||
margin-top: -7px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.jsoneditor-schema-error:hover .jsoneditor-popover,
|
||||
.jsoneditor-schema-error:focus .jsoneditor-popover {
|
||||
display: block;
|
||||
-webkit-animation: fade-in .3s linear 1, move-up .3s linear 1;
|
||||
-moz-animation: fade-in .3s linear 1, move-up .3s linear 1;
|
||||
-ms-animation: fade-in .3s linear 1, move-up .3s linear 1;
|
||||
}
|
||||
|
||||
@-webkit-keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@-moz-keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@-ms-keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*@-webkit-keyframes move-up {*/
|
||||
|
||||
/*from { bottom: 24px; }*/
|
||||
|
||||
/*to { bottom: 32px; }*/
|
||||
|
||||
/*}*/
|
||||
|
||||
/*@-moz-keyframes move-up {*/
|
||||
|
||||
/*from { bottom: 24px; }*/
|
||||
|
||||
/*to { bottom: 32px; }*/
|
||||
|
||||
/*}*/
|
||||
|
||||
/*@-ms-keyframes move-up {*/
|
||||
|
||||
/*from { bottom: 24px; }*/
|
||||
|
||||
/*to { bottom: 32px; }*/
|
||||
|
||||
/*}*/
|
||||
|
||||
/* JSON schema errors displayed at the bottom of the editor in mode text and code */
|
||||
|
||||
.jsoneditor .jsoneditor-text-errors {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background-color: #ffef8b;
|
||||
border-top: 1px solid #ffd700;
|
||||
}
|
||||
|
||||
.jsoneditor .jsoneditor-text-errors td {
|
||||
padding: 3px 6px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.jsoneditor-text-errors .jsoneditor-schema-error {
|
||||
border: none;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 0;
|
||||
margin: 0 4px 0 0;
|
||||
background: url("img/jsoneditor-icons.svg") -168px -48px;
|
||||
}
|
||||
/* ContextMenu - main menu */
|
||||
|
||||
div.jsoneditor-contextmenu-root {
|
||||
position: relative;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu {
|
||||
position: absolute;
|
||||
box-sizing: content-box;
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu ul,
|
||||
div.jsoneditor-contextmenu li {
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu ul {
|
||||
position: relative;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 124px;
|
||||
background: white;
|
||||
border: 1px solid #d3d3d3;
|
||||
box-shadow: 2px 2px 12px rgba(128, 128, 128, 0.3);
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu ul li button {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 124px;
|
||||
height: 24px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: #4d4d4d;
|
||||
background: transparent;
|
||||
font-size: 10pt;
|
||||
font-family: arial, sans-serif;
|
||||
box-sizing: border-box;
|
||||
line-height: 26px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* Fix button padding in firefox */
|
||||
|
||||
div.jsoneditor-contextmenu ul li button::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu ul li button:hover,
|
||||
div.jsoneditor-contextmenu ul li button:focus {
|
||||
color: #1a1a1a;
|
||||
background-color: #f5f5f5;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu ul li button.jsoneditor-default {
|
||||
width: 92px;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu ul li button.jsoneditor-expand {
|
||||
float: right;
|
||||
width: 32px;
|
||||
height: 24px;
|
||||
border-left: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu div.jsoneditor-icon {
|
||||
float: left;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background-image: url("img/jsoneditor-icons.svg");
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu ul li button div.jsoneditor-expand {
|
||||
float: right;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 0;
|
||||
margin: 0 4px 0 0;
|
||||
background: url("img/jsoneditor-icons.svg") 0 -72px;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu ul li button:hover div.jsoneditor-expand,
|
||||
div.jsoneditor-contextmenu ul li button:focus div.jsoneditor-expand,
|
||||
div.jsoneditor-contextmenu ul li.jsoneditor-selected div.jsoneditor-expand,
|
||||
div.jsoneditor-contextmenu ul li button.jsoneditor-expand:hover div.jsoneditor-expand,
|
||||
div.jsoneditor-contextmenu ul li button.jsoneditor-expand:focus div.jsoneditor-expand {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu div.jsoneditor-separator {
|
||||
height: 0;
|
||||
border-top: 1px solid #e5e5e5;
|
||||
padding-top: 5px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu button.jsoneditor-remove > div.jsoneditor-icon {
|
||||
background-position: -24px -24px;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu button.jsoneditor-remove:hover > div.jsoneditor-icon,
|
||||
div.jsoneditor-contextmenu button.jsoneditor-remove:focus > div.jsoneditor-icon {
|
||||
background-position: -24px 0;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu button.jsoneditor-append > div.jsoneditor-icon {
|
||||
background-position: 0 -24px;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu button.jsoneditor-append:hover > div.jsoneditor-icon,
|
||||
div.jsoneditor-contextmenu button.jsoneditor-append:focus > div.jsoneditor-icon {
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu button.jsoneditor-insert > div.jsoneditor-icon {
|
||||
background-position: 0 -24px;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu button.jsoneditor-insert:hover > div.jsoneditor-icon,
|
||||
div.jsoneditor-contextmenu button.jsoneditor-insert:focus > div.jsoneditor-icon {
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu button.jsoneditor-duplicate > div.jsoneditor-icon {
|
||||
background-position: -48px -24px;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu button.jsoneditor-duplicate:hover > div.jsoneditor-icon,
|
||||
div.jsoneditor-contextmenu button.jsoneditor-duplicate:focus > div.jsoneditor-icon {
|
||||
background-position: -48px 0;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu button.jsoneditor-sort-asc > div.jsoneditor-icon {
|
||||
background-position: -168px -24px;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu button.jsoneditor-sort-asc:hover > div.jsoneditor-icon,
|
||||
div.jsoneditor-contextmenu button.jsoneditor-sort-asc:focus > div.jsoneditor-icon {
|
||||
background-position: -168px 0;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu button.jsoneditor-sort-desc > div.jsoneditor-icon {
|
||||
background-position: -192px -24px;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu button.jsoneditor-sort-desc:hover > div.jsoneditor-icon,
|
||||
div.jsoneditor-contextmenu button.jsoneditor-sort-desc:focus > div.jsoneditor-icon {
|
||||
background-position: -192px 0;
|
||||
}
|
||||
|
||||
/* ContextMenu - sub menu */
|
||||
|
||||
div.jsoneditor-contextmenu ul li button.jsoneditor-selected,
|
||||
div.jsoneditor-contextmenu ul li button.jsoneditor-selected:hover,
|
||||
div.jsoneditor-contextmenu ul li button.jsoneditor-selected:focus {
|
||||
color: white;
|
||||
background-color: #ee422e;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu ul li {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu ul li ul {
|
||||
display: none;
|
||||
position: relative;
|
||||
left: -10px;
|
||||
top: 0;
|
||||
border: none;
|
||||
box-shadow: inset 0 0 10px rgba(128, 128, 128, 0.5);
|
||||
padding: 0 10px;
|
||||
/* TODO: transition is not supported on IE8-9 */
|
||||
-webkit-transition: all 0.3s ease-out;
|
||||
-moz-transition: all 0.3s ease-out;
|
||||
-o-transition: all 0.3s ease-out;
|
||||
transition: all 0.3s ease-out;
|
||||
}
|
||||
|
||||
|
||||
|
||||
div.jsoneditor-contextmenu ul li ul li button {
|
||||
padding-left: 24px;
|
||||
animation: all ease-in-out 1s;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu ul li ul li button:hover,
|
||||
div.jsoneditor-contextmenu ul li ul li button:focus {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu button.jsoneditor-type-string > div.jsoneditor-icon {
|
||||
background-position: -144px -24px;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu button.jsoneditor-type-string:hover > div.jsoneditor-icon,
|
||||
div.jsoneditor-contextmenu button.jsoneditor-type-string:focus > div.jsoneditor-icon,
|
||||
div.jsoneditor-contextmenu button.jsoneditor-type-string.jsoneditor-selected > div.jsoneditor-icon {
|
||||
background-position: -144px 0;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu button.jsoneditor-type-auto > div.jsoneditor-icon {
|
||||
background-position: -120px -24px;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu button.jsoneditor-type-auto:hover > div.jsoneditor-icon,
|
||||
div.jsoneditor-contextmenu button.jsoneditor-type-auto:focus > div.jsoneditor-icon,
|
||||
div.jsoneditor-contextmenu button.jsoneditor-type-auto.jsoneditor-selected > div.jsoneditor-icon {
|
||||
background-position: -120px 0;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu button.jsoneditor-type-object > div.jsoneditor-icon {
|
||||
background-position: -72px -24px;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu button.jsoneditor-type-object:hover > div.jsoneditor-icon,
|
||||
div.jsoneditor-contextmenu button.jsoneditor-type-object:focus > div.jsoneditor-icon,
|
||||
div.jsoneditor-contextmenu button.jsoneditor-type-object.jsoneditor-selected > div.jsoneditor-icon {
|
||||
background-position: -72px 0;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu button.jsoneditor-type-array > div.jsoneditor-icon {
|
||||
background-position: -96px -24px;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu button.jsoneditor-type-array:hover > div.jsoneditor-icon,
|
||||
div.jsoneditor-contextmenu button.jsoneditor-type-array:focus > div.jsoneditor-icon,
|
||||
div.jsoneditor-contextmenu button.jsoneditor-type-array.jsoneditor-selected > div.jsoneditor-icon {
|
||||
background-position: -96px 0;
|
||||
}
|
||||
|
||||
div.jsoneditor-contextmenu button.jsoneditor-type-modes > div.jsoneditor-icon {
|
||||
background-image: none;
|
||||
width: 6px;
|
||||
}
|
||||
div.jsoneditor-menu {
|
||||
width: 100%;
|
||||
height: 35px;
|
||||
padding: 2px;
|
||||
margin: 0;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
color: white;
|
||||
background-color: #3883fa;
|
||||
border-bottom: 1px solid #3883fa;
|
||||
}
|
||||
|
||||
div.jsoneditor-menu > button,
|
||||
div.jsoneditor-menu > div.jsoneditor-modes > button {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
margin: 2px;
|
||||
padding: 0;
|
||||
border-radius: 2px;
|
||||
border: 1px solid transparent;
|
||||
background: transparent url("img/jsoneditor-icons.svg");
|
||||
color: white;
|
||||
opacity: 0.8;
|
||||
font-family: arial, sans-serif;
|
||||
font-size: 10pt;
|
||||
float: left;
|
||||
}
|
||||
|
||||
div.jsoneditor-menu > button:hover,
|
||||
div.jsoneditor-menu > div.jsoneditor-modes > button:hover {
|
||||
background-color: rgba(255,255,255,0.2);
|
||||
border: 1px solid rgba(255,255,255,0.4);
|
||||
}
|
||||
|
||||
div.jsoneditor-menu > button:focus,
|
||||
div.jsoneditor-menu > button:active,
|
||||
div.jsoneditor-menu > div.jsoneditor-modes > button:focus,
|
||||
div.jsoneditor-menu > div.jsoneditor-modes > button:active {
|
||||
background-color: rgba(255,255,255,0.3);
|
||||
}
|
||||
|
||||
div.jsoneditor-menu > button:disabled,
|
||||
div.jsoneditor-menu > div.jsoneditor-modes > button:disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
div.jsoneditor-menu > button.jsoneditor-collapse-all {
|
||||
background-position: 0 -96px;
|
||||
}
|
||||
|
||||
div.jsoneditor-menu > button.jsoneditor-expand-all {
|
||||
background-position: 0 -120px;
|
||||
}
|
||||
|
||||
div.jsoneditor-menu > button.jsoneditor-undo {
|
||||
background-position: -24px -96px;
|
||||
}
|
||||
|
||||
div.jsoneditor-menu > button.jsoneditor-undo:disabled {
|
||||
background-position: -24px -120px;
|
||||
}
|
||||
|
||||
div.jsoneditor-menu > button.jsoneditor-redo {
|
||||
background-position: -48px -96px;
|
||||
}
|
||||
|
||||
div.jsoneditor-menu > button.jsoneditor-redo:disabled {
|
||||
background-position: -48px -120px;
|
||||
}
|
||||
|
||||
div.jsoneditor-menu > button.jsoneditor-compact {
|
||||
background-position: -72px -96px;
|
||||
}
|
||||
|
||||
div.jsoneditor-menu > button.jsoneditor-format {
|
||||
background-position: -72px -120px;
|
||||
}
|
||||
|
||||
div.jsoneditor-menu > div.jsoneditor-modes {
|
||||
display: inline-block;
|
||||
float: left;
|
||||
}
|
||||
|
||||
div.jsoneditor-menu > div.jsoneditor-modes > button {
|
||||
background-image: none;
|
||||
width: auto;
|
||||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
div.jsoneditor-menu > button.jsoneditor-separator,
|
||||
div.jsoneditor-menu > div.jsoneditor-modes > button.jsoneditor-separator {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
div.jsoneditor-menu a {
|
||||
font-family: arial, sans-serif;
|
||||
font-size: 10pt;
|
||||
color: white;
|
||||
opacity: 0.8;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
div.jsoneditor-menu a:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
div.jsoneditor-menu a.jsoneditor-poweredBy {
|
||||
font-size: 8pt;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
padding: 10px;
|
||||
}
|
||||
table.jsoneditor-search input,
|
||||
table.jsoneditor-search div.jsoneditor-results {
|
||||
font-family: arial, sans-serif;
|
||||
font-size: 10pt;
|
||||
color: #1A1A1A;
|
||||
background: transparent;
|
||||
/* For Firefox */
|
||||
}
|
||||
|
||||
table.jsoneditor-search div.jsoneditor-results {
|
||||
color: white;
|
||||
padding-right: 5px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
table.jsoneditor-search {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
top: 4px;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
table.jsoneditor-search div.jsoneditor-frame {
|
||||
border: 1px solid transparent;
|
||||
background-color: white;
|
||||
padding: 0 2px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
table.jsoneditor-search div.jsoneditor-frame table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table.jsoneditor-search input {
|
||||
width: 120px;
|
||||
border: none;
|
||||
outline: none;
|
||||
margin: 1px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
table.jsoneditor-search button {
|
||||
width: 16px;
|
||||
height: 24px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
background: url("img/jsoneditor-icons.svg");
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
table.jsoneditor-search button:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
table.jsoneditor-search button.jsoneditor-refresh {
|
||||
width: 18px;
|
||||
background-position: -99px -73px;
|
||||
}
|
||||
|
||||
table.jsoneditor-search button.jsoneditor-next {
|
||||
cursor: pointer;
|
||||
background-position: -124px -73px;
|
||||
}
|
||||
|
||||
table.jsoneditor-search button.jsoneditor-next:hover {
|
||||
background-position: -124px -49px;
|
||||
}
|
||||
|
||||
table.jsoneditor-search button.jsoneditor-previous {
|
||||
cursor: pointer;
|
||||
background-position: -148px -73px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
table.jsoneditor-search button.jsoneditor-previous:hover {
|
||||
background-position: -148px -49px;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<div class="layui-tab layui-tab-brief">
|
||||
<ul class="layui-tab-title site-demo-title">
|
||||
<li class="layui-this add-client-title"></li>
|
||||
</ul>
|
||||
<div class="main-content">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label i18n-client-name"></label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="name" autocomplete="off" placeholder="" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label i18n-client-key"></label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="clientKey" autocomplete="off" placeholder="" class="layui-input">
|
||||
</div>
|
||||
<div class="layui-form-mid"><a class="layui-btn layui-btn-mini random-key random"></a></div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn add"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$(".add-client-title").html($.i18n.prop('client.add'));
|
||||
$(".i18n-client-name").html($.i18n.prop('client.name'));
|
||||
$(".i18n-client-key").html($.i18n.prop('client.key'));
|
||||
$("input[name='name']").attr("placeholder", $.i18n.prop('client.name.placeholder'));
|
||||
$("input[name='clientKey']").attr("placeholder", $.i18n.prop('client.key.placeholder'));
|
||||
$(".random").html($.i18n.prop('client.randomkey'));
|
||||
$(".add").html($.i18n.prop('public.submit'));
|
||||
$(".random").click(function(){
|
||||
$("input[name='clientKey']").val(uuid());
|
||||
});
|
||||
|
||||
$(".add").click(function(){
|
||||
var name = $("input[name='name']").val();
|
||||
if(name == ""){
|
||||
layer.alert($.i18n.prop('client.notice.inputname'), {
|
||||
title: $.i18n.prop('public.tips')
|
||||
});
|
||||
return;
|
||||
}
|
||||
var clientKey = $("input[name='clientKey']").val();
|
||||
if(clientKey == ""){
|
||||
layer.alert($.i18n.prop('client.notice.inputkey'), {
|
||||
title: $.i18n.prop('public.tips')
|
||||
});
|
||||
return;
|
||||
}
|
||||
clientList.push({
|
||||
name:name,
|
||||
clientKey:clientKey,
|
||||
proxyMappings:[]
|
||||
});
|
||||
|
||||
api_invoke("/config/update", clientList, function(data) {
|
||||
if (data.code == 20000) {
|
||||
layer.alert($.i18n.prop('client.notice.addsuccess'), {title: $.i18n.prop('public.tips')}, function(index){
|
||||
layer.close(index);
|
||||
location.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(".back").click(function(){
|
||||
load_page("html/client/list.html");
|
||||
});
|
||||
|
||||
function uuid() {
|
||||
var s = [];
|
||||
var hexDigits = "0123456789abcdef";
|
||||
for (var i = 0; i < 36; i++) {
|
||||
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
|
||||
}
|
||||
s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
|
||||
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
|
||||
s[8] = s[13] = s[18] = s[23] = "";
|
||||
|
||||
var uuid = s.join("");
|
||||
return uuid;
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,73 @@
|
||||
<div class="layui-tab layui-tab-brief">
|
||||
<ul class="layui-tab-title site-demo-title">
|
||||
<li class="layui-this edit-client-title"></li>
|
||||
</ul>
|
||||
<div class="main-content">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label i18n-client-name"></label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="name" autocomplete="off" placeholder="" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label i18n-client-key"></label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="clientKey" autocomplete="off" placeholder="" class="layui-input">
|
||||
</div>
|
||||
<div class="layui-form-mid"><a class="layui-btn layui-btn-mini random-key random"></a></div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn update"></button>
|
||||
<button class="layui-btn layui-btn-primary back"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$(".edit-client-title").html($.i18n.prop('client.edit'));
|
||||
$(".i18n-client-name").html($.i18n.prop('client.name'));
|
||||
$(".i18n-client-key").html($.i18n.prop('client.key'));
|
||||
$("input[name='name']").attr("placeholder", $.i18n.prop('client.name.placeholder'));
|
||||
$("input[name='clientKey']").attr("placeholder", $.i18n.prop('client.key.placeholder'));
|
||||
$(".random").html($.i18n.prop('client.randomkey'));
|
||||
$(".update").html($.i18n.prop('public.submit'));
|
||||
$(".back").html($.i18n.prop('public.back'));
|
||||
$("input[name='name']").val(clientList[clientIndex].name);
|
||||
$("input[name='clientKey']").val(clientList[clientIndex].clientKey);
|
||||
|
||||
$(".random").click(function(){
|
||||
$("input[name='clientKey']").val(uuid());
|
||||
});
|
||||
|
||||
$(".update").click(function(){
|
||||
clientList[clientIndex]['name'] = $("input[name='name']").val();
|
||||
clientList[clientIndex]['clientKey'] = $("input[name='clientKey']").val();
|
||||
api_invoke("/config/update", clientList, function(data) {
|
||||
if (data.code == 20000) {
|
||||
layer.alert($.i18n.prop('public.notice.updatesuccess'), {title: $.i18n.prop('public.tips')}, function(index){
|
||||
layer.close(index);
|
||||
location.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(".back").click(function(){
|
||||
load_page("html/client/list.html");
|
||||
});
|
||||
|
||||
function uuid() {
|
||||
var s = [];
|
||||
var hexDigits = "0123456789abcdef";
|
||||
for (var i = 0; i < 36; i++) {
|
||||
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
|
||||
}
|
||||
s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
|
||||
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
|
||||
s[8] = s[13] = s[18] = s[23] = "";
|
||||
|
||||
var uuid = s.join("");
|
||||
return uuid;
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,75 @@
|
||||
<div class="layui-tab layui-tab-brief">
|
||||
<ul class="layui-tab-title site-demo-title">
|
||||
<li class="layui-this detail-page"></li>
|
||||
</ul>
|
||||
<div class="main-content"></div>
|
||||
</div>
|
||||
<script>
|
||||
$(".layui-this.detail-page").html($.i18n.prop('client.list'));
|
||||
window.clientList = [];
|
||||
api_invoke("/config/detail", {}, function(data) {
|
||||
if (data.code == 20000) {
|
||||
clientList = data.data;
|
||||
var html = template($("#client-tpl").html(), data);
|
||||
$(".main-content").html(html);
|
||||
$(".mapping-config").click(function() {
|
||||
window.clientIndex = $(this).attr("data-index");
|
||||
load_page("html/lan/list.html");
|
||||
});
|
||||
$(".client-edit").click(function() {
|
||||
window.clientIndex = $(this).attr("data-index");
|
||||
load_page("html/client/edit.html");
|
||||
});
|
||||
$(".client-delete").click(function() {
|
||||
window.clientIndex = $(this).attr("data-index");
|
||||
layer.confirm($.i18n.prop('public.confirm.delete'), {
|
||||
title: $.i18n.prop('public.tips'),
|
||||
btn : [ $.i18n.prop('public.ok'), $.i18n.prop('public.cancel') ]
|
||||
}, function(i) {
|
||||
layer.close(i);
|
||||
clientList.splice(clientIndex, 1);
|
||||
api_invoke("/config/update", clientList, function(data) {
|
||||
if (data.code != 20000) {
|
||||
layer.alert(data.message);
|
||||
} else {
|
||||
location.reload();
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
} else {
|
||||
alert(data.message);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script id="client-tpl" type="text/html">
|
||||
<table class="layui-table" lay-skin="line">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="th-client-name"><%:=$.i18n.prop('client.name')%></th>
|
||||
<th class="th-client-key"><%:=$.i18n.prop('client.key')%></th>
|
||||
<th class="th-status"><%:=$.i18n.prop('client.status')%></th>
|
||||
<th class="th-options"><%:=$.i18n.prop('public.options')%></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%for(var i = 0; i < data.length; i++) {%>
|
||||
<tr>
|
||||
<td><%:=data[i].name%></td>
|
||||
<td><%:=data[i].clientKey%></td>
|
||||
<td>
|
||||
<% if(data[i].status == 1){ %>
|
||||
<span class="layui-badge layui-bg-green"><%:=$.i18n.prop('client.status.online')%></span>
|
||||
<% } else { %>
|
||||
<span class="layui-badge layui-bg-gray"><%:=$.i18n.prop('client.status.offline')%></span>
|
||||
<% }%>
|
||||
</td>
|
||||
<td>
|
||||
<a data-index="<%:=i%>" class="layui-btn layui-btn-mini client-edit"><%:=$.i18n.prop('public.edit')%></a>
|
||||
<a data-index="<%:=i%>" class="layui-btn layui-btn-danger layui-btn-mini client-delete"><%:=$.i18n.prop('public.delete')%></a>
|
||||
</td>
|
||||
</tr>
|
||||
<%}%>
|
||||
</tbody>
|
||||
</table>
|
||||
</script>
|
||||
@@ -0,0 +1,86 @@
|
||||
<div class="layui-tab layui-tab-brief">
|
||||
<ul class="layui-tab-title site-demo-title">
|
||||
<li class="layui-this i18n-new-proxy"></li>
|
||||
</ul>
|
||||
<div class="main-content">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label i18n-lan-name"></label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="name" autocomplete="off" placeholder="" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label i18n-lan-inetport"></label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="inetPort" autocomplete="off" placeholder="" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label i18n-lan-ip"></label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="lan" autocomplete="off" placeholder="" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn add"></button>
|
||||
<button class="layui-btn layui-btn-primary back"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$(".i18n-new-proxy").html($.i18n.prop('lan.addnewconfig'));
|
||||
$(".i18n-lan-name").html($.i18n.prop('lan.name'));
|
||||
$(".i18n-lan-inetport").html($.i18n.prop('lan.inetport'));
|
||||
$(".i18n-lan-ip").html($.i18n.prop('lan.ip'));
|
||||
$("input[name='inetPort']").attr("placeholder", $.i18n.prop('lan.inetport.placeholder'));
|
||||
$("input[name='lan']").attr("placeholder", $.i18n.prop('lan.ip.placeholder'));
|
||||
$(".add").html($.i18n.prop('public.submit'));
|
||||
$(".back").html($.i18n.prop('public.back'));
|
||||
$(".add").click(function(){
|
||||
var name = $("input[name='name']").val();
|
||||
if(name == ""){
|
||||
layer.alert($.i18n.prop('lan.notice.inputname'), {title:"Tips"});
|
||||
return;
|
||||
}
|
||||
var inetPort = $("input[name='inetPort']").val();
|
||||
if(inetPort == ""){
|
||||
layer.alert($.i18n.prop('lan.notice.inputinetport'), {title:"Tips"});
|
||||
return;
|
||||
}
|
||||
if(!check_port(inetPort)){
|
||||
layer.alert($.i18n.prop('lan.notice.errorport'), {title:"Tips"});
|
||||
return;
|
||||
}
|
||||
|
||||
var lan = $("input[name='lan']").val();
|
||||
if(lan == ""){
|
||||
layer.alert($.i18n.prop('lan.notice.inputlan'), {title:"Tips"});
|
||||
return;
|
||||
}
|
||||
if(!check_lan(lan)){
|
||||
layer.alert($.i18n.prop('lan.notice.errorlan'), {title:"Tips"});
|
||||
return;
|
||||
}
|
||||
|
||||
clientList[clientIndex].proxyMappings.push({
|
||||
name:name,
|
||||
inetPort:inetPort,
|
||||
lan:lan
|
||||
});
|
||||
|
||||
api_invoke("/config/update", clientList, function(data) {
|
||||
if (data.code == 20000) {
|
||||
layer.alert($.i18n.prop('public.notice.addsuccess'), {title:"Tips"}, function(index){
|
||||
layer.close(index);
|
||||
load_page("html/lan/list.html");
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(".back").click(function(){
|
||||
load_page("html/lan/list.html");
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,91 @@
|
||||
<div class="layui-tab layui-tab-brief">
|
||||
<ul class="layui-tab-title site-demo-title">
|
||||
<li class="layui-this i18n-lan-title"></li>
|
||||
</ul>
|
||||
<div class="main-content">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label i18n-lan-name"></label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="name" autocomplete="off" placeholder="" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label i18n-lan-inetport"></label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="inetPort" autocomplete="off" placeholder="" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label i18n-lan-ip"></label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="lan" autocomplete="off" placeholder="" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn update"></button>
|
||||
<button class="layui-btn layui-btn-primary back"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$(function(){
|
||||
$("input[name='name']").val(clientList[clientIndex].proxyMappings[mappingIndex].name);
|
||||
$("input[name='inetPort']").val(clientList[clientIndex].proxyMappings[mappingIndex].inetPort);
|
||||
$("input[name='lan']").val(clientList[clientIndex].proxyMappings[mappingIndex].lan);
|
||||
$(".i18n-lan-title").html($.i18n.prop('lan.editconfig'));
|
||||
$(".i18n-lan-name").html($.i18n.prop('lan.name'));
|
||||
$(".i18n-lan-inetport").html($.i18n.prop('lan.inetport'));
|
||||
$(".i18n-lan-ip").html($.i18n.prop('lan.ip'));
|
||||
$("input[name='inetPort']").attr("placeholder", $.i18n.prop('lan.inetport.placeholder'));
|
||||
$("input[name='lan']").attr("placeholder", $.i18n.prop('lan.ip.placeholder'));
|
||||
$(".update").html($.i18n.prop('public.submit'));
|
||||
$(".back").html($.i18n.prop('public.back'));
|
||||
});
|
||||
$(".update").click(function(){
|
||||
var name = $("input[name='name']").val();
|
||||
if(name == ""){
|
||||
layer.alert($.i18n.prop('lan.notice.inputname'), {title:"Tips"});
|
||||
return;
|
||||
}
|
||||
var inetPort = $("input[name='inetPort']").val();
|
||||
if(inetPort == ""){
|
||||
layer.alert($.i18n.prop('lan.notice.inputinetport'), {title:"Tips"});
|
||||
return;
|
||||
}
|
||||
if(!check_port(inetPort)){
|
||||
layer.alert($.i18n.prop('lan.notice.errorport'), {title:"Tips"});
|
||||
return;
|
||||
}
|
||||
|
||||
var lan = $("input[name='lan']").val();
|
||||
if(lan == ""){
|
||||
layer.alert($.i18n.prop('lan.notice.inputlan'), {title:"Tips"});
|
||||
return;
|
||||
}
|
||||
if(!check_lan(lan)){
|
||||
layer.alert($.i18n.prop('lan.notice.errorlan'), {title:"Tips"});
|
||||
return;
|
||||
}
|
||||
|
||||
clientList[clientIndex].proxyMappings[mappingIndex]= {
|
||||
name:name,
|
||||
inetPort:inetPort,
|
||||
lan:lan
|
||||
};
|
||||
|
||||
api_invoke("/config/update", clientList, function(data) {
|
||||
if (data.code == 20000) {
|
||||
layer.alert($.i18n.prop('public.notice.updatesuccess'), {title:"Tips"}, function(index){
|
||||
layer.close(index);
|
||||
load_page("html/lan/list.html");
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(".back").click(function(){
|
||||
load_page("html/lan/list.html");
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,80 @@
|
||||
<div class="layui-tab layui-tab-brief">
|
||||
<ul class="layui-tab-title site-demo-title">
|
||||
<li class="layui-this tab-title"></li>
|
||||
</ul>
|
||||
<div class="main-content"></div>
|
||||
<div class="layui-input-block" style="float: left; margin-left: 15px;">
|
||||
<button class="layui-btn mapping-add"></button>
|
||||
</div>
|
||||
</div>
|
||||
<script id="mapping-tpl" type="text/html">
|
||||
<table class="layui-table" lay-skin="line">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="i18n-lan-name"></th>
|
||||
<th class="i18n-lan-inetport"></th>
|
||||
<th class="i18n-lan-ip"></th>
|
||||
<th class="i18n-lan-options"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%for(var i = 0; i < data.length; i++) {%>
|
||||
<tr>
|
||||
<td><%:=data[i].name%></td>
|
||||
<td><%:=data[i].inetPort%></td>
|
||||
<td><%:=data[i].lan%></td>
|
||||
<td>
|
||||
<a data-index="<%:=i%>" class="layui-btn layui-btn-mini mapping-edit"></a>
|
||||
<a data-index="<%:=i%>" class="layui-btn layui-btn-danger layui-btn-mini mapping-delete"></a>
|
||||
</td>
|
||||
</tr>
|
||||
<%}%>
|
||||
</tbody>
|
||||
</table>
|
||||
</script>
|
||||
<script>
|
||||
$(".tab-title").html(clientList[clientIndex].name + " - " + $.i18n.prop('lan.proxyconfig'));
|
||||
$(".mapping-add").html($.i18n.prop('lan.addnewconfig'));
|
||||
var html = template($("#mapping-tpl").html(), {
|
||||
data : clientList[clientIndex].proxyMappings
|
||||
});
|
||||
$(".main-content").html(html);
|
||||
$(".i18n-lan-name").html($.i18n.prop('lan.name'));
|
||||
$(".i18n-lan-inetport").html($.i18n.prop('lan.inetport'));
|
||||
$(".i18n-lan-ip").html($.i18n.prop('lan.ip'));
|
||||
$(".i18n-lan-options").html($.i18n.prop('public.options'));
|
||||
$(".mapping-edit").html($.i18n.prop('public.edit'));
|
||||
$(".mapping-delete").html($.i18n.prop('public.delete'));
|
||||
$(".mapping-config").click(function() {
|
||||
window.clientIndex = $(this).attr("data-index");
|
||||
load_page("html/lan/list.html");
|
||||
});
|
||||
$(".mapping-edit").click(function() {
|
||||
window.mappingIndex = $(this).attr("data-index");
|
||||
load_page("html/lan/edit.html");
|
||||
});
|
||||
$(".mapping-delete").click(function() {
|
||||
var mappingIndex = $(this).attr("data-index");
|
||||
layer.confirm($.i18n.prop('public.confirm.delete'), {
|
||||
title: $.i18n.prop('public.tips'),
|
||||
btn : [ $.i18n.prop('public.ok'), $.i18n.prop('public.cancel') ]
|
||||
}, function(i) {
|
||||
layer.close(i);
|
||||
clientList[clientIndex].proxyMappings.splice(mappingIndex, 1);
|
||||
api_invoke("/config/update", clientList, function(data) {
|
||||
if (data.code != 20000) {
|
||||
layer.alert(data.message);
|
||||
} else {
|
||||
load_page("html/lan/list.html");
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
$(".back").click(function() {
|
||||
load_page("html/client/list.html");
|
||||
});
|
||||
|
||||
$(".mapping-add").click(function() {
|
||||
load_page("html/lan/add.html");
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,51 @@
|
||||
<div class="layui-tab layui-tab-brief">
|
||||
<ul class="layui-tab-title site-demo-title">
|
||||
<li class="layui-this tab-title"></li>
|
||||
</ul>
|
||||
<div class="main-content"></div>
|
||||
</div>
|
||||
<script id="stat-tpl" type="text/html">
|
||||
<table class="layui-table" lay-skin="line">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="i18n-inetport"></th>
|
||||
<th class="i18n-inflow"></th>
|
||||
<th class="i18n-outflow"></th>
|
||||
<th class="i18n-channels"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%for(var i = 0; i < data.length; i++) {%>
|
||||
<tr>
|
||||
<td><%:=data[i].port%></td>
|
||||
<td><%:=bytesToSize(data[i].readBytes)%></td>
|
||||
<td><%:=bytesToSize(data[i].wroteBytes)%></td>
|
||||
<td><%:=data[i].channels%></td>
|
||||
</tr>
|
||||
<%}%>
|
||||
</tbody>
|
||||
</table>
|
||||
</script>
|
||||
<script>
|
||||
$(".tab-title").html($.i18n.prop('menu.client.statistics'));
|
||||
function bytesToSize(bytes) {
|
||||
if (bytes === 0)
|
||||
return '0 B';
|
||||
var k = 1000, // or 1024
|
||||
sizes = [ 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' ], i = Math
|
||||
.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
api_invoke("/metrics/get", {}, function(data) {
|
||||
var html = template($("#stat-tpl").html(), {
|
||||
data : data.data
|
||||
});
|
||||
$(".main-content").html(html);
|
||||
$(".i18n-inetport").html($.i18n.prop('statistics.inetport'));
|
||||
$(".i18n-inflow").html($.i18n.prop('statistics.inflow'));
|
||||
$(".i18n-outflow").html($.i18n.prop('statistics.outflow'));
|
||||
$(".i18n-channels").html($.i18n.prop('statistics.channels'));
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,205 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title></title>
|
||||
<link rel="stylesheet" href="/lanproxy-config/layui/css/layui.css">
|
||||
<style type="text/css">
|
||||
.main-content {
|
||||
padding: 15px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="layui-layout layui-layout-admin">
|
||||
<div class="layui-header">
|
||||
<div class="layui-logo">言逍网络科技 配置控制台</div>
|
||||
<ul class="layui-nav layui-layout-right">
|
||||
<li class="layui-nav-item"><a href="javascript:;"
|
||||
class="logout"></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="layui-side layui-bg-black">
|
||||
<div class="layui-side-scroll"></div>
|
||||
</div>
|
||||
|
||||
<div class="layui-body"></div>
|
||||
|
||||
<div class="layui-footer"></div>
|
||||
</div>
|
||||
<script src="/lanproxy-config/layui/layui.js"></script>
|
||||
<script src="/jquery/jquery-3.1.1.min.js"></script>
|
||||
<script src="/jquery/jquery.i18n.properties.min.js" type="text/javascript"></script>
|
||||
<script src="/template/template.js"></script>
|
||||
<script>
|
||||
window.clientList = [];
|
||||
|
||||
function api_invoke(uri, params, callback) {
|
||||
var index = layer.load(1, {
|
||||
shade : [ 0.1, '#fff' ]
|
||||
});
|
||||
$.ajax({
|
||||
url : uri,
|
||||
data : JSON.stringify(params),
|
||||
type : 'POST',
|
||||
cache : false,
|
||||
dataType : 'json',
|
||||
contentType : "application/json; charset=utf-8",
|
||||
success : function(data) {
|
||||
layer.close(index);
|
||||
callback(data);
|
||||
},
|
||||
error : function(XMLHttpRequest, textStatus, errorThrown) {
|
||||
layer.close(index);
|
||||
if ("undefined" == typeof (XMLHttpRequest.responseJSON)) {
|
||||
layer.alert("System error, please try again later.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (40100 == XMLHttpRequest.responseJSON.code) {
|
||||
location.href = "/";
|
||||
} else {
|
||||
layer.alert(XMLHttpRequest.responseJSON.message);
|
||||
api_invoke("/config/detail", {}, function(data) {
|
||||
clientList = data.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function load_page(pageUrl) {
|
||||
$(".layui-body").load(pageUrl);
|
||||
}
|
||||
|
||||
function check_lan(ip) {
|
||||
var re = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5]):([0-9]|[1-9]\d{1,3}|[1-5]\d{4}|6[0-5]{2}[0-3][0-5])$/
|
||||
return re.test(ip);
|
||||
}
|
||||
|
||||
function check_port(port) {
|
||||
if (port > 65535) {
|
||||
return false;
|
||||
}
|
||||
var re = /^[1-9]+[0-9]*]*$/
|
||||
return re.test(port);
|
||||
}
|
||||
|
||||
$(function() {
|
||||
if (window.history && window.history.pushState) {
|
||||
$(window).on('popstate', function() {
|
||||
window.history.pushState('forward', null, '#');
|
||||
window.history.forward(1);
|
||||
});
|
||||
}
|
||||
if ('pushState' in history) {
|
||||
window.history.pushState('forward', null, '#');
|
||||
window.history.forward(1);
|
||||
} else {
|
||||
History.pushState('forward', null, '?state=2');
|
||||
window.history.forward(1);
|
||||
}
|
||||
window.onhashchange = function() {
|
||||
History.pushState('forward', null, '?state=1');
|
||||
}
|
||||
|
||||
$(".logout").click(function() {
|
||||
api_invoke("/logout", {}, function(data) {
|
||||
location.href = "/";
|
||||
})
|
||||
});
|
||||
|
||||
});
|
||||
function update_menu() {
|
||||
api_invoke("/config/detail", {}, function(data) {
|
||||
if (data.code == 20000) {
|
||||
clientList = data.data;
|
||||
var html = template($("#menu-tpl").html(), {
|
||||
list : clientList
|
||||
});
|
||||
$(".layui-side-scroll").html(html);
|
||||
layui.use('element', function() {
|
||||
var element = layui.element;
|
||||
});
|
||||
$(".client-list").html($.i18n.prop('menu.client.list'));
|
||||
$(".client-list-sub").html($.i18n.prop('menu.client.list'));
|
||||
$(".client-add").html($.i18n.prop('menu.client.add'));
|
||||
$(".client-config").html($.i18n.prop('menu.client.config'));
|
||||
$(".statistics").html($.i18n.prop('menu.client.statistics'));
|
||||
$(".menu-item").click(function() {
|
||||
window.clientIndex = $(this).attr("data-index");
|
||||
load_page("html/lan/list.html");
|
||||
});
|
||||
} else {
|
||||
alert(data.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var i18nLanguage = "en";
|
||||
var webLanguage = [ 'zh-CN', 'en' ];
|
||||
|
||||
function initWebLanguage() {
|
||||
var navLanguage = navigator.language;
|
||||
if (navLanguage) {
|
||||
var charSize = $.inArray(navLanguage, webLanguage);
|
||||
if (charSize > -1) {
|
||||
i18nLanguage = navLanguage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
initWebLanguage();
|
||||
|
||||
jQuery.i18n.properties({
|
||||
name : 'lang', //资源文件名称
|
||||
path : '/i18n/', //资源文件路径
|
||||
mode : 'map', //用Map的方式使用资源文件中的值
|
||||
language : i18nLanguage,
|
||||
encoding: 'UTF-8',
|
||||
callback : function() {//加载成功后设置显示内容
|
||||
$('title').html($.i18n.prop('title'));
|
||||
$(".layui-footer").html($.i18n.prop('title'));
|
||||
$(".logout").html($.i18n.prop('logout'));
|
||||
layui.use('layer', function() {
|
||||
window.layer = layui.layer;
|
||||
update_menu();
|
||||
load_page("html/client/list.html");
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script id="menu-tpl" type="text/html">
|
||||
<ul class="layui-nav layui-nav-tree">
|
||||
<li class="layui-nav-item layui-nav-itemed"><a class="client-list"
|
||||
href="javascript:;"></a>
|
||||
<dl class="layui-nav-child">
|
||||
<dd class="layui-this">
|
||||
<a href="javascript:load_page('html/client/list.html');" class="client-list-sub"></a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="javascript:load_page('html/client/add.html');" class="client-add"></a>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li class="layui-nav-item layui-nav-itemed">
|
||||
<a class="client-config" href="javascript:;"></a>
|
||||
<dl class="layui-nav-child">
|
||||
<%for(var i = 0; i < list.length; i++) {%>
|
||||
<dd>
|
||||
<a href="javascript:;" class="menu-item" data-index="<%:=i%>"><%:=list[i].name%></a>
|
||||
</dd>
|
||||
<%}%>
|
||||
</dl>
|
||||
</li>
|
||||
<li class="layui-nav-item layui-nav-itemed"><a class="statistics"
|
||||
href="javascript:load_page('html/statistics/list.html');"></a>
|
||||
</li>
|
||||
</ul>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,2 @@
|
||||
/** layui-v2.0.2 MIT License By http://www.layui.com */
|
||||
html #layuicss-skincodecss{display:none;position:absolute;width:1989px}.layui-code-h3,.layui-code-view{position:relative;font-size:12px}.layui-code-view{display:block;margin:10px 0;padding:0;border:1px solid #e2e2e2;border-left-width:6px;background-color:#F2F2F2;color:#333;font-family:Courier New}.layui-code-h3{padding:0 10px;height:32px;line-height:32px;border-bottom:1px solid #e2e2e2}.layui-code-h3 a{position:absolute;right:10px;top:0;color:#999}.layui-code-view .layui-code-ol{position:relative;overflow:auto}.layui-code-view .layui-code-ol li{position:relative;margin-left:45px;line-height:20px;padding:0 5px;border-left:1px solid #e2e2e2;list-style-type:decimal-leading-zero;*list-style-type:decimal;background-color:#fff}.layui-code-view pre{margin:0}.layui-code-notepad{border:1px solid #0C0C0C;border-left-color:#3F3F3F;background-color:#0C0C0C;color:#C2BE9E}.layui-code-notepad .layui-code-h3{border-bottom:none}.layui-code-notepad .layui-code-ol li{background-color:#3F3F3F;border-left:none}
|
||||
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 701 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 200 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 3.1 KiB |