添加stcp,xtcp隧道类型

添加stcp,xtcp隧道类型设置导入、导出功能
添加HTTP Basic Auth设置
This commit is contained in:
octopusYan 2022-04-16 00:47:52 +08:00
parent 4bf2a8f4ce
commit 4b0c793e42
23 changed files with 1169 additions and 365 deletions

View File

@ -1,12 +1,23 @@
# Yan Frp
基于javafx开发的 frp 客户端, 后端使用的是开源的 frp管理面板 [SakuraPanel](https://github.com/ZeroDream-CN/SakuraPanel)
网址https://frp.octopusyan.top
基于javafx开发的 frp 客户端
后台管理面板使用的是开源的 frp 管理面板 [SakuraPanel](https://github.com/ZeroDream-CN/SakuraPanel)
ps:为了方便 YanFrp 我自己修改了些面板代码,如果想自己搭建需要改造
管理面板网址https://frp.octopusyan.top/?page=login
# 说明:
1.
# 后续开发计划
- ~~HTTP Basic Auth~~ (已完成)
- ~~添加p2p连接方式(xtcp,stcp)~~ (已完成)
- HTTP URL路由
- 待添加...
# 界面:
登录

View File

@ -6,7 +6,7 @@
<groupId>top.octopusyan</groupId>
<artifactId>YanFrp</artifactId>
<version>1.0.3-SNAPSHOT</version>
<version>1.1</version>
<name>YanFrp</name>
<properties>
@ -84,7 +84,6 @@
<version>2.11.0</version>
</dependency>
<!-- 算法库 -->
<dependency>
<groupId>cn.hutool</groupId>

View File

@ -86,12 +86,7 @@ public class YanFrpApplication extends Application {
FXMLLoader fxmlLoader = FxmlUtil.init("/fxml/login.fxml");
StackPane root = fxmlLoader.load();//底层面板
stage.initStyle(StageStyle.TRANSPARENT);
Scene scene = new Scene(
root,
root.getPrefWidth() + 20,
root.getPrefHeight() + 20,
Color.TRANSPARENT
);
Scene scene = new Scene(root, root.getPrefWidth() + 20, root.getPrefHeight() + 20, Color.TRANSPARENT);
scene.getStylesheets().addAll(Objects.requireNonNull(getClass().getResource("/css/root.css")).toExternalForm());
stage.setScene(scene);
stage.show();

View File

@ -7,6 +7,7 @@ import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
@ -17,6 +18,7 @@ import top.octopusyan.manager.FrpManager;
import top.octopusyan.model.ApplicatonStore;
import top.octopusyan.utils.FxmlUtil;
import top.octopusyan.utils.Loading;
import top.octopusyan.utils.TooltipUtil;
import java.io.IOException;
import java.net.URL;
@ -36,6 +38,8 @@ public abstract class BaseController<P extends Pane> implements Initializable {
private volatile Loading loading;
protected TooltipUtil tooltipUtil;
public void jumpTo(BaseController<P> controller) throws IOException {
FXMLLoader fxmlLoader = FxmlUtil.init(controller.getRootFxmlPath());
@ -77,11 +81,14 @@ public abstract class BaseController<P extends Pane> implements Initializable {
});
}
// app 版本信息
if (getAppVersionLabel() != null) getAppVersionLabel().setText("version : v" + ApplicatonStore.APP_VERSION);
// 这个位置的左边第一个 JFXBtn 会莫名其妙会的焦点效果启动时禁用焦点取消按钮效果
getFirstBtn().setDisableVisualFocus(true);
if (getFirstBtn() != null) getFirstBtn().setDisableVisualFocus(true);
// 最小化窗口
getMinimizeBtn().setOnMouseClicked(event -> {
if (getMinimizeBtn() != null) getMinimizeBtn().setOnMouseClicked(event -> {
if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 1) {
Stage stage = (Stage) getRootPanel().getScene().getWindow();
stage.setIconified(true);
@ -89,7 +96,7 @@ public abstract class BaseController<P extends Pane> implements Initializable {
});
// 关闭窗口
getClooseBtn().setOnMouseClicked(event -> {
if (getClooseBtn() != null) getClooseBtn().setOnMouseClicked(event -> {
if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 1) {
onDestroy();
}
@ -111,7 +118,7 @@ public abstract class BaseController<P extends Pane> implements Initializable {
public void showLoading(String message) {
if (loading == null) loading = new Loading((Stage) getRootPanel().getScene().getWindow());
if (!StringUtils.isEmpty(message)) loading.showMessage(message);
if (StringUtils.isNotEmpty(message)) loading.showMessage(message);
loading.show();
}
@ -131,6 +138,11 @@ public abstract class BaseController<P extends Pane> implements Initializable {
loading.closeStage();
}
protected TooltipUtil getTooltipUtil() {
if (tooltipUtil == null) tooltipUtil = TooltipUtil.getInstance(getRootPanel());
return tooltipUtil;
}
/**
* 窗口拖拽设置
*
@ -153,6 +165,11 @@ public abstract class BaseController<P extends Pane> implements Initializable {
@NotNull
public abstract String getRootFxmlPath();
/**
* App版本信息标签
*/
public abstract Label getAppVersionLabel();
/**
* 关闭按钮
*/

View File

@ -2,6 +2,7 @@ package top.octopusyan.config;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import top.octopusyan.model.ProxySetupModel;
import java.util.Arrays;
import java.util.HashMap;
@ -29,7 +30,6 @@ public class ProxyConfig {
typePort.put("http", 80);
typePort.put("https", 80);
typePort.put("ssh", 22);
typePort.put("tcp", 0);
typePort.put("udp", 0);
}
@ -57,22 +57,13 @@ public class ProxyConfig {
}
public enum ProxyType {
HTTP("http"),
HTTPS("https"),
SSH("tcp"),
TCP("tcp"),
UDP("udp"),
HTTP,
HTTPS,
TCP,
UDP,
STCP,
XTCP,
;
private final String type;
public String getType() {
return type;
}
ProxyType(String type) {
this.type = type;
}
}
public static String getServerPath(String serverName) {
@ -80,8 +71,8 @@ public class ProxyConfig {
}
public static int getServerNode(String serverName) {
for (ProxyServer server : Arrays.asList(ProxyServer.values())) {
if(server.serverName.equals(serverName)) return server.value;
for (ProxyServer server : ProxyServer.values()) {
if (server.serverName.equals(serverName)) return server.value;
}
return 3;
@ -89,6 +80,7 @@ public class ProxyConfig {
/**
* 获取服务名称
*
* @param node 服务器标签
*/
public static String getServerName(int node) {
@ -97,8 +89,17 @@ public class ProxyConfig {
return proxyServer.getServerName();
}
public static boolean isHttp(ProxySetupModel model) {
return ProxyConfig.getTypeIndex(model.getProxyType()) < 2;
}
public static boolean isP2p(ProxySetupModel model) {
return ProxyConfig.getTypeIndex(model.getProxyType()) > 3;
}
/**
* 获取服务器IP地址
*
* @param serverName 服务器名称
*/
public static String getServerIP(String serverName) {
@ -107,6 +108,7 @@ public class ProxyConfig {
/**
* 获取服务器IP地址
*
* @param node 服务器标签
*/
public static String getServerIP(int node) {
@ -115,10 +117,12 @@ public class ProxyConfig {
/**
* 获取代理类型默认端口
*
* @param type 类型名称
*/
public static Integer getTypePort(String type) {
return typePort.get(type);
Integer port = typePort.get(type);
return port == null ? 0 : port;
}
public static Integer getTypeIndex(String type) {

View File

@ -48,6 +48,14 @@ public class TextValidate {
* 域名为空检查
*/
public static RequiredFieldValidator DomainRequired = new RequiredFieldValidator("域名不能为空!");
/**
* 访问密码为空检查
*/
public static RequiredFieldValidator p2pPwdRequired = new RequiredFieldValidator("访问密码不能为空!");
/**
* 访问服务名为空检查
*/
public static RequiredFieldValidator p2pServerNameRequired = new RequiredFieldValidator("访问服务名称不能为空!");
/**
* 端口为空检查
*/
@ -60,6 +68,18 @@ public class TextValidate {
* IP为空检查
*/
public static RequiredFieldValidator IpRequired = new RequiredFieldValidator("本地IP不能为空");
/**
* http访问用户名格式检查
*/
public static final RegexValidator HttpUserFormat = new RegexValidator("用户名格式错误");
/**
* http访问密码格式检查
*/
public static final RegexValidator HttpPwdFormat = new RegexValidator("密码格式错误");
/**
* http访问密码格式检查
*/
public static final RegexValidator P2pPwdFormat = new RegexValidator("密码格式错误");
/**
* IP格式检查
*/
@ -69,7 +89,11 @@ public class TextValidate {
EmailFormat.setRegexPattern("^\\s*\\w+(?:\\.{0,1}[\\w-]+)*@[a-zA-Z0-9]+(?:[-.][a-zA-Z0-9]+)*\\.[a-zA-Z]+\\s*$");
AccoountValidator.setRegexPattern("^[a-zA-Z0-9_-]*$");
PortFormat.setRegexPattern("^([0-9]|[1-9]\\d{1,3}|[1-5]\\d{4}|6[0-4]\\d{4}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])$");
IpFormat.setRegexPattern("^(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])$");
IpFormat.setRegexPattern("^(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\" +
".(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])$");
HttpUserFormat.setRegexPattern("^[a-zA-Z0-9_-]*$");
HttpPwdFormat.setRegexPattern("^[a-zA-Z0-9_-]*$");
P2pPwdFormat.setRegexPattern("^[a-zA-Z0-9_-]{3,18}$");
}
/**
@ -103,7 +127,7 @@ public class TextValidate {
@Override
protected void eval() {
if (!DomainUtil.isCustomize(model.get())) {
if (DomainUtil.isHttp(model)) {
if (ProxyConfig.isHttp(model)) {
// http / https
boolean matches = Pattern.compile("^[a-zA-Z0-9_-]{3,18}$").matcher(model.getDomain()).matches();
hasErrors.set(!matches);
@ -129,7 +153,7 @@ public class TextValidate {
@Override
protected void eval() {
setRegexPattern("^[a-zA-Z0-9_-]{3,18}$");
if (DomainUtil.isHttp(model) && !DomainUtil.isCustomize(model.get())) {
if (ProxyConfig.isHttp(model) && !DomainUtil.isCustomize(model.get())) {
super.eval();
} else {
hasErrors.set(false);

View File

@ -9,6 +9,8 @@ import javafx.beans.property.SimpleStringProperty;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextInputDialog;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.AnchorPane;
@ -76,8 +78,10 @@ public class LoginController extends BaseController<StackPane> implements Initia
public JFXCheckBox rememberCBox;
@FXML
public JFXButton findpassBtn;
@FXML
public Label appVersionLabel;
private SimpleStringProperty tmppwd = new SimpleStringProperty();
private final SimpleStringProperty tmpPwd = new SimpleStringProperty();
@Override
public boolean dragWindow() {
@ -95,6 +99,11 @@ public class LoginController extends BaseController<StackPane> implements Initia
return "/fxml/login.fxml";
}
@Override
public Label getAppVersionLabel() {
return appVersionLabel;
}
@Override
public Button getClooseBtn() {
return closeBtn;
@ -116,22 +125,22 @@ public class LoginController extends BaseController<StackPane> implements Initia
// 账号
accountTextField.textProperty().bindBidirectional(ApplicatonStore.accountProperty());
// 密码
if (ApplicatonStore.isRememberMe()) tmppwd.set(ApplicatonStore.getPassword());
if (ApplicatonStore.isRememberMe()) tmpPwd.set(ApplicatonStore.getPassword());
// 自动登录
autoLoginCBox.selectedProperty().bindBidirectional(ApplicatonStore.autoLoginProperty());
// 记住我
rememberCBox.selectedProperty().bindBidirectional(ApplicatonStore.rememberMeProperty());
passwordTextField.textProperty().bindBidirectional(tmppwd);
seePwdTextField.textProperty().bindBidirectional(tmppwd);
passwordTextField.textProperty().bindBidirectional(tmpPwd);
seePwdTextField.textProperty().bindBidirectional(tmpPwd);
}
// 隐藏展示密码框
private AnchorPane pwdParent;
private AlertUtil.Builder findpassAlert;
private AlertUtil.Builder<TextInputDialog> findpassAlert;
@Override
public void initViewStyle() {
@ -169,7 +178,7 @@ public class LoginController extends BaseController<StackPane> implements Initia
// 注册
registerBtn.setOnMouseClicked(event -> {
ApplicatonStore.setPassword(tmppwd.get());
ApplicatonStore.setPassword(tmpPwd.get());
try {
jumpTo(new RegisterController());
} catch (IOException e) {
@ -183,7 +192,7 @@ public class LoginController extends BaseController<StackPane> implements Initia
pwdParent.getChildren().remove(isHide ? passwordTextField : seePwdTextField);
pwdParent.getChildren().add(1, isHide ? seePwdTextField : passwordTextField);
(isHide ? seePwdTextField : passwordTextField).setText(tmppwd.get());
(isHide ? seePwdTextField : passwordTextField).setText(tmpPwd.get());
seePwdIcon.setIconColor(isHide ? Color.BLUE : Color.BLACK);
});
@ -206,8 +215,8 @@ public class LoginController extends BaseController<StackPane> implements Initia
// 自动登录
if ((ApplicatonStore.isAutoLogin() || ApplicatonStore.isRegisterSuccess()) &&
!StringUtils.isEmpty(ApplicatonStore.getAccount()) &&
!StringUtils.isEmpty(ApplicatonStore.getPassword())
StringUtils.isNotEmpty(ApplicatonStore.getAccount()) &&
StringUtils.isNotEmpty(ApplicatonStore.getPassword())
) {
login();
}
@ -250,7 +259,7 @@ public class LoginController extends BaseController<StackPane> implements Initia
if (pwdValidate && accountValidate)
EasyHttp.builder()
.api(Api.Login)
.param(new LoginParam(accountTextField.getText(), tmppwd.get()))
.param(new LoginParam(accountTextField.getText(), tmpPwd.get()))
.request(new OnHttpListener<String>() {
@Override
public void onSucceed(String result) {
@ -262,7 +271,7 @@ public class LoginController extends BaseController<StackPane> implements Initia
// 登录成功
setCsrf();
// 记住我
ApplicatonStore.rememberMe(tmppwd.get());
ApplicatonStore.rememberMe(tmpPwd.get());
// 跳转
Platform.runLater(() -> {
try {

View File

@ -1,18 +1,25 @@
package top.octopusyan.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.jfoenix.controls.*;
import com.jfoenix.validation.base.ValidatorBase;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.*;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
@ -27,12 +34,8 @@ import top.octopusyan.manager.ProxyManager;
import top.octopusyan.manager.http.request.ProxySetup;
import top.octopusyan.model.ApplicatonStore;
import top.octopusyan.model.ProxySetupModel;
import top.octopusyan.utils.AlertUtil;
import top.octopusyan.utils.DomainUtil;
import top.octopusyan.utils.*;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.io.IOException;
import java.util.List;
import java.util.*;
@ -53,50 +56,57 @@ public class MainController extends BaseController<StackPane> implements Initial
public static final String PROXY_LIST_ITEM_CLOSE_CLASS = "proxyListItem-close";
public static final String PROXY_LIST_ITEM_SELECT_CLASS = "proxyListItem-select";
@FXML
/* 窗口通用控件 */
public StackPane root;
public JFXButton minimizeBtn, closeBtn;
public Label appVersionLabel;
@FXML
public JFXButton closeBtn, minimizeBtn;
@FXML
public JFXButton startProxyBtn, addProxyBtn;
@FXML
public JFXComboBox<String> proxyProtocolComboBox;
@FXML
public JFXButton customizeDomainBtn;
@FXML
public JFXRadioButton openProxyRBtn, closeProxyRBtn;
@FXML
public JFXTextField domainTextField, domainSuffixTextField;
@FXML
public JFXTextField proxyNameTextField, localHostTextField, localPortTextField;
@FXML
/* 隧道设置控件 */
public JFXComboBox<String> proxyServerComboBox;
@FXML
public JFXTabPane tabPane;
@FXML
public JFXListView<Label> proxyListView;
@FXML
public Label domainHtinTextField;
@FXML
public JFXButton resetProxyBtn;
public JFXTextField proxyNameTextField;
public JFXRadioButton openProxyRBtn, closeProxyRBtn;
public JFXComboBox<String> proxyProtocolComboBox;
public JFXRadioButton serverProviderRBtn, serverVisitorRBtn;
public Label localHostLabel;
public JFXTextField localHostTextField, localPortTextField;
public HBox domainPane;
@FXML
public JFXButton resetProxyBtn, clearLogBtn, copyDomainBtn;
@FXML
public Tab proxyLogPane;
@FXML
public Hyperlink domainLink;
@FXML
public JFXTextField domainTextField, domainSuffixTextField;
public JFXButton customizeDomainBtn;
public HBox httpUserPane;
public JFXTextField httpUserTextField;
public HBox httpPwdPane;
public JFXTextField httpPwdTextField;
public HBox p2pRolePane, p2pPwdPane, p2pRoleView, serverNamePane;
public JFXButton copyP2pConfig, importP2pConfig;
public JFXTextField p2pPwdTextField, serverNameTextField;
public JFXButton proxyStatusTip, httpUserTip, proxyTypeTip, p2pPwdTip, serverNameTip;
public HBox proxyStatusPane;
@FXML
public JFXButton logoutBtn;
public Label runingLabel;
public Hyperlink domainLink;
public JFXButton copyDomainBtn;
private final ToggleGroup openProxyGroup = new ToggleGroup();
private final SimpleBooleanProperty customizeDomain = new SimpleBooleanProperty(false);
private final Map<String, FrpManager> frpUtilMap = new HashMap<>();
/* 隧道列表控件 */
public JFXListView<Label> proxyListView;
public JFXButton addProxyBtn;
/* 日志帮助面板控件 */
public JFXTabPane tabPane;
public Tab proxyLogPane;
public JFXButton clearLogBtn;
/* 功能控件 */
public JFXButton startProxyBtn, logoutBtn;
/* 自定义属性 */
private ObservableList<Node> children;
private final ProxySetupModel proxySetupModel = new ProxySetupModel(ProxyManager.initProxy(null));
private final BooleanProperty customizeDomain = new SimpleBooleanProperty(false);
private final Map<String, ProxySetup> userProxy = new HashMap<>();
private ProxySetupModel proxySetupModel;
private final Map<String, FrpManager> frpUtilMap = new HashMap<>();
private final ToggleGroup openProxyGroup = new ToggleGroup();
private final ToggleGroup serverRoleGroup = new ToggleGroup();
private final SimpleBooleanProperty setup = new SimpleBooleanProperty(false);
@Override
@ -115,6 +125,11 @@ public class MainController extends BaseController<StackPane> implements Initial
return "/fxml/main.fxml";
}
@Override
public Label getAppVersionLabel() {
return appVersionLabel;
}
@Override
public Button getClooseBtn() {
return closeBtn;
@ -132,18 +147,15 @@ public class MainController extends BaseController<StackPane> implements Initial
@Override
public void initData() {
// 初始化视图模型
ProxySetup proxySetup = ProxyManager.initProxy(null);
proxySetupModel = new ProxySetupModel(proxySetup);
// 初始化用户隧道列表
setProxyList(Collections.singletonList(proxySetup));
setProxyList(Collections.singletonList(proxySetupModel.get()));
// 重置隧道列表视图
initProxyListView();
// 隧道类型
// 初始化隧道类型
Arrays.asList(ProxyType.values()).forEach((type) -> proxyProtocolComboBox.getItems().add(type.name()));
// 服务器
// 初始化服务器
Arrays.asList(ProxyServer.values()).forEach((server) -> proxyServerComboBox.getItems().add(server.getServerName()));
// 获取用户隧道列表
@ -185,7 +197,6 @@ public class MainController extends BaseController<StackPane> implements Initial
}
});
/* 数据绑定 */
// 服务器
@ -204,12 +215,27 @@ public class MainController extends BaseController<StackPane> implements Initial
domainSuffixTextField.textProperty().bindBidirectional(proxySetupModel.domainSuffixProperty());
// 是否自定义访问域名
customizeDomain.bindBidirectional(proxySetupModel.isCustomizeProperty());
// http访问用户名
httpUserTextField.textProperty().bindBidirectional(proxySetupModel.httpUserProperty());
// http访问密码
httpPwdTextField.textProperty().bindBidirectional(proxySetupModel.httpPwdProperty());
// p2p访问密码
p2pPwdTextField.textProperty().bindBidirectional(proxySetupModel.skProperty());
// p2p访问服务提示
serverNameTextField.textProperty().bindBidirectional(proxySetupModel.serverNameProperty());
// 启动成功提示
proxyStatusPane.visibleProperty().bindBidirectional(proxySetupModel.runningProperty());
}
@Override
public void initViewStyle() {
children = ((Pane) domainPane.getParent()).getChildren();
// 默认隧道协议为http
children.remove(p2pRolePane);
children.remove(p2pPwdPane);
children.remove(serverNamePane);
// 设置列表
proxyListView.getSelectionModel().select(0);
// 启用链接
@ -219,30 +245,144 @@ public class MainController extends BaseController<StackPane> implements Initial
closeProxyRBtn.setUserData(false);
openProxyRBtn.setSelected(true);
// 点对点服务角色
serverProviderRBtn.setToggleGroup(serverRoleGroup);
serverVisitorRBtn.setToggleGroup(serverRoleGroup);
serverProviderRBtn.setUserData(true);
serverVisitorRBtn.setUserData(false);
serverProviderRBtn.setSelected(true);
// 是否启用
proxySetupModel.statusProperty().addListener((observable, oldValue, newValue) -> {
openProxyRBtn.setSelected(newValue);
closeProxyRBtn.setSelected(!newValue);
closeProxy(!newValue);
});
// 隧道类型监听
proxySetupModel.proxyTypeProperty().addListener((observable, oldValue, newValue) -> {
String newType = StringUtils.lowerCase(newValue);
String port, host;
if (!newType.equals(userProxy.get(proxySetupModel.getId()))) {
if (!newType.equals(StringUtils.upperCase(userProxy.get(proxySetupModel.getId()).getProxy_type()))) {
host = "127.0.0.1";
port = ProxyConfig.getTypePort(newType).toString();
} else {
host = proxySetupModel.getLocalIp();
port = proxySetupModel.getLocalPort();
}
// 域名提示
domainPane.setVisible(DomainUtil.isHttp(proxySetupModel));
// 隧道类型相关控件显示
boolean isHttp = ProxyConfig.isHttp(proxySetupModel);
boolean isP2p = ProxyConfig.isP2p(proxySetupModel);
if (isHttp) {
// http / https
children.remove(p2pRolePane);
children.remove(p2pPwdPane);
children.remove(serverNamePane);
if (!children.contains(domainPane)) {
int index = children.size() - 1;
children.add(index, httpPwdPane);
children.add(index, httpUserPane);
children.add(index, domainPane);
}
// 添加验证
ObservableList<ValidatorBase> httpValid = domainTextField.getValidators();
if (!httpValid.contains(TextValidate.DomainRequired)) {
httpValid.add(TextValidate.DomainRequired);// 域名为空检查
httpValid.add(TextValidate.domainFormatValidator(proxySetupModel));// 域名格式检查
httpValid.add(TextValidate.domainLengthValidator(proxySetupModel));// 域名长度检查
httpValid.add(TextValidate.domainAddressValidator(proxySetupModel));// 自定义域名解析检查
}
p2pPwdTextField.getValidators().clear();
// 默认系统分配域名
ProxySetup proxySetup = proxySetupModel.get();
if (proxySetup.getDomain().equals("")) {
proxySetup.setDomain("." + ProxyConfig.getServerPath(proxySetupModel.getServer()));
proxySetupModel.set(proxySetup);
}
} else if (isP2p) {
// stcp / xtcp
children.remove(httpPwdPane);
children.remove(httpUserPane);
children.remove(domainPane);
int index = children.size() - 1;
if (!children.contains(p2pRolePane)) {
children.add(index, p2pPwdPane);
children.add(index - 1, p2pRolePane);
if(proxySetupModel.isProvider != null && !proxySetupModel.isProvider){
children.add(index, serverNamePane);
}
}
Boolean provider = proxySetupModel.isProvider();
importP2pConfig.setVisible(provider != null && !provider);
copyP2pConfig.setVisible(provider == null || provider);
// 添加验证
ObservableList<ValidatorBase> p2pValid = p2pPwdTextField.getValidators();
if (!p2pValid.contains(TextValidate.p2pPwdRequired)) {
p2pValid.add(TextValidate.p2pPwdRequired);
p2pValid.add(TextValidate.p2pServerNameRequired);
}
// 设置
domainTextField.getValidators().clear();
} else {
// tcp / udp
children.remove(p2pRolePane);
children.remove(p2pPwdPane);
children.remove(serverNamePane);
children.remove(httpPwdPane);
children.remove(httpUserPane);
children.remove(domainPane);
// 验证
p2pPwdTextField.getValidators().clear();
domainTextField.getValidators().clear();
}
// 设置本地端口
localHostTextField.setText(host);
localPortTextField.setText(port);
});
// 服务角色监听
proxySetupModel.serverNameProperty().addListener((observable, oldValue, newValue) -> {
if (newValue == null) {
serverProviderRBtn.setSelected(false);
serverVisitorRBtn.setSelected(false);
proxySetupModel.isProvider = null;
} else if (oldValue == null) {
if (proxySetupModel.isProvider == null) {
proxySetupModel.isProvider = proxySetupModel.isProvider();
}
serverProviderRBtn.setSelected(proxySetupModel.isProvider);
serverVisitorRBtn.setSelected(!proxySetupModel.isProvider);
}
});
serverRoleGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) -> {
if (newValue == null) return;
proxySetupModel.isProvider = (boolean) newValue.getUserData();
if (proxySetupModel.isProvider) {
localHostLabel.setText("内网服务地址");
localHostTextField.setPromptText("内网服务IP (IPv4)");
localPortTextField.setPromptText("端口");
copyP2pConfig.setVisible(true);
importP2pConfig.setVisible(false);
children.remove(serverNamePane);
proxySetupModel.setServerName("提供者");
} else {
localHostLabel.setText("服务绑定地址");
localHostTextField.setPromptText("服务绑定IP (IPv4)");
localPortTextField.setPromptText("端口");
copyP2pConfig.setVisible(false);
importP2pConfig.setVisible(true);
if (!children.contains(serverNamePane)) children.add(children.size() - 3, serverNamePane);
proxySetupModel.setServerName("");
}
ObservableList<Node> children = p2pRolePane.getChildren();
JFXButton remove = proxySetupModel.isProvider ? importP2pConfig : copyP2pConfig;
JFXButton add = proxySetupModel.isProvider ? copyP2pConfig : importP2pConfig;
if (children.contains(add)) children.add(add);
children.remove(remove);
});
// 隧道名称
proxySetupModel.proxyNameProperty().addListener((observable, oldValue, newValue) -> {
if (proxyListView.getItems().size() > 0)
@ -267,38 +407,20 @@ public class MainController extends BaseController<StackPane> implements Initial
setDomainLink();
});
// 自定义外网访问地址按钮
// 外网访问地址
customizeDomain.addListener((observable, oldValue, newValue) -> {
AnchorPane parent = (AnchorPane) domainTextField.getParent();
// 是否切换为自定义域名
if (newValue) {
// 隐藏系统域名
parent.getChildren().remove(domainSuffixTextField);
// 用户域名是否自定义
if (!DomainUtil.isCustomize(proxySetupModel.get())) {
proxySetupModel.setDomain("");
} else {
proxySetupModel.setDomain(proxySetupModel.getDomain());
}
proxySetupModel.setDomainSuffix("");
domainTextField.promptTextProperty().set("自定义域名");
customizeDomainBtn.setText("系统分配");
domainHtinTextField.setText("请输入您的域名,并解析至: " + ProxyConfig.getServerIP(proxySetupModel.getServer()));
} else {
// 显示系统域名
if (!parent.getChildren().contains(domainSuffixTextField))
parent.getChildren().add(domainSuffixTextField);
// 用户域名是否自定义
if (DomainUtil.isCustomize(proxySetupModel.get())) {
proxySetupModel.setDomain("");
proxySetupModel.setDomainSuffix("." + ProxyConfig.getServerPath(proxySetupModel.getServer()));
} else {
proxySetupModel.setDomain(DomainUtil.getCustomize(proxySetupModel.get()));
proxySetupModel.setDomainSuffix(DomainUtil.getSuffix(userProxy.get(proxySetupModel.getId())));
}
domainTextField.promptTextProperty().set("自定义子域名 大于3位");
customizeDomainBtn.setText("自定义");
domainHtinTextField.setText("请输入子域名长度不小于3个字符");
}
});
@ -311,10 +433,19 @@ public class MainController extends BaseController<StackPane> implements Initial
*/
private void setDomainLink() {
// 外网访问连接
if (DomainUtil.isHttp(proxySetupModel)) {
if (ProxyConfig.isHttp(proxySetupModel)) {
runingLabel.setText("启动成功!立即访问 ");
domainLink.setVisible(true);
copyDomainBtn.setVisible(true);
// http / https
String prefix = proxySetupModel.getProxyType() + "://";
domainLink.textProperty().set(prefix + proxySetupModel.getDomain() + proxySetupModel.getDomainSuffix());
} else if (ProxyConfig.isP2p(proxySetupModel)) {
// stcp / xtcp
runingLabel.setText("启动成功!");
domainLink.setText("");
domainLink.setVisible(false);
copyDomainBtn.setVisible(false);
} else {
// ssh / tcp
domainLink.textProperty().set(ProxyConfig.getServerIP(ProxyConfig.getServerNode(proxySetupModel.getServer())) + ":" + proxySetupModel.getRemotePort());
@ -326,7 +457,7 @@ public class MainController extends BaseController<StackPane> implements Initial
// 重置
resetProxyBtn.setOnMouseClicked(event -> {
ProxySetup proxySetup = userProxy.get(proxySetupModel.getId());
if(proxySetup != null){
if (proxySetup != null) {
proxySetup.setRuning(proxySetupModel.isRunning());
proxySetupModel.set(proxySetup);
domainTextField.resetValidation();
@ -336,20 +467,22 @@ public class MainController extends BaseController<StackPane> implements Initial
// 日志清理
clearLogBtn.setOnMouseClicked(event -> {
ProxySetup proxySetup = userProxy.get(proxySetupModel.getId());
if (frpUtilMap.get(proxySetup.getId()) != null)
frpUtilMap.get(proxySetup.getId()).clearLog();
if (frpUtilMap.get(proxySetup.getId().toString()) != null)
frpUtilMap.get(proxySetup.getId().toString()).clearLog();
});
// 更换服务器地址
proxyServerComboBox.valueProperty().addListener((observable, oldValue, newValue) -> {
// 重新设置是否自定义
proxySetupModel.set(proxySetupModel.get());
if (!customizeDomain.get()) {
boolean customize = DomainUtil.isCustomize(proxySetupModel.get());
if (!DomainUtil.isCustomize(proxySetupModel.get(), oldValue) || !customize) {
proxySetupModel.setDomainSuffix("." + ProxyConfig.getServerPath(newValue));
customizeDomain.set(false);
} else {
proxySetupModel.setDomainSuffix("");
// 域名检查
domainTextField.validate();
customizeDomain.set(true);
}
});
@ -365,7 +498,28 @@ public class MainController extends BaseController<StackPane> implements Initial
);
// 自定义访问域名
customizeDomainBtn.setOnMouseClicked((event) -> customizeDomain.set(!customizeDomain.get()));
customizeDomainBtn.setOnMouseClicked((event) -> {
// 是否切换为自定义域名
if (!customizeDomain.get()) {
// 用户域名是否自定义
if (!DomainUtil.isCustomize(proxySetupModel.get())) {
proxySetupModel.setDomain("");
} else {
proxySetupModel.setDomain(proxySetupModel.getDomain());
}
proxySetupModel.setDomainSuffix("");
} else {
// 用户域名是否自定义
if (DomainUtil.isCustomize(proxySetupModel.get())) {
proxySetupModel.setDomain("");
proxySetupModel.setDomainSuffix("." + ProxyConfig.getServerPath(proxySetupModel.getServer()));
} else {
proxySetupModel.setDomain(DomainUtil.getCustomize(proxySetupModel.get()));
proxySetupModel.setDomainSuffix(DomainUtil.getSuffix(userProxy.get(proxySetupModel.getId())));
}
}
customizeDomain.set(!customizeDomain.get());
});
// 本地IP检查
localHostTextField.getValidators().add(TextValidate.IpRequired);
@ -380,15 +534,60 @@ public class MainController extends BaseController<StackPane> implements Initial
// 域名检查
setup.addListener((observable, oldValue, newValue) -> {
if (newValue) domainTextField.validate();
if (newValue) p2pPwdTextField.validate();
});
domainTextField.textProperty().addListener((observable, oldValue, newValue) -> {
if (!domainTextField.getValidators().contains(TextValidate.DomainRequired)) {
domainTextField.getValidators().add(TextValidate.DomainRequired);// 域名为空检查
domainTextField.getValidators().add(TextValidate.domainFormatValidator(proxySetupModel));// 域名格式检查
domainTextField.getValidators().add(TextValidate.domainLengthValidator(proxySetupModel));// 域名长度检查
domainTextField.getValidators().add(TextValidate.domainAddressValidator(proxySetupModel));// 自定义域名解析检查
// 复制服务设置
copyP2pConfig.setOnMouseClicked(event -> {
if (!localPortTextField.validate() || !localPortTextField.validate() || !p2pPwdTextField.validate())
return;
ProxySetup setup = proxySetupModel.get();
setup.setServer_name(EncryptionUtil.MD5_16(ApplicatonStore.getAccount() + "_" + setup.getSort()));
JSONObject json = JSON.parseObject(JSON.toJSONString(setup));
try {
String serverConfig = EncryptionUtil.DESencode(json.toJSONString(), EncryptionUtil.getDefaultPassword());
// 设置剪切板
ClipUtil.setClip(FrpManager.serverConfigHeader + serverConfig);
AlertUtil.info("复制成功").show();
} catch (Exception e) {
e.printStackTrace();
}
});
// 导入设置
importP2pConfig.setOnMouseClicked(event -> {
try {
String clipData = ClipUtil.getString();
// 服务设置检验
if (clipData == null || !clipData.contains(FrpManager.serverConfigHeader)) throw new Exception();
// 解密服务设置
clipData = clipData.replace(FrpManager.serverConfigHeader, "");
String jsonStr = EncryptionUtil.DESdecode(clipData, EncryptionUtil.getDefaultPassword());
JSONObject jsonObject = JSON.parseObject(jsonStr);
proxySetupModel.set(jsonObject.toJavaObject(ProxySetup.class));
AlertUtil.info("导入成功").show();
} catch (Exception e) {
AlertUtil.error("导入失败,请重新复制服务设置").show();
}
});
// 添加验证
ObservableList<ValidatorBase> httpValid = domainTextField.getValidators();
httpValid.add(TextValidate.DomainRequired);// 域名为空检查
httpValid.add(TextValidate.domainFormatValidator(proxySetupModel));// 域名格式检查
httpValid.add(TextValidate.domainLengthValidator(proxySetupModel));// 域名长度检查
httpValid.add(TextValidate.domainAddressValidator(proxySetupModel));// 自定义域名解析检查
domainTextField.textProperty().addListener((observable, oldValue, newValue) -> domainTextField.validate());
p2pPwdTextField.textProperty().addListener((observable, oldValue, newValue) -> p2pPwdTextField.validate());
// 访问密码检查
p2pPwdTextField.textProperty().addListener((observable, oldValue, newValue) -> {
if (!p2pPwdTextField.getValidators().contains(TextValidate.p2pPwdRequired)) {
p2pPwdTextField.getValidators().add(TextValidate.p2pPwdRequired); // 访问密码为空检查
p2pPwdTextField.getValidators().add(TextValidate.HttpPwdFormat); // 访问密码格式检查
} else
domainTextField.validate();
p2pPwdTextField.validate();
});
// 点击隧道列表
@ -446,12 +645,8 @@ public class MainController extends BaseController<StackPane> implements Initial
// 复制外网访问地址
copyDomainBtn.setOnMouseClicked(event -> {
//获取系统剪切板
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
//构建String数据类型
StringSelection selection = new StringSelection(domainLink.getText());
//添加文本到系统剪切板
clipboard.setContents(selection, null);
// 设置剪切板
ClipUtil.setClip(domainLink.getText());
// 复制成功提示
AlertUtil.info("复制成功,快去分享给小伙伴吧!").show();
});
@ -468,15 +663,37 @@ public class MainController extends BaseController<StackPane> implements Initial
e.printStackTrace();
}
});
// 隧道类型提示
proxyTypeTip.setOnMouseClicked(event -> {
TooltipUtil util = getTooltipUtil();
if (util.isShowing()) util.hide();
else util.showProxyTypeTip(event);
});
// http访问用户名提示
httpUserTip.setOnMouseClicked(event -> {
TooltipUtil util = getTooltipUtil();
if (util.isShowing()) util.hide();
else util.showHttpUserTip(event);
});
// 隧道启用提示
proxyStatusTip.setOnMouseClicked(event -> {
TooltipUtil util = getTooltipUtil();
if (util.isShowing()) util.hide();
else util.showProxyStatusTip(event);
});
// p2p 访问密码提示
p2pPwdTip.setOnMouseClicked(event -> {
TooltipUtil util = getTooltipUtil();
if (util.isShowing()) util.hide();
else util.showP2pPwdTip(event);
});
// p2p 访问服务提示
serverNameTip.setOnMouseClicked(event -> {
TooltipUtil util = getTooltipUtil();
if (util.isShowing()) util.hide();
else util.showP2pServerName(event);
});
}
//
// private void initEmptyProxyListView() {
// // 初始化视图模型
// proxySetup = ProxyManager.initProxy(null);
// proxySetupModel = new ProxySetupModel(proxySetup);
// setProxyList(Collections.singletonList(proxySetup));
// initProxyListView();
// }
/**
* 是否启用当前隧道
@ -484,12 +701,16 @@ public class MainController extends BaseController<StackPane> implements Initial
private void closeProxy(boolean close) {
if (close && proxySetupModel.isRunning()) stopProxy();
proxyNameTextField.setDisable(close);// 隧道名称
proxyNameTextField.setDisable(close); // 隧道名称
proxyProtocolComboBox.setDisable(close);// 隧道类型
localHostTextField.setDisable(close);// 本地地址
localPortTextField.setDisable(close);// 本地端口
domainTextField.setDisable(close); // 外网子域名
startProxyBtn.setDisable(close); // 启动按钮
localHostTextField.setDisable(close); // 本地地址
localPortTextField.setDisable(close); // 本地端口
domainTextField.setDisable(close); // 外网子域名
httpUserTextField.setDisable(close); // 外网子域名
httpPwdTextField.setDisable(close); // 外网子域名
startProxyBtn.setDisable(close); // 启动按钮
p2pRoleView.setDisable(close); // p2p角色
p2pPwdTextField.setDisable(close); // p2p访问面板
ObservableList<String> styleClass = proxyListView.getItems().get(selectProxy()).getStyleClass();
styleClass.remove(PROXY_LIST_ITEM_STOP_CLASS);
@ -518,6 +739,7 @@ public class MainController extends BaseController<StackPane> implements Initial
label.textProperty().set(setup.getProxy_name());
ObservableList<String> styleClass = label.getStyleClass();
styleClass.addAll(PROXY_LIST_ITEM_CLASS, setup.isRuning() ? PROXY_LIST_ITEM_RUN_CLASS : PROXY_LIST_ITEM_STOP_CLASS);
if (selectProxy() == index) styleClass.add(PROXY_LIST_ITEM_SELECT_CLASS);
if (index < items.size()) {
items.set(index, label);
} else {
@ -532,16 +754,32 @@ public class MainController extends BaseController<StackPane> implements Initial
* 启动代理
*/
private void startProxy() {
// http访问用户名密码输入验证
if (httpUserPane.isVisible() && StringUtils.isNotEmpty(httpUserTextField.getText())) {
httpUserTextField.getValidators().add(TextValidate.HttpUserFormat);
httpPwdTextField.getValidators().add(TextValidate.HttpPwdFormat);
} else {
httpUserTextField.getValidators().remove(TextValidate.HttpUserFormat);
httpPwdTextField.getValidators().remove(TextValidate.HttpPwdFormat);
}
// 验证
if (!domainTextField.validate() || !localHostTextField.validate() || !localPortTextField.validate())
return;
if (!domainTextField.validate() || !localHostTextField.validate() || !localPortTextField.validate() ||
!httpUserTextField.validate() || !httpPwdTextField.validate()
) return;
// http 隧道系统随机分配端口
if (!DomainUtil.isHttp(proxySetupModel)) {
proxySetupModel.setRemotePort(String.valueOf(ProxyManager.randomPort()));
// http 隧道系统随机分配远程端口
if (!ProxyConfig.isHttp(proxySetupModel) && !ProxyConfig.isP2p(proxySetupModel)) {
// 远程端口为空
if (StringUtils.isEmpty(proxySetupModel.getRemotePort()))
proxySetupModel.setRemotePort(String.valueOf(ProxyManager.randomPort()));
} else {
proxySetupModel.setRemotePort(null);
}
// 清空非隧道类型属性
clearModel();
// 是否有修改
if (!proxySetupModel.get().equals(userProxy.get(proxySetupModel.getId()))) {
// 添加隧道
@ -564,6 +802,33 @@ public class MainController extends BaseController<StackPane> implements Initial
}
}
/**
* 清空无效属性
*/
private void clearModel() {
boolean http = ProxyConfig.isHttp(proxySetupModel);
boolean p2p = ProxyConfig.isP2p(proxySetupModel);
if (http) {
proxySetupModel.setSk(null);
proxySetupModel.setServerName(null);
proxySetupModel.setRemotePort("");
} else if (p2p) {
proxySetupModel.setHttpPwd(null);
proxySetupModel.setHttpUser(null);
proxySetupModel.setDomain("");
proxySetupModel.setDomainSuffix("");
proxySetupModel.setRemotePort("");
} else {
// tcp / udp
proxySetupModel.setSk(null);
proxySetupModel.setServerName(null);
proxySetupModel.setHttpPwd(null);
proxySetupModel.setHttpUser(null);
proxySetupModel.setDomain("");
proxySetupModel.setDomainSuffix("");
}
}
private void start(ProxySetup setup) {
// 添加成功设置运行状态
setup.setRuning(frpUtilMap.containsKey(setup.getId().toString()));

View File

@ -6,6 +6,7 @@ import com.jfoenix.controls.JFXTextField;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
@ -44,6 +45,8 @@ public class RegisterController extends BaseController<StackPane> implements Ser
public JFXPasswordField passwordTextField;
@FXML
public JFXButton loginBtn;
@FXML
public Label appVersionLabel;
@Override
public boolean dragWindow() {
@ -61,6 +64,11 @@ public class RegisterController extends BaseController<StackPane> implements Ser
return "/fxml/register.fxml";
}
@Override
public Label getAppVersionLabel() {
return appVersionLabel;
}
@Override
public Button getClooseBtn() {
return closeBtn;
@ -81,8 +89,8 @@ public class RegisterController extends BaseController<StackPane> implements Ser
String account = ApplicatonStore.getAccount();
String password = ApplicatonStore.getPassword();
if (!StringUtils.isEmpty(account)) accooundTextField.setText(account);
if (!StringUtils.isEmpty(password)) passwordTextField.setText(password);
if (StringUtils.isNotEmpty(account)) accooundTextField.setText(account);
if (StringUtils.isNotEmpty(password)) passwordTextField.setText(password);
}
@Override

View File

@ -11,6 +11,7 @@ import top.octopusyan.http.model.ResponseClass;
import top.octopusyan.manager.http.Api;
import top.octopusyan.model.ApplicatonStore;
import top.octopusyan.model.ProxySetupModel;
import top.octopusyan.utils.EncryptionUtil;
import top.octopusyan.utils.JsoupUtil;
import java.io.*;
@ -24,11 +25,12 @@ import java.util.*;
* @create : 2022-4-7 23:19
*/
public class FrpManager {
public static final String serverConfigHeader = "yanfrp://";
public static List<FrpManager> frpcList = new ArrayList<>();
private static final String YAN_FRP_TEMP_DIR_NAME = "frpclienttmp";
public static final String FRPC_CLIENT_FILE_NAME = "frpclienttmpfile.exe";
private final String FRPC_CONF_PREFIX_NAME = "proxy_";
private final String FRPC_CONF_SUFFIX_NAME = ".ini";
private static final String FRPC_CONF_PREFIX_NAME = "proxy_";
private static final String FRPC_CONF_SUFFIX_NAME = ".ini";
private static final Map<Integer, String> serverConfigraution = new HashMap<>(3);
/**
* 应用临时目录 地址
@ -154,27 +156,66 @@ public class FrpManager {
public static String getProxyFrpConfig(ProxySetupModel setup) {
String n = "\n";
boolean ishttp = setup.getProxyType().contains("http");
boolean isp2p = "xtcp".equals(setup.getProxyType()) || "stcp".equals(setup.getProxyType());
// 基础配置
StringBuilder stringBuilder = new StringBuilder("[" + ApplicatonStore.getAccount() + "_" + setup.getSort() + "]\n");
stringBuilder.append("privilege_mode = true\n")
.append("type = ").append(setup.getProxyType().contains("http") ? "http" : setup.getProxyType()).append(n)
.append("local_ip = ").append(setup.getLocalIp()).append(n)
.append("local_port = ").append(setup.getLocalPort()).append(n).append(n);
if ("http".equals(setup.getProxyType()) || "https".equals(setup.getProxyType())) {
// HTTP / HTTPS
stringBuilder.append("custom_domains = ").append(setup.getDomain()).append(setup.getDomainSuffix()).append(n);
if (!StringUtils.isEmpty(setup.getLocations()))
stringBuilder.append("locations = ").append(setup.getLocations()).append(n);
if (!StringUtils.isEmpty(setup.getLocations()))
stringBuilder.append("host_header_rewrite = ").append(setup.getHostHeaderRewrite()).append(n);
if (!StringUtils.isEmpty(setup.getLocations()))
stringBuilder.append("header_X-From-Where = ").append(setup.getHeader_X_From_Where()).append(n);
StringBuilder stringBuilder = new StringBuilder("[");
// 服务名称
StringBuilder serverName = new StringBuilder();
// p2p 服务名
if (isp2p) {
if (setup.isProvider()) {
// 提供者
serverName.append(EncryptionUtil.MD5_16(serverName.toString()));
} else {
// 访问者
serverName.append(setup.getServerName()).append("_visitor");
}
} else {
// TCP / UDP / XTCP / STCP
stringBuilder.append("remote_port = ").append(setup.getRemotePort()).append(n);
if (!StringUtils.isEmpty(setup.getSk()))
stringBuilder.append("sk = ").append(setup.getSk()).append(n);
serverName.append(ApplicatonStore.getAccount()).append("_").append(setup.getSort());
}
stringBuilder.append(serverName).append("]\n");
stringBuilder.append("privilege_mode = true\n")
.append("type = ").append(setup.getProxyType().contains("http") ? "http" : setup.getProxyType()).append(n);
if (ishttp) {
// HTTP / HTTPS
stringBuilder.append("custom_domains = ").append(setup.getDomain()).append(setup.getDomainSuffix()).append(n)
.append("local_ip = ").append(setup.getLocalIp()).append(n)
.append("local_port = ").append(setup.getLocalPort()).append(n).append(n);
if (StringUtils.isNotEmpty(setup.getLocations()))
stringBuilder.append("locations = ").append(setup.getLocations()).append(n);
if (StringUtils.isNotEmpty(setup.getHttpUser()))
stringBuilder.append("http_user = ").append(setup.getHttpUser()).append(n);
if (StringUtils.isNotEmpty(setup.getHttpPwd()))
stringBuilder.append("http_pwd = ").append(setup.getHttpPwd()).append(n);
if (StringUtils.isNotEmpty(setup.getHostHeaderRewrite()))
stringBuilder.append("host_header_rewrite = ").append(setup.getHostHeaderRewrite()).append(n);
if (StringUtils.isNotEmpty(setup.getHeader_X_From_Where()))
stringBuilder.append("header_X-From-Where = ").append(setup.getHeader_X_From_Where()).append(n);
} else if (isp2p) {
// xtcp / stcp
if (!setup.isProvider()) {
stringBuilder.append("role = visitor").append(n);
stringBuilder.append("server_name = ").append(setup.getServerName()).append(n);
stringBuilder.append("bind_addr = ").append(setup.getLocalIp()).append(n);
stringBuilder.append("bind_port = ").append(setup.getLocalPort()).append(n);
} else {
stringBuilder.append("local_ip = ").append(setup.getLocalIp()).append(n);
stringBuilder.append("local_port = ").append(setup.getLocalPort()).append(n);
}
stringBuilder.append("sk = ").append(setup.getSk()).append(n);
} else {
// TCP / UDP
stringBuilder.append("local_ip = ").append(setup.getLocalIp()).append(n)
.append("local_port = ").append(setup.getLocalPort()).append(n).append(n)
.append("remote_port = ").append(setup.getRemotePort()).append(n);
}
// 压缩和加密
stringBuilder.append("use_encryption = ").append(setup.isUseEncryption()).append(n)

View File

@ -28,6 +28,10 @@ import java.util.stream.Collectors;
public class ProxyManager {
private static String csrf;
private static final String Null = "";
private static final String isOpen = "启用";
/**
* 初始化隧道设置
*/
@ -48,6 +52,11 @@ public class ProxyManager {
return setup;
}
/**
* 删除隧道
*
* @param id 隧道ID
*/
public static void delete(Integer id) {
EasyHttp.builder()
.api(Api.DeleteProxy())
@ -92,7 +101,11 @@ public class ProxyManager {
String useEnc = select.get(6).text();
String useCom = select.get(7).text();
String domain = select.get(8).text();
String sk = select.get(11).text();
String status = select.get(13).text();
String httpUser = select.get(15).text();
String httpPwd = select.get(16).text();
String serverName = select.get(17).text();
for (ProxyServer value : ProxyServer.values()) {
String name = value.getServerName();
@ -101,11 +114,15 @@ public class ProxyManager {
}
setup.setLocal_ip(localIp);
setup.setLocal_port(Integer.parseInt(localPort));
setup.setRemote_port("".equals(webPort) ? "" : webPort);
setup.setDomain(domain);
setup.setUse_compression(useCom.equals("启用"));
setup.setUse_encryption(useEnc.equals("启用"));
setup.setStatus(status.equals("启用"));
setup.setRemote_port(Null.equals(webPort) ? "" : webPort);
setup.setDomain(Null.equals(domain) ? "" : domain);
setup.setUse_compression(useCom.equals(isOpen));
setup.setUse_encryption(useEnc.equals(isOpen));
setup.setStatus(status.equals(isOpen));
setup.setSk(Null.equals(sk) ? null : sk);
setup.setHttp_user(Null.equals(httpUser) ? null : httpUser);
setup.setHttp_pwd(Null.equals(httpPwd) ? null : httpPwd);
setup.setServer_name(Null.equals(serverName) ? null : serverName);
return setup;
}
@ -166,7 +183,7 @@ public class ProxyManager {
public void onSucceed(String result) {
// 不成功
if(!result.contains("成功")){
if (!result.contains("成功")) {
Platform.runLater(() -> AlertUtil.error(result).header(null).show());
listener.onSucceed(null);
return;

View File

@ -2,6 +2,7 @@ package top.octopusyan.manager.http.request;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
@ -22,6 +23,8 @@ public class ProxySetup {
private Integer local_port;
private String remote_port;
private String domain;
private String http_user;
private String http_pwd;
private Boolean use_encryption;
private Boolean use_compression;
private String locations;
@ -29,9 +32,13 @@ public class ProxySetup {
private String header_X_From_Where;
private String sk;
private Integer sort;
// null:不是p2p, 空字符:提供者
private String server_name;
// 非保留数据
/** 是否启用 */
@EqualsAndHashCode.Exclude
private Boolean status;
@EqualsAndHashCode.Exclude
private boolean runing;
}

View File

@ -24,6 +24,8 @@ import java.util.List;
* @create : 2022-4-4 17:22
*/
public class ApplicatonStore {
public static final String APP_NAME = "YanFrp";
public static final String APP_VERSION = "1.0.4";
public static final String appDataDirPath = System.getProperty("user.home") + File.separator + "AppData" + File.separator + "Local" + File.separator + "yanfrp";
public static final String appDataFilePath = appDataDirPath + File.separator + "yanfrp";
private static final File appDataDir = new File(appDataDirPath);
@ -155,7 +157,8 @@ public class ApplicatonStore {
// 记住
if (appDataJson.containsKey(REMEMBER_ME_KEY)) rememberMe.set(appDataJson.getBoolean(REMEMBER_ME_KEY));
// 已选择隧道
if (appDataJson.containsKey(SELECT_PROXY_NAME_KEY)) selectProxyName = appDataJson.getString(SELECT_PROXY_NAME_KEY);
if (appDataJson.containsKey(SELECT_PROXY_NAME_KEY))
selectProxyName = appDataJson.getString(SELECT_PROXY_NAME_KEY);
}
/**
@ -193,7 +196,7 @@ public class ApplicatonStore {
}
}
public static void logout(){
public static void logout() {
setRegisterSuccess(false);
setAutoLogin(false);
setRememberMe(false);

View File

@ -31,12 +31,16 @@ public class ProxySetupModel {
private final SimpleStringProperty header_X_From_Where = new SimpleStringProperty();
private final SimpleStringProperty sk = new SimpleStringProperty();
private final SimpleBooleanProperty status = new SimpleBooleanProperty();
private final SimpleStringProperty serverName = new SimpleStringProperty();
private final SimpleIntegerProperty sort = new SimpleIntegerProperty();
private final SimpleBooleanProperty running = new SimpleBooleanProperty();
private final SimpleBooleanProperty isCustomize = new SimpleBooleanProperty();
private final SimpleStringProperty httpUser = new SimpleStringProperty();
private final SimpleStringProperty httpPwd = new SimpleStringProperty();
public volatile Boolean isProvider = null;
public ProxySetupModel(ProxySetup setup) {
if(setup == null) return;
if (setup == null) return;
String domainStr = setup.getDomain();
String suffix = "";
@ -48,11 +52,13 @@ public class ProxySetupModel {
setId(setup.getId());
setServer(ProxyConfig.getServerName(setup.getNode()));
setProxyName(setup.getProxy_name());
setProxyType(setup.getProxy_type());
setProxyType(StringUtils.upperCase(setup.getProxy_type()));
setLocalIp(setup.getLocal_ip());
setLocalPort(setup.getLocal_port());
setRemotePort(setup.getRemote_port());
setDomain(domainStr);
setHttpUser(setup.getHttp_user());
setHttpPwd(setup.getHttp_pwd());
setDomainSuffix(suffix);
setLocations(setup.getLocations());
setUseEncryption(setup.getUse_encryption());
@ -60,6 +66,7 @@ public class ProxySetupModel {
setHeader_X_From_Where(setup.getHost_header_rewrite());
setHostHeaderRewrite(setup.getHost_header_rewrite());
setStatus(setup.getStatus() == null || setup.getStatus());
setServerName(setup.getServer_name());
setSk(setup.getSk());
setSort(setup.getSort());
setRunning(setup.isRuning());
@ -129,6 +136,14 @@ public class ProxySetupModel {
return status.get();
}
public Boolean isProvider() {
return serverName.get() == null ? null : serverName.get().equals("提供者");
}
public String getServerName() {
return serverName.get();
}
public boolean isUseCompression() {
return useCompression.get();
}
@ -137,6 +152,14 @@ public class ProxySetupModel {
return useEncryption.get();
}
public String getHttpUser() {
return httpUser.get();
}
public String getHttpPwd() {
return httpPwd.get();
}
public void setId(Integer id) {
this.id.set(id == null ? null : id.toString());
}
@ -205,10 +228,30 @@ public class ProxySetupModel {
this.status.set(status);
}
public void setServerName(String serverName) {
this.serverName.set(serverName);
}
public void setUseEncryption(boolean useEncryption) {
this.useEncryption.set(useEncryption);
}
public void setHttpUser(String httpUser) {
this.httpUser.set(httpUser);
}
public void setHttpPwd(String httpPwd) {
this.httpPwd.set(httpPwd);
}
public SimpleStringProperty httpPwdProperty() {
return httpPwd;
}
public SimpleStringProperty httpUserProperty() {
return httpUser;
}
public SimpleStringProperty serverProperty() {
return server;
}
@ -281,6 +324,10 @@ public class ProxySetupModel {
return hostHeaderRewrite;
}
public SimpleStringProperty serverNameProperty() {
return serverName;
}
public SimpleBooleanProperty isCustomizeProperty() {
return isCustomize;
}
@ -307,6 +354,7 @@ public class ProxySetupModel {
setHeader_X_From_Where(setup.getHost_header_rewrite());
setHostHeaderRewrite(setup.getHost_header_rewrite());
setStatus(setup.getStatus() == null || setup.getStatus());
setServerName(setup.getServer_name());
setSk(setup.getSk());
setSort(setup.getSort());
setRunning(setup.isRuning());
@ -322,8 +370,10 @@ public class ProxySetupModel {
StringUtils.lowerCase(proxyType.getValue()),
getLocalIp(),
getLocalPort() == null ? null : Integer.parseInt(getLocalPort()),
getRemotePort() == null ? "" : getRemotePort(),
getDomain() + getDomainSuffix(),
getRemotePort(),
getDomain() == null ? null : getDomain() + getDomainSuffix(),
ProxyConfig.isHttp(this) ? getHttpUser() : null,
ProxyConfig.isHttp(this) ? getHttpPwd() : null,
isUseEncryption(),
isUseCompression(),
getLocations(),
@ -331,6 +381,7 @@ public class ProxySetupModel {
getHeader_X_From_Where(),
getSk(),
getSort(),
serverName.get(),
getStatus(),
isRunning()
);

View File

@ -12,6 +12,7 @@ import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
@ -33,30 +34,30 @@ public class AlertUtil {
public Builder(T alert) {
this.alert = alert;
if(mOwner != null) this.alert.initOwner(mOwner);
if (mOwner != null) this.alert.initOwner(mOwner);
}
public Builder title(String title) {
public Builder<T> title(String title) {
alert.setTitle(title);
return this;
}
public Builder header(String header) {
public Builder<T> header(String header) {
alert.setHeaderText(header);
return this;
}
public Builder content(String content) {
public Builder<T> content(String content) {
alert.setContentText(content);
return this;
}
public Builder icon(String path) {
icon(new Image(this.getClass().getResource(path).toString()));
public Builder<T> icon(String path) {
icon(new Image(Objects.requireNonNull(this.getClass().getResource(path)).toString()));
return this;
}
public Builder icon(Image image) {
public Builder<T> icon(Image image) {
getStage().getIcons().add(image);
return this;
}
@ -67,7 +68,6 @@ public class AlertUtil {
/**
* AlertUtil.confirm
* @param listener
*/
public void show(OnClickListener listener) {
@ -78,7 +78,6 @@ public class AlertUtil {
/**
* AlertUtil.confirm
* @param listener
*/
public void show(OnChoseListener listener) {
Optional<ButtonType> result = alert.showAndWait();
@ -103,13 +102,10 @@ public class AlertUtil {
/**
* AlertUtil.choices
* @param choices
* @param <R>
* @return
*/
public <R> R getChoice(R... choices) {
Optional<R> result = alert.showAndWait();
return result.get();
Optional result = alert.showAndWait();
return (R) result.get();
}
private Stage getStage() {
@ -117,24 +113,24 @@ public class AlertUtil {
}
}
public static Builder info(String content) {
return new Builder(new Alert(AlertType.INFORMATION)).content(content).header(null);
public static Builder<Alert> info(String content) {
return new Builder<Alert>(new Alert(AlertType.INFORMATION)).content(content).header(null);
}
public static Builder info() {
return new Builder(new Alert(AlertType.INFORMATION));
public static Builder<Alert> info() {
return new Builder<Alert>(new Alert(AlertType.INFORMATION));
}
public static Builder error(String message) {
return new Builder(new Alert(AlertType.ERROR)).header(null).content(message);
public static Builder<Alert> error(String message) {
return new Builder<Alert>(new Alert(AlertType.ERROR)).header(null).content(message);
}
public static Builder warning() {
return new Builder(new Alert(AlertType.WARNING));
public static Builder<Alert> warning() {
return new Builder<Alert>(new Alert(AlertType.WARNING));
}
public static Builder exception(Exception ex) {
return new Builder(exceptionAlert(ex));
public static Builder<Alert> exception(Exception ex) {
return new Builder<Alert>(exceptionAlert(ex));
}
public static Alert exceptionAlert(Exception ex) {
@ -173,17 +169,17 @@ public class AlertUtil {
/**
* 确认对话框
*/
public static Builder confirm() {
public static Builder<Alert> confirm() {
Alert alert = new Alert(AlertType.CONFIRMATION);
alert.setTitle("确认对话框");
return new Builder(alert);
return new Builder<Alert>(alert);
}
/**
* 自定义确认对话框 <p>
* <code>"Cancel"</code> OR <code>"取消"</code> 为取消按钮
*/
public static Builder confirm(String... buttons) {
public static Builder<Alert> confirm(String... buttons) {
Alert alert = new Alert(AlertType.CONFIRMATION);
List<ButtonType> buttonList = Arrays.stream(buttons).map((type) -> {
@ -195,19 +191,19 @@ public class AlertUtil {
alert.getButtonTypes().setAll(buttonList);
return new Builder(alert);
return new Builder<Alert>(alert);
}
public static Builder input(String content) {
public static Builder<TextInputDialog> input(String content) {
TextInputDialog dialog = new TextInputDialog();
dialog.setContentText(content);
return new Builder(dialog);
return new Builder<TextInputDialog>(dialog);
}
public static <T> Builder choices(String hintText, T... choices) {
public static <T> Builder<ChoiceDialog<T>> choices(String hintText, T... choices) {
ChoiceDialog<T> dialog = new ChoiceDialog<T>(choices[0], choices);
dialog.setContentText(hintText);
return new Builder(dialog);
return new Builder<ChoiceDialog<T>>(dialog);
}

View File

@ -0,0 +1,37 @@
package top.octopusyan.utils;
import java.awt.*;
import java.awt.datatransfer.*;
import java.io.IOException;
/**
* <p> author : octopus yan
* <p> email : octopus_yan@foxmail.com
* <p> description : 剪切板工具
* <p> create : 2022-4-14 23:21
*/
public class ClipUtil {
//获取系统剪切板
private static final Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
public static void setClip(String data) {
//构建String数据类型
StringSelection stringSelection = new StringSelection(data);
//添加文本到系统剪切板
clipboard.setContents(stringSelection, null);
}
public static String getString() {
Transferable content = clipboard.getContents(null);//从系统剪切板中获取数据
if (content.isDataFlavorSupported(DataFlavor.stringFlavor)) {//判断是否为文本类型
try {
//从数据中获取文本值
return (String) content.getTransferData(DataFlavor.stringFlavor);
} catch (UnsupportedFlavorException | IOException e) {
e.printStackTrace();
return null;
}
}
return null;
}
}

View File

@ -2,7 +2,6 @@ package top.octopusyan.utils;
import top.octopusyan.config.ProxyConfig;
import top.octopusyan.manager.http.request.ProxySetup;
import top.octopusyan.model.ProxySetupModel;
import java.net.InetAddress;
import java.net.UnknownHostException;
@ -28,10 +27,6 @@ public class DomainUtil {
return "." + serverPath;
}
public static boolean isHttp(ProxySetupModel model){
return ProxyConfig.getTypeIndex(model.getProxyType()) < 2;
}
/**
* 是否自定义隧道
*/
@ -39,14 +34,17 @@ public class DomainUtil {
return !setup.getDomain().contains(getServerPath(setup));
}
public static boolean isCustomize(ProxySetup setup, String serverName) {
return !setup.getDomain().contains(ProxyConfig.getServerPath(serverName));
}
private static String getServerPath(ProxySetup setup) {
return ProxyConfig.getServerPath(ProxyConfig.getServerName(setup.getNode()));
}
public static String getDomainAddress(String domain) {
try {
String hostAddress = InetAddress.getByName(domain).getHostAddress();
return hostAddress;
return InetAddress.getByName(domain).getHostAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
return null;

View File

@ -50,7 +50,7 @@ public class EncryptionUtil {
* 加解密算法/工作模式/填充方式
*/
private static final String ECB_MOB = "DES/ECB/PKCS5Padding";
// private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS7Padding";
// private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS7Padding";
private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS5Padding";
private static final Charset UTF_8 = StandardCharsets.UTF_8;
@ -79,7 +79,7 @@ public class EncryptionUtil {
return getSHA(defaultPassword + getMessageDigestString("frp." + defaultPassword, hexDigits, MessageDigest_SHA1));
}
public static String decodeAppData(String str){
public static String decodeAppData(String str) {
try {
return DESdecode(str, getDefaultPassword());
} catch (Exception e) {
@ -88,7 +88,7 @@ public class EncryptionUtil {
}
}
public static String encodeAppData(String str){
public static String encodeAppData(String str) {
try {
return DESencode(str, getDefaultPassword());
} catch (Exception e) {
@ -105,16 +105,14 @@ public class EncryptionUtil {
}
public static String getSHA(String spara) {
String sRtn = null;
try {
byte[] plainText = spara.getBytes(UTF_8);
// 开始使用算法
MessageDigest_SHA1.update(plainText);
// 输出算法运算结果
sRtn = BASE64Encoder.encode(MessageDigest_SHA1.digest());
} catch (Exception e) {
logger.error("SHA-1 160加密出错" + e.getMessage());
}
String sRtn;
byte[] plainText = spara.getBytes(UTF_8);
// 开始使用算法
MessageDigest_SHA1.update(plainText);
// 输出算法运算结果
sRtn = BASE64Encoder.encode(MessageDigest_SHA1.digest());
return sRtn;
}
@ -124,16 +122,13 @@ public class EncryptionUtil {
* @param data 加密内容
* @param password 秘钥
*/
public static String DESencode(String data, String password) {
public static String DESencode(String data, String password) throws Exception {
byte[] pasByte;
try {
Key key = getDESKey(password);
Cipher_ECB_MOB.init(Cipher.ENCRYPT_MODE, key);
pasByte = Cipher_ECB_MOB.doFinal(data.getBytes(UTF_8));
} catch (Exception e) {
logger.error("DES加密出错" + e.getMessage());
throw new RuntimeException("DES加密出错" + e.getMessage());
}
Key key = getDESKey(password);
Cipher_ECB_MOB.init(Cipher.ENCRYPT_MODE, key);
pasByte = Cipher_ECB_MOB.doFinal(data.getBytes(UTF_8));
return Base64.encode(pasByte);
}
@ -259,19 +254,16 @@ public class EncryptionUtil {
* @return MD5加密后生成32位(大写字母 + 数字)字符串
*/
public static String MD5Upper(String plainText, String saltValue) {
try {
// 使用指定的字节更新摘要
MessageDigest_MD5.update(plainText.getBytes());
MessageDigest_MD5.update(saltValue.getBytes());
// 获得密文
byte[] mdResult = MessageDigest_MD5.digest();
// 把密文转换成十六进制的字符串形式
return getHexString(hexDigitsLower, mdResult);
} catch (Exception e) {
logger.error("MDS 加密出错:" + e.getMessage());
throw new RuntimeException("MDS 加密出错:" + e.getMessage());
}
// 使用指定的字节更新摘要
MessageDigest_MD5.update(plainText.getBytes());
MessageDigest_MD5.update(saltValue.getBytes());
// 获得密文
byte[] mdResult = MessageDigest_MD5.digest();
// 把密文转换成十六进制的字符串形式
return getHexString(hexDigitsLower, mdResult);
}
/**
@ -282,6 +274,14 @@ public class EncryptionUtil {
return getMessageDigestString(plainText, hexDigitsLower, MessageDigest_MD5);
}
/**
* MD5加密后生成16位(小写字母+数字)字符串
* MD5Lower() 一样
*/
public static String MD5_16(String plainText) {
return getMessageDigestString(plainText, hexDigitsLower, MessageDigest_MD5).substring(8, 24);
}
/**
* 校验MD5码
*

View File

@ -0,0 +1,139 @@
package top.octopusyan.utils;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.scene.control.Tooltip;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.stage.Window;
/**
* <p> author : octopus yan
* <p> email : octopus_yan@foxmail.com
* <p> description : 提示工具
* <p> create : 2022-4-12 17:16 *
*/
public class TooltipUtil {
private static TooltipUtil util;
private final Tooltip tooltip = new Tooltip();
private Window owner;
private ChangeListener<Number> xListener;
private ChangeListener<Number> yListener;
private boolean paneMove = false;
private TooltipUtil(Window window) {
this.owner = window;
this.tooltip.styleProperty().set(
"-fx-background-color: white;" +
"-fx-text-fill: grey;" +
"-fx-font-size: 12px;"
);
}
public static TooltipUtil getInstance(Pane pane) {
if (pane == null) return null;
Window window = pane.getScene().getWindow();
if (window == null) return null;
if (util == null) {
util = new TooltipUtil(window);
// 窗口位置监听
util.xListener = (observable, oldValue, newValue) -> {
util.tooltip.setAnchorX(util.tooltip.getAnchorX() + (newValue.doubleValue() - oldValue.doubleValue()));
util.paneMove = true;
};
util.yListener = (observable, oldValue, newValue) -> {
util.tooltip.setAnchorY(util.tooltip.getAnchorY() + (newValue.doubleValue() - oldValue.doubleValue()));
util.paneMove = true;
};
util.tooltip.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue) util.paneMove = false;
});
// 随窗口移动
util.owner.xProperty().addListener(util.xListener);
util.owner.yProperty().addListener(util.yListener);
}
if (!window.equals(util.owner)) {
// 删除旧监听
util.owner.xProperty().removeListener(util.xListener);
util.owner.yProperty().removeListener(util.yListener);
// 新窗口
util.owner = window;
// 随窗口移动
util.owner.xProperty().addListener(util.xListener);
util.owner.yProperty().addListener(util.yListener);
}
// 点击关闭
pane.setOnMouseClicked(event -> {
if (!util.paneMove) util.tooltip.hide();
util.paneMove = false;
});
util.tooltip.hide();
return util;
}
public void showProxyTypeTip(MouseEvent event) {
tooltip.setText(
"提示XTCP 映射成功率并不高,具体取决于 NAT 设备的复杂度。\n" +
"TCP :基础的 TCP 映射适用于大多数服务例如远程桌面、SSH、Minecraft、泰拉瑞亚等\n" +
"UDP :基础的 UDP 映射,适用于域名解析、部分基于 UDP 协议的游戏等\n" +
"HTTP :搭建网站专用映射,并通过 80 端口访问\n" +
"HTTPS :带有 SSL 加密的网站映射,通过 443 端口访问,服务器需要支持 SSL\n" +
"XTCP :客户端之间点对点 (P2P) 连接协议,流量不经过服务器,适合大流量传输的场景,需要两台设备之间都运行一个客户端\n" +
"STCP :安全交换 TCP 连接协议,基于 TCP访问此服务的用户也需要运行一个客户端才能建立连接流量由服务器转发"
);
show(event);
}
public void showHttpUserTip(MouseEvent event) {
tooltip.setText(
"您的内网服务映射到外网之后,所有知道外网访问地址的人均可以访问;\n" +
"但如果您在此处设置用户名、密码,则只有知道用户名、密码的访客才能访问"
);
show(event);
}
public void showProxyStatusTip(MouseEvent event) {
tooltip.setText("当有多个连接时,您可以选择启用或停用某些连接");
show(event);
}
private void show(MouseEvent event) {
if (tooltip.isShowing()) {
tooltip.hide();
} else {
tooltip.show(owner);
double mx = event.getScreenX();
double my = event.getScreenY();
double tw = tooltip.widthProperty().doubleValue();
double th = tooltip.heightProperty().doubleValue();
tooltip.setX(mx - tw / 2);
tooltip.setY(my - th - 10);
}
}
public void hide(){
tooltip.hide();
}
public boolean isShowing(){
return tooltip.isShowing();
}
public void showP2pPwdTip(MouseEvent event) {
tooltip.setText("以访客模式连接时的密码,仅限 XTCP/STCP\n只有访问密码一致的用户才能访问到此服务");
show(event);
}
public void showP2pServerName(MouseEvent event) {
tooltip.setText("要访问的服务的名字(系统加密)");
show(event);
}
}

View File

@ -55,6 +55,13 @@
-fx-text-fill: white;
}
.proxyListItemLabel {
-fx-font-size: 16px;
}
.proxyListItemIcon {
-fx-font-size: 14px;
}
.proxyListItem-run FontIcon {
-fx-icon-color: linear-gradient(#95f257, #91e5ac);
}
@ -66,7 +73,7 @@
}
/* 面板背景 */
#proxySetupPane, #proxyListPane {
#proxySetupPane, #proxyListPane, .whitePane {
-fx-background-color: white;
}
@ -101,7 +108,7 @@
-fx-font-size: 16px;
-fx-font-family: "Microsoft YaHei";
}
.startProxyBtn {
.startProxyBtn, #copyP2pConfig, #importP2pConfig {
-fx-text-fill: white;
-fx-background-color: linear-gradient(#57b4f2, #9198e5);
}
@ -120,3 +127,9 @@
-fx-font-family: monospace;
-fx-background-color: white;
}
.proxySetupItemBox {
-fx-pref-width: 520;
-fx-pref-height: 50;
-fx-padding: 10 0 10 0;
}

View File

@ -2,6 +2,7 @@
<?import com.jfoenix.controls.*?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.shape.Rectangle?>
@ -23,7 +24,7 @@
<VBox fx:id="loginMainPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
prefHeight="330.0" prefWidth="430.0">
<AnchorPane fx:id="loginTopPane" prefHeight="130.0" prefWidth="430.0">
<JFXButton fx:id="titleLable" disable="true" prefHeight="35.0" prefWidth="64.0" text="Yan Frp" />
<JFXButton fx:id="titleLable" disable="true" prefHeight="35.0" prefWidth="64.0" text="YanFrp"/>
<JFXButton fx:id="minimizeBtn" layoutX="360.0" prefHeight="35.0" prefWidth="36.0" text="—"/>
<JFXButton fx:id="closeBtn" layoutX="395.0" prefHeight="35.0" prefWidth="36.0" text="X"/>
</AnchorPane>
@ -88,8 +89,8 @@
<Font size="11.5"/>
</font>
</JFXCheckBox>
<JFXButton fx:id="findpassBtn" alignment="TOP_CENTER" contentDisplay="RIGHT" prefHeight="23.0" prefWidth="64.0"
text="忘记密码" textAlignment="RIGHT" textFill="#00000078">
<JFXButton fx:id="findpassBtn" alignment="TOP_CENTER" contentDisplay="RIGHT" prefHeight="23.0"
prefWidth="64.0" text="忘记密码" textAlignment="RIGHT" textFill="#00000078">
<font>
<Font size="11.0"/>
</font>
@ -108,6 +109,11 @@
<Font size="11.0"/>
</font>
</JFXButton>
<Label fx:id="appVersionLabel" disable="true" layoutX="369.0" layoutY="172.0" text="Label">
<font>
<Font size="14.0"/>
</font>
</Label>
</AnchorPane>
</VBox>
</StackPane>

View File

@ -6,7 +6,7 @@
<?import javafx.scene.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<?import org.kordamp.ikonli.javafx.FontIcon?>
<?import org.kordamp.ikonli.javafx.*?>
<StackPane fx:id="root" prefHeight="700.0" prefWidth="750.0" stylesheets="@../css/main.css"
xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="top.octopusyan.controller.MainController">
@ -14,32 +14,33 @@
<VBox fx:id="MainPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
prefHeight="700.0" prefWidth="750.0">
<AnchorPane fx:id="MainTopPane" prefHeight="35.0" prefWidth="750.0">
<JFXButton fx:id="titleLable" disable="true" prefHeight="35.0" prefWidth="90.0" text="Yan Frp"/>
<JFXButton fx:id="minimizeBtn" layoutX="678.0" prefHeight="35.0" prefWidth="36.0" text="—"/>
<JFXButton fx:id="closeBtn" layoutX="714.0" prefHeight="35.0" prefWidth="36.0" text="X"/>
<JFXComboBox fx:id="proxyServerComboBox" layoutX="90.0" prefHeight="36.0" prefWidth="90.0">
<Label fx:id="titleLable" alignment="CENTER" disable="true" prefHeight="35.0" prefWidth="90.0"
text="YanFrp"/>
<JFXButton fx:id="minimizeBtn" layoutX="680.0" prefHeight="35.0" prefWidth="35.0" text="—"/>
<JFXButton fx:id="closeBtn" layoutX="715.0" prefHeight="35.0" prefWidth="35.0" text="X"/>
<JFXComboBox fx:id="proxyServerComboBox" layoutX="90.0" prefHeight="35.0" prefWidth="90.0">
<cursor>
<Cursor fx:constant="HAND"/>
</cursor>
</JFXComboBox>
</AnchorPane>
<GridPane fx:id="MainBottomPane" hgap="10.0" prefHeight="684.0" prefWidth="750.0" vgap="10.0">
<GridPane fx:id="MainBottomPane" hgap="10.0" prefHeight="680.0" prefWidth="750.0" vgap="10.0">
<VBox.margin>
<Insets top="-10.0"/>
</VBox.margin>
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="539.0" minWidth="10.0" prefWidth="520.0"/>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="370.0" minWidth="10.0" prefWidth="200.0"/>
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="493.0" minHeight="10.0" prefHeight="404.0" vgrow="SOMETIMES"/>
<RowConstraints maxHeight="337.0" minHeight="10.0" prefHeight="231.0" vgrow="SOMETIMES"/>
<RowConstraints maxHeight="493.0" minHeight="10.0" prefHeight="445.0" vgrow="SOMETIMES"/>
<RowConstraints maxHeight="337.0" minHeight="10.0" prefHeight="200.0" vgrow="SOMETIMES"/>
</rowConstraints>
<children>
<VBox fx:id="proxySetupPane" prefHeight="200.0" prefWidth="100.0" styleClass="mainPane">
<padding>
<Insets left="10.0"/>
</padding>
<HBox alignment="CENTER_RIGHT" prefHeight="50.0" prefWidth="520.0">
<HBox alignment="CENTER_RIGHT" prefHeight="40.0" prefWidth="520.0">
<padding>
<Insets bottom="10.0" top="10.0"/>
<Insets bottom="5.0" top="5.0"/>
</padding>
<JFXButton fx:id="resetProxyBtn" prefHeight="Infinity" text="重置">
<HBox.margin>
@ -50,21 +51,15 @@
</cursor>
</JFXButton>
</HBox>
<HBox alignment="CENTER_LEFT" prefHeight="50.0" prefWidth="520.0">
<padding>
<Insets bottom="10.0" top="10.0"/>
</padding>
<HBox alignment="CENTER_LEFT" styleClass="proxySetupItemBox">
<Label alignment="CENTER_RIGHT" prefHeight="Infinity" prefWidth="100.0"
styleClass="proxySetupLabel" text="隧道名称">
<HBox.margin>
<Insets right="20.0"/>
<Insets left="10.0" right="20.0"/>
</HBox.margin>
</Label>
<JFXTextField fx:id="proxyNameTextField" prefHeight="Infinity" prefWidth="320.0"
<JFXTextField fx:id="proxyNameTextField" prefHeight="Infinity" prefWidth="310.0"
styleClass="inputText">
<HBox.margin>
<Insets right="20.0"/>
</HBox.margin>
<padding>
<Insets left="15.0"/>
</padding>
@ -73,17 +68,14 @@
</font>
</JFXTextField>
</HBox>
<HBox alignment="CENTER_LEFT" prefHeight="50.0" prefWidth="520.0">
<padding>
<Insets bottom="10.0" top="10.0"/>
</padding>
<HBox alignment="CENTER_LEFT" styleClass="proxySetupItemBox">
<Label alignment="CENTER_RIGHT" prefHeight="Infinity" prefWidth="100.0"
styleClass="proxySetupLabel" text="是否启用">
<HBox.margin>
<Insets right="20.0"/>
<Insets left="10.0" right="20.0"/>
</HBox.margin>
</Label>
<HBox alignment="CENTER_LEFT" prefHeight="Infinity" prefWidth="320.0">
<HBox alignment="CENTER_LEFT" prefHeight="Infinity" prefWidth="310.0">
<JFXRadioButton fx:id="openProxyRBtn" text="启用">
<HBox.margin>
<Insets right="20.0"/>
@ -97,38 +89,94 @@
<Cursor fx:constant="HAND"/>
</cursor>
</JFXRadioButton>
<JFXButton fx:id="proxyStatusTip" text="">
<cursor>
<Cursor fx:constant="HAND"/>
</cursor>
<graphic>
<FontIcon iconColor="#a5a3a3" iconLiteral="fa-question-circle-o" iconSize="15"/>
</graphic>
<HBox.margin>
<Insets left="20.0"/>
</HBox.margin>
</JFXButton>
</HBox>
</HBox>
<HBox alignment="CENTER_LEFT" prefHeight="50.0" prefWidth="520.0">
<padding>
<Insets bottom="10.0" top="10.0"/>
</padding>
<HBox alignment="CENTER_LEFT" styleClass="proxySetupItemBox">
<Label alignment="CENTER_RIGHT" prefHeight="Infinity" prefWidth="100.0"
styleClass="proxySetupLabel" text="协议类型">
<HBox.margin>
<Insets right="20.0"/>
<Insets left="10.0" right="20.0"/>
</HBox.margin>
</Label>
<JFXComboBox fx:id="proxyProtocolComboBox" prefHeight="Infinity" prefWidth="320.0"
<JFXComboBox fx:id="proxyProtocolComboBox" prefHeight="Infinity" prefWidth="310.0"
styleClass="inputText">
<cursor>
<Cursor fx:constant="HAND"/>
</cursor>
</JFXComboBox>
</HBox>
<HBox alignment="CENTER_LEFT" prefHeight="50.0" prefWidth="520.0">
<padding>
<Insets bottom="10.0" top="10.0"/>
</padding>
<Label alignment="CENTER_RIGHT" prefHeight="Infinity" prefWidth="100.0"
styleClass="proxySetupLabel" text="内网服务地址">
<JFXButton fx:id="proxyTypeTip" text="">
<HBox.margin>
<Insets right="20.0"/>
<Insets left="10.0"/>
</HBox.margin>
<cursor>
<Cursor fx:constant="HAND"/>
</cursor>
<graphic>
<FontIcon iconColor="#a5a3a3" iconLiteral="fa-question-circle-o" iconSize="15">
</FontIcon>
</graphic>
</JFXButton>
</HBox>
<HBox fx:id="p2pRolePane" alignment="CENTER_LEFT" styleClass="proxySetupItemBox">
<Label alignment="CENTER_RIGHT" prefHeight="Infinity" prefWidth="100.0"
styleClass="proxySetupLabel" text="服务角色">
<HBox.margin>
<Insets left="10.0" right="20.0"/>
</HBox.margin>
</Label>
<HBox prefHeight="Infinity" prefWidth="320.0">
<HBox fx:id="p2pRoleView" alignment="CENTER_LEFT" prefHeight="Infinity" prefWidth="310.0">
<JFXRadioButton fx:id="serverProviderRBtn" text="提供者">
<HBox.margin>
<Insets right="20.0"/>
</HBox.margin>
<cursor>
<Cursor fx:constant="HAND"/>
</cursor>
</JFXRadioButton>
<JFXRadioButton fx:id="serverVisitorRBtn" text="访问者">
<cursor>
<Cursor fx:constant="HAND"/>
</cursor>
</JFXRadioButton>
<JFXButton fx:id="copyP2pConfig" text="复制">
<HBox.margin>
<Insets left="20.0"/>
</HBox.margin>
<cursor>
<Cursor fx:constant="HAND"/>
</cursor>
</JFXButton>
<JFXButton fx:id="importP2pConfig" text="从剪切板导入">
<HBox.margin>
<Insets left="10.0"/>
</HBox.margin>
<cursor>
<Cursor fx:constant="HAND"/>
</cursor>
</JFXButton>
</HBox>
</HBox>
<HBox alignment="CENTER_LEFT" styleClass="proxySetupItemBox">
<Label fx:id="localHostLabel" alignment="CENTER_RIGHT" prefHeight="Infinity" prefWidth="100.0"
styleClass="proxySetupLabel" text="内网服务地址">
<HBox.margin>
<Insets left="10.0" right="20.0"/>
</HBox.margin>
</Label>
<HBox prefHeight="Infinity" prefWidth="310.0">
<JFXTextField fx:id="localHostTextField" alignment="CENTER" prefWidth="220.0"
promptText="内网服务IP (IPV4)" styleClass="inputText-left" text="127.0.0.1">
promptText="内网服务IP (IPv4)" styleClass="inputText-left" text="127.0.0.1">
<font>
<Font size="14.0"/>
</font>
@ -141,28 +189,89 @@
</JFXTextField>
</HBox>
</HBox>
<HBox fx:id="domainPane" alignment="CENTER_LEFT" prefHeight="40.0" prefWidth="520.0">
<padding>
<Insets top="10.0"/>
</padding>
<HBox fx:id="serverNamePane" alignment="CENTER_LEFT" styleClass="proxySetupItemBox">
<children>
<Label alignment="CENTER_RIGHT" prefHeight="Infinity" prefWidth="100.0"
styleClass="proxySetupLabel" text="服务名称">
<HBox.margin>
<Insets left="10.0" right="20.0"/>
</HBox.margin>
</Label>
<JFXTextField fx:id="serverNameTextField" prefHeight="Infinity" prefWidth="310.0"
styleClass="inputText">
<HBox.margin>
<Insets/>
</HBox.margin>
<padding>
<Insets left="15.0"/>
</padding>
<font>
<Font size="14.0"/>
</font>
</JFXTextField>
<JFXButton fx:id="serverNameTip" text="">
<cursor>
<Cursor fx:constant="HAND"/>
</cursor>
<graphic>
<FontIcon iconColor="#a5a3a3" iconLiteral="fa-question-circle-o" iconSize="15"/>
</graphic>
<HBox.margin>
<Insets left="10.0"/>
</HBox.margin>
</JFXButton>
</children>
</HBox>
<HBox fx:id="p2pPwdPane" alignment="CENTER_LEFT" styleClass="proxySetupItemBox">
<Label alignment="CENTER_RIGHT" prefHeight="Infinity" prefWidth="100.0"
styleClass="proxySetupLabel" text="访问密码">
<HBox.margin>
<Insets left="10.0" right="20.0"/>
</HBox.margin>
</Label>
<JFXTextField fx:id="p2pPwdTextField" prefHeight="Infinity" prefWidth="310.0"
styleClass="inputText">
<HBox.margin>
<Insets/>
</HBox.margin>
<padding>
<Insets left="15.0"/>
</padding>
<font>
<Font size="14.0"/>
</font>
</JFXTextField>
<JFXButton fx:id="p2pPwdTip" text="">
<cursor>
<Cursor fx:constant="HAND"/>
</cursor>
<graphic>
<FontIcon iconColor="#a5a3a3" iconLiteral="fa-question-circle-o" iconSize="15"/>
</graphic>
<HBox.margin>
<Insets left="10.0"/>
</HBox.margin>
</JFXButton>
</HBox>
<HBox fx:id="domainPane" alignment="CENTER_LEFT" styleClass="proxySetupItemBox">
<Label alignment="CENTER_RIGHT" prefHeight="Infinity" prefWidth="100.0"
styleClass="proxySetupLabel" text="外网访问地址">
<HBox.margin>
<Insets right="20.0"/>
<Insets left="10.0" right="20.0"/>
</HBox.margin>
</Label>
<AnchorPane maxHeight="30.0" prefHeight="30" prefWidth="320.0">
<JFXTextField fx:id="domainTextField" prefHeight="31.0" prefWidth="320.0"
promptText="自定义子域名大于3位" styleClass="inputText">
<AnchorPane maxHeight="30.0" prefHeight="30" prefWidth="310.0">
<JFXTextField fx:id="domainTextField" prefHeight="31.0" prefWidth="310.0"
promptText="自定义子域名" styleClass="inputText">
<font>
<Font size="14.0"/>
</font>
<padding>
<Insets left="15.0"/>
<Insets left="10.0"/>
</padding>
</JFXTextField>
<JFXTextField fx:id="domainSuffixTextField" alignment="CENTER" disable="true"
layoutX="165.0" prefHeight="30.0" prefWidth="155.0"
layoutX="155.0" prefHeight="30.0" prefWidth="155.0"
styleClass="inputText-right" text=".frp.octopusyan.top">
<font>
<Font size="14.0"/>
@ -178,21 +287,54 @@
</cursor>
</JFXButton>
</HBox>
<Label fx:id="domainHtinTextField" alignment="CENTER" prefHeight="20.0" prefWidth="320.0"
text="Label" visible="false">
<VBox.margin>
<Insets left="120.0"/>
</VBox.margin>
<font>
<Font size="14.0"/>
</font>
</Label>
<HBox fx:id="httpUserPane" alignment="CENTER_LEFT" styleClass="proxySetupItemBox">
<Label alignment="CENTER_RIGHT" prefHeight="Infinity" prefWidth="100.0"
styleClass="proxySetupLabel" text="用户名">
<HBox.margin>
<Insets left="10.0" right="20.0"/>
</HBox.margin>
</Label>
<JFXTextField fx:id="httpUserTextField" prefHeight="Infinity" prefWidth="310.0"
promptText="(可选)外网访问用户名" styleClass="inputText">
<padding>
<Insets left="15.0"/>
</padding>
<font>
<Font size="14.0"/>
</font>
</JFXTextField>
<JFXButton fx:id="httpUserTip" text="">
<cursor>
<Cursor fx:constant="HAND"/>
</cursor>
<graphic>
<FontIcon iconColor="#a5a3a3" iconLiteral="fa-question-circle-o" iconSize="15"/>
</graphic>
<HBox.margin>
<Insets left="10.0"/>
</HBox.margin>
</JFXButton>
</HBox>
<HBox fx:id="httpPwdPane" alignment="CENTER_LEFT" styleClass="proxySetupItemBox">
<Label alignment="CENTER_RIGHT" prefHeight="Infinity" prefWidth="100.0"
styleClass="proxySetupLabel" text="密码">
<HBox.margin>
<Insets left="10.0" right="20.0"/>
</HBox.margin>
</Label>
<JFXTextField fx:id="httpPwdTextField" prefHeight="Infinity" prefWidth="310.0"
promptText="(可选)外网访问密码" styleClass="inputText">
<padding>
<Insets left="15.0"/>
</padding>
<font>
<Font size="14.0"/>
</font>
</JFXTextField>
</HBox>
<HBox fx:id="proxyStatusPane" alignment="CENTER" prefHeight="40.0" prefWidth="520.0">
<padding>
<Insets bottom="10.0"/>
</padding>
<Label alignment="CENTER_RIGHT" prefHeight="30.0" styleClass="proxySetupLabel"
text="启动成功!立即访问 ">
<Label fx:id="runingLabel" alignment="CENTER_RIGHT" prefHeight="30.0"
styleClass="proxySetupLabel" text="启动成功!立即访问 ">
<HBox.margin>
<Insets/>
</HBox.margin>
@ -202,7 +344,7 @@
</HBox>
</VBox>
<VBox fx:id="proxyListPane" prefHeight="Infinity" prefWidth="Infinity" GridPane.columnIndex="1">
<JFXListView fx:id="proxyListView" prefHeight="360.0" prefWidth="Infinity"/>
<JFXListView fx:id="proxyListView" prefHeight="400.0" prefWidth="200.0"/>
<JFXButton fx:id="addProxyBtn" prefHeight="45.0" prefWidth="Infinity" text="新增连接">
<graphic>
<FontIcon iconColor="white" iconLiteral="fa-plus"/>
@ -213,19 +355,15 @@
</JFXButton>
</VBox>
<AnchorPane prefHeight="Infinity" prefWidth="Infinity" GridPane.rowIndex="1">
<JFXTabPane fx:id="tabPane" prefHeight="230" prefWidth="520.0" styleClass="mainPane">
<JFXTabPane fx:id="tabPane" prefHeight="200.0" prefWidth="520.0" styleClass="mainPane">
<tabs>
<Tab fx:id="proxyLogPane" text=" 日志 "/>
<Tab text="常见问题">
<VBox minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
<VBox minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0"
styleClass="whitePane">
<padding>
<Insets left="20.0" top="20.0"/>
</padding>
<HBox prefHeight="30.0" prefWidth="Infinity" visible="false">
<Label prefHeight="Infinity" text="* 若启动后无法通过外网访问,请点击"/>
<Hyperlink prefHeight="Infinity" text="此处"/>
<Label prefHeight="Infinity" text="排查问题"/>
</HBox>
<Label prefHeight="30" text="* 修改配置信息后,必须重启才能生效"/>
<Label prefHeight="30" text="* 只有启动或重启内网穿透服务时,配置信息才会被保存"/>
<Label prefHeight="30" text="* 同一个账号在同一时间只能在一台电脑上登录使用"/>
@ -265,7 +403,21 @@
<cursor>
<Cursor fx:constant="HAND"/>
</cursor>
<font>
<Font size="14.0"/>
</font>
</JFXButton>
<Label fx:id="appVersionLabel" contentDisplay="CENTER" disable="true">
<VBox.margin>
<Insets top="80.0"/>
</VBox.margin>
<font>
<Font size="11.0"/>
</font>
<opaqueInsets>
<Insets/>
</opaqueInsets>
</Label>
</VBox>
</children>
</GridPane>

View File

@ -1,11 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import com.jfoenix.controls.*?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.shape.Rectangle?>
<?import javafx.scene.text.*?>
<StackPane fx:id="root" prefHeight="330.0" prefWidth="430.0" stylesheets="@../css/register.css" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="top.octopusyan.controller.RegisterController">
<StackPane fx:id="root" prefHeight="330.0" prefWidth="430.0" stylesheets="@../css/register.css"
xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="top.octopusyan.controller.RegisterController">
<ImageView fx:id="registBkgPane" fitHeight="330.0" fitWidth="430.0">
<clip>
<Rectangle height="330" width="430">
@ -14,42 +17,51 @@
</Rectangle>
</clip>
</ImageView>
<VBox fx:id="registMainPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="330.0" prefWidth="430.0">
<VBox fx:id="registMainPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
prefHeight="330.0" prefWidth="430.0">
<AnchorPane fx:id="registTopPane" prefHeight="130.0" prefWidth="430.0">
<JFXButton fx:id="titleLable" disable="true" prefHeight="35.0" prefWidth="64.0" text="Yan Frp" />
<JFXButton fx:id="minimizeBtn" layoutX="360.0" prefHeight="35.0" prefWidth="36.0" text="—" />
<JFXButton fx:id="closeBtn" layoutX="395.0" prefHeight="35.0" prefWidth="36.0" text="X" />
<JFXButton fx:id="titleLable" disable="true" prefHeight="35.0" prefWidth="64.0" text="YanFrp"/>
<JFXButton fx:id="minimizeBtn" layoutX="360.0" prefHeight="35.0" prefWidth="36.0" text="—"/>
<JFXButton fx:id="closeBtn" layoutX="395.0" prefHeight="35.0" prefWidth="36.0" text="X"/>
</AnchorPane>
<AnchorPane fx:id="registBottomPane" prefHeight="200.0" prefWidth="430.0">
<JFXTextField fx:id="accooundTextField" labelFloat="true" layoutX="25.0" layoutY="25.0" prefHeight="29.0" prefWidth="279.0" promptText="请输入6-18位账号支持大小写数字下划线">
<JFXTextField fx:id="accooundTextField" labelFloat="true" layoutX="25.0" layoutY="21.0" prefHeight="29.0"
prefWidth="279.0" promptText="请输入6-18位账号支持大小写数字下划线">
<font>
<Font size="14.0" />
<Font size="14.0"/>
</font>
</JFXTextField>
<JFXTextField fx:id="emailTf" labelFloat="true" layoutX="25.0" layoutY="90.0" promptText="请输入邮箱">
<JFXTextField fx:id="emailTf" labelFloat="true" layoutX="25.0" layoutY="70.0" promptText="请输入邮箱">
<font>
<Font size="14.0" />
<Font size="14.0"/>
</font>
</JFXTextField>
<HBox layoutX="25.0" layoutY="145.0" prefHeight="30.0" prefWidth="171.0">
<JFXTextField fx:id="emailCheckCodeTf" prefHeight="30.0" prefWidth="92.0" promptText="6位验证号码">
<HBox layoutX="25.0" layoutY="127.0" prefHeight="30.0" prefWidth="170.0">
<JFXTextField fx:id="emailCheckCodeTf" prefHeight="30.0" prefWidth="94.0" promptText="6位验证号码">
<font>
<Font size="14.0" />
<Font size="14.0"/>
</font>
</JFXTextField>
<JFXButton fx:id="sendCheckCodeBtn" buttonType="RAISED" layoutX="75.0" prefHeight="30.0" text="发送验证码" />
<JFXButton fx:id="sendCheckCodeBtn" buttonType="RAISED" prefHeight="30.0" text="发送验证码"/>
</HBox>
<JFXPasswordField fx:id="passwordTextField" labelFloat="true" layoutX="233.0" layoutY="70.0" promptText="请输入密码不少于6位">
<JFXPasswordField fx:id="passwordTextField" labelFloat="true" layoutX="233.0" layoutY="70.0"
promptText="请输入密码不少于6位">
<font>
<Font size="14.0" />
<Font size="14.0"/>
</font>
</JFXPasswordField>
<JFXButton fx:id="registerBtn" buttonType="RAISED" layoutX="227.0" layoutY="127.0" prefHeight="51.0" prefWidth="177.0" text="注册" textFill="WHITE">
<JFXButton fx:id="registerBtn" buttonType="RAISED" layoutX="227.0" layoutY="127.0" prefHeight="51.0"
prefWidth="177.0" text="注册" textFill="WHITE">
<font>
<Font size="15.0" />
<Font size="15.0"/>
</font>
</JFXButton>
<JFXButton fx:id="loginBtn" layoutX="306.0" layoutY="24.0" text="已有账号,去登录" textFill="#00000080" />
<JFXButton fx:id="loginBtn" layoutX="306.0" layoutY="24.0" text="已有账号,去登录" textFill="#00000080"/>
<Label fx:id="appVersionLabel" disable="true" layoutX="80.0" layoutY="171.0" text="Label">
<font>
<Font size="14.0"/>
</font>
</Label>
</AnchorPane>
</VBox>
</StackPane>