🚧 传输配置完善

This commit is contained in:
刘嘉伟 2025-01-06 16:14:52 +08:00
parent a1910be29c
commit 91b97df99a
4 changed files with 643 additions and 392 deletions

View File

@ -1,268 +1,277 @@
import {app, dialog, ipcMain, shell} from "electron"; import { app, dialog, ipcMain, shell } from "electron";
import {clearConfig, getConfig, saveConfig} from "../storage/config"; import { clearConfig, getConfig, saveConfig } from "../storage/config";
import {clearVersion, listVersion} from "../storage/version"; import { clearVersion, listVersion } from "../storage/version";
import {genIniConfig, genTomlConfig, stopFrpcProcess} from "./frpc"; import { genIniConfig, genTomlConfig, stopFrpcProcess } from "./frpc";
import {clearProxy, insertProxy, listProxy} from "../storage/proxy"; import { clearProxy, insertProxy, listProxy } from "../storage/proxy";
import path from "path"; import path from "path";
import fs from "fs"; import fs from "fs";
const log = require("electron-log"); const log = require("electron-log");
const toml = require("@iarna/toml"); const toml = require("@iarna/toml");
const {v4: uuidv4} = require("uuid"); const { v4: uuidv4 } = require("uuid");
export const initConfigApi = win => { export const initConfigApi = win => {
ipcMain.on("config.saveConfig", async (event, args) => { ipcMain.on("config.saveConfig", async (event, args) => {
saveConfig(args, (err, numberOfUpdated, upsert) => { saveConfig(args, (err, numberOfUpdated, upsert) => {
if (!err) { if (!err) {
const start = args.systemSelfStart || false; const start = args.systemSelfStart || false;
log.info("开启自启状态", start); log.info("开启自启状态", start);
app.setLoginItemSettings({ app.setLoginItemSettings({
openAtLogin: start, //win openAtLogin: start, //win
openAsHidden: start //macOs openAsHidden: start //macOs
}); });
}
event.reply("Config.saveConfig.hook", {
err: err,
numberOfUpdated: numberOfUpdated,
upsert: upsert
});
});
});
ipcMain.on("config.getConfig", async (event, args) => {
getConfig((err, doc) => {
event.reply("Config.getConfig.hook", {
err: err,
data: doc
});
});
});
ipcMain.on("config.versions", event => {
listVersion((err, doc) => {
event.reply("Config.versions.hook", {
err: err,
data: doc
});
});
});
ipcMain.on("config.hasConfig", event => {
getConfig((err, doc) => {
event.reply("Config.getConfig.hook", {
err: err,
data: doc
});
});
});
ipcMain.on("config.exportConfig", async (event, args) => {
const result = await dialog.showOpenDialog({
properties: ["openDirectory"]
});
const outputDirectory = result.filePaths[0];
if (!outputDirectory) {
// 取消了
return;
}
log.info(`导出目录 ${outputDirectory} 类型:${args}`);
getConfig((err1, config) => {
if (!err1 && config) {
listProxy((err2, proxys) => {
if (!err2) {
let configContent = "";
if (args === "ini") {
configContent = genIniConfig(config, proxys);
} else if (args === "toml") {
configContent = genTomlConfig(config, proxys);
} }
event.reply("Config.saveConfig.hook", { const configPath = path.join(
err: err, outputDirectory,
numberOfUpdated: numberOfUpdated, `frpc-desktop.${args}`
upsert: upsert );
fs.writeFile(
configPath, // 配置文件目录
configContent, // 配置文件内容
{ flag: "w" },
err => {
if (!err) {
// callback(filename);
event.reply("config.exportConfig.hook", {
data: "导出错误",
err: err
});
}
}
);
event.reply("Config.exportConfig.hook", {
data: {
configPath: configPath
}
}); });
}
}); });
}
}); });
});
ipcMain.on("config.getConfig", async (event, args) => { const parseTomlConfig = (tomlPath: string) => {
getConfig((err, doc) => { const importConfigPath = tomlPath;
event.reply("Config.getConfig.hook", { const tomlData = fs.readFileSync(importConfigPath, "utf-8");
err: err, log.info(`读取到配置内容 ${tomlData}`);
data: doc const sourceConfig = toml.parse(tomlData);
}); // log.info(`解析结果 ${sourceConfig}`);
}); // console.log(sourceConfig, "frpcConfig");
}); // 解析config
const targetConfig: FrpConfig = {
ipcMain.on("config.versions", event => { currentVersion: null,
listVersion((err, doc) => { serverAddr: sourceConfig.serverAddr || "",
event.reply("Config.versions.hook", { serverPort: sourceConfig.serverPort || "",
err: err, authMethod: sourceConfig?.user
data: doc ? "multiuser"
}); : sourceConfig?.auth?.method || "",
}); authToken: sourceConfig?.auth?.token || "",
}); transportHeartbeatInterval:
sourceConfig?.transport?.heartbeatInterval || 30,
ipcMain.on("config.hasConfig", event => { transportHeartbeatTimeout:
getConfig((err, doc) => { sourceConfig?.transport?.heartbeatTimeout || 90,
event.reply("Config.getConfig.hook", { tlsConfigEnable: sourceConfig?.transport?.tls?.enable || false,
err: err, tlsConfigCertFile: sourceConfig?.transport?.tls?.certFile || "",
data: doc tlsConfigKeyFile: sourceConfig?.transport?.tls?.keyFile || "",
}); tlsConfigServerName: sourceConfig?.transport?.tls?.serverName || "",
}); tlsConfigTrustedCaFile: sourceConfig?.transport?.tls?.trustedCaFile || "",
}); logLevel: sourceConfig?.log?.level || "info",
logMaxDays: sourceConfig?.log?.maxDays || 3,
ipcMain.on("config.exportConfig", async (event, args) => { proxyConfigProxyUrl: sourceConfig?.transport?.proxyURL || "",
const result = await dialog.showOpenDialog({ proxyConfigEnable: Boolean(sourceConfig?.transport?.proxyURL) || false,
properties: ["openDirectory"] user: sourceConfig?.user || "",
}); metaToken: sourceConfig?.metadatas?.token || "",
const outputDirectory = result.filePaths[0]; systemSelfStart: false,
if (!outputDirectory) { systemStartupConnect: false,
// 取消了 systemSilentStartup: false,
return; webEnable: true,
} webPort: sourceConfig?.webServer?.port || 57400,
log.info(`导出目录 ${outputDirectory} 类型:${args}`); transportProtocol: sourceConfig?.transport?.protocol || "tcp",
getConfig((err1, config) => { transportDialServerTimeout:
if (!err1 && config) { sourceConfig?.transport?.dialServerTimeout || 10,
listProxy((err2, proxys) => { transportDialServerKeepalive:
if (!err2) { sourceConfig?.transport?.dialServerKeepalive || 70,
let configContent = ""; transportPoolCount: sourceConfig?.transport?.poolCount || 0,
if (args === "ini") { transportTcpMux: sourceConfig?.transport?.tcpMux || true,
configContent = genIniConfig(config, proxys); transportTcpMuxKeepaliveInterval:
} else if (args === "toml") { sourceConfig?.transport?.tcpMuxKeepaliveInterval || 7200
configContent = genTomlConfig(config, proxys);
}
const configPath = path.join(
outputDirectory,
`frpc-desktop.${args}`
);
fs.writeFile(
configPath, // 配置文件目录
configContent, // 配置文件内容
{flag: "w"},
err => {
if (!err) {
// callback(filename);
event.reply("config.exportConfig.hook", {
data: "导出错误",
err: err
});
}
}
);
event.reply("Config.exportConfig.hook", {
data: {
configPath: configPath
}
});
}
});
}
});
});
const parseTomlConfig = (tomlPath: string) => {
const importConfigPath = tomlPath;
const tomlData = fs.readFileSync(importConfigPath, "utf-8");
log.info(`读取到配置内容 ${tomlData}`);
const sourceConfig = toml.parse(tomlData);
// log.info(`解析结果 ${sourceConfig}`);
// console.log(sourceConfig, "frpcConfig");
// 解析config
const targetConfig: FrpConfig = {
currentVersion: null,
serverAddr: sourceConfig.serverAddr || "",
serverPort: sourceConfig.serverPort || "",
authMethod: sourceConfig?.user
? "multiuser"
: sourceConfig?.auth?.method || "",
authToken: sourceConfig?.auth?.token || "",
transportHeartbeatInterval:
sourceConfig?.transport?.heartbeatInterval || 30,
transportHeartbeatTimeout:
sourceConfig?.transport?.heartbeatTimeout || 90,
tlsConfigEnable: sourceConfig?.transport?.tls?.enable || false,
tlsConfigCertFile: sourceConfig?.transport?.tls?.certFile || "",
tlsConfigKeyFile: sourceConfig?.transport?.tls?.keyFile || "",
tlsConfigServerName: sourceConfig?.transport?.tls?.serverName || "",
tlsConfigTrustedCaFile: sourceConfig?.transport?.tls?.trustedCaFile || "",
logLevel: sourceConfig?.log?.level || "info",
logMaxDays: sourceConfig?.log?.maxDays || 3,
proxyConfigProxyUrl: sourceConfig?.transport?.proxyURL || "",
proxyConfigEnable: Boolean(sourceConfig?.transport?.proxyURL) || false,
user: sourceConfig?.user || "",
metaToken: sourceConfig?.metadatas?.token || "",
systemSelfStart: false,
systemStartupConnect: false,
systemSilentStartup: false,
webEnable: true,
webPort: sourceConfig?.webServer?.port || 57400
};
let frpcProxys = [];
// 解析proxy
if (sourceConfig?.proxies && sourceConfig.proxies.length > 0) {
const frpcProxys1 = sourceConfig.proxies.map(m => {
const rm: Proxy = {
_id: uuidv4(),
name: m?.name,
type: m?.type,
localIp: m?.localIP || "",
localPort: m?.localPort || null,
remotePort: m?.remotePort || null,
customDomains: m?.customDomains || [],
subdomain: m.subdomain || "",
basicAuth: m.basicAuth || false,
httpUser: m.httpUser || "",
httpPassword: m.httpPassword || "",
// 以下为stcp参数
stcpModel: "visited",
serverName: "",
secretKey: m?.secretKey || "",
bindAddr: "",
bindPort: null,
status: m?.status || true,
fallbackTo: m?.fallbackTo,
fallbackTimeoutMs: m?.fallbackTimeoutMs || 500
};
return rm;
});
frpcProxys = [...frpcProxys, ...frpcProxys1];
}
// 解析stcp的访问者
if (sourceConfig?.visitors && sourceConfig.visitors.length > 0) {
const frpcProxys2 = sourceConfig.visitors.map(m => {
const rm: Proxy = {
_id: uuidv4(),
name: m?.name,
type: m?.type,
localIp: "",
localPort: null,
remotePort: null,
customDomains: [],
subdomain: m.subdomain || "",
basicAuth: m.basicAuth || false,
httpUser: m.httpUser || "",
httpPassword: m.httpPassword || "",
// 以下为stcp参数
stcpModel: "visitors",
serverName: m?.serverName,
secretKey: m?.secretKey || "",
bindAddr: m?.bindAddr,
bindPort: m?.bindPort,
status: m?.status || true,
fallbackTo: m?.fallbackTo,
fallbackTimeoutMs: m?.fallbackTimeoutMs || 500
};
return rm;
});
frpcProxys = [...frpcProxys, ...frpcProxys2];
}
if (targetConfig) {
clearConfig(() => {
saveConfig(targetConfig);
});
}
if (frpcProxys && frpcProxys.length > 0) {
clearProxy(() => {
frpcProxys.forEach(f => {
insertProxy(f, err => {
console.log("插入", f, err);
});
});
});
}
}; };
let frpcProxys = [];
ipcMain.on("config.importConfig", async (event, args) => { // 解析proxy
const result = await dialog.showOpenDialog(win, { if (sourceConfig?.proxies && sourceConfig.proxies.length > 0) {
properties: ["openFile"], const frpcProxys1 = sourceConfig.proxies.map(m => {
filters: [ const rm: Proxy = {
{name: "FrpcConfig Files", extensions: ["toml", "ini"]} // 允许选择的文件类型 _id: uuidv4(),
] name: m?.name,
type: m?.type,
localIp: m?.localIP || "",
localPort: m?.localPort || null,
remotePort: m?.remotePort || null,
customDomains: m?.customDomains || [],
subdomain: m.subdomain || "",
basicAuth: m.basicAuth || false,
httpUser: m.httpUser || "",
httpPassword: m.httpPassword || "",
// 以下为stcp参数
stcpModel: "visited",
serverName: "",
secretKey: m?.secretKey || "",
bindAddr: "",
bindPort: null,
status: m?.status || true,
fallbackTo: m?.fallbackTo,
fallbackTimeoutMs: m?.fallbackTimeoutMs || 500
};
return rm;
});
frpcProxys = [...frpcProxys, ...frpcProxys1];
}
// 解析stcp的访问者
if (sourceConfig?.visitors && sourceConfig.visitors.length > 0) {
const frpcProxys2 = sourceConfig.visitors.map(m => {
const rm: Proxy = {
_id: uuidv4(),
name: m?.name,
type: m?.type,
localIp: "",
localPort: null,
remotePort: null,
customDomains: [],
subdomain: m.subdomain || "",
basicAuth: m.basicAuth || false,
httpUser: m.httpUser || "",
httpPassword: m.httpPassword || "",
// 以下为stcp参数
stcpModel: "visitors",
serverName: m?.serverName,
secretKey: m?.secretKey || "",
bindAddr: m?.bindAddr,
bindPort: m?.bindPort,
status: m?.status || true,
fallbackTo: m?.fallbackTo,
fallbackTimeoutMs: m?.fallbackTimeoutMs || 500
};
return rm;
});
frpcProxys = [...frpcProxys, ...frpcProxys2];
}
if (targetConfig) {
clearConfig(() => {
saveConfig(targetConfig);
});
}
if (frpcProxys && frpcProxys.length > 0) {
clearProxy(() => {
frpcProxys.forEach(f => {
insertProxy(f, err => {
console.log("插入", f, err);
});
}); });
if (result.canceled) { });
return; }
} else { };
const filePath = result.filePaths[0];
const fileExtension = path.extname(filePath); // 获取文件后缀名
log.info(`导入文件 ${filePath} ${fileExtension}`);
if (fileExtension === ".toml") {
parseTomlConfig(filePath);
event.reply("Config.importConfig.hook", {
success: true
});
} else {
event.reply("Config.importConfig.hook", {
success: false,
data: `导入失败,暂不支持 ${fileExtension} 格式文件`
});
}
}
});
ipcMain.on("config.clearAll", async (event, args) => { ipcMain.on("config.importConfig", async (event, args) => {
stopFrpcProcess(() => { const result = await dialog.showOpenDialog(win, {
clearConfig(); properties: ["openFile"],
clearProxy(); filters: [
clearVersion(); { name: "FrpcConfig Files", extensions: ["toml", "ini"] } // 允许选择的文件类型
event.reply("Config.clearAll.hook", {}); ]
});
}); });
if (result.canceled) {
return;
} else {
const filePath = result.filePaths[0];
const fileExtension = path.extname(filePath); // 获取文件后缀名
log.info(`导入文件 ${filePath} ${fileExtension}`);
if (fileExtension === ".toml") {
parseTomlConfig(filePath);
event.reply("Config.importConfig.hook", {
success: true
});
} else {
event.reply("Config.importConfig.hook", {
success: false,
data: `导入失败,暂不支持 ${fileExtension} 格式文件`
});
}
}
});
ipcMain.on("config.openDataFolder", async (event, args) => { ipcMain.on("config.clearAll", async (event, args) => {
const userDataPath = app.getPath("userData"); stopFrpcProcess(() => {
shell.openPath(userDataPath).then((errorMessage) => { clearConfig();
if (errorMessage) { clearProxy();
console.error('Failed to open Logger:', errorMessage); clearVersion();
event.reply("Config.openDataFolder.hook", false); event.reply("Config.clearAll.hook", {});
} else {
console.log('Logger opened successfully');
event.reply("Config.openDataFolder.hook", true);
}
});
}); });
});
ipcMain.on("config.openDataFolder", async (event, args) => {
const userDataPath = app.getPath("userData");
shell.openPath(userDataPath).then(errorMessage => {
if (errorMessage) {
console.error("Failed to open Logger:", errorMessage);
event.reply("Config.openDataFolder.hook", false);
} else {
console.log("Logger opened successfully");
event.reply("Config.openDataFolder.hook", true);
}
});
});
}; };

View File

@ -55,8 +55,7 @@ export const genTomlConfig = (config: FrpConfig, proxys: Proxy[]) => {
/\\/g, /\\/g,
"\\\\" "\\\\"
); );
let toml = ` let toml = `${
${
rangePort rangePort
? `{{- range $_, $v := parseNumberRangePair "${m.localPort}" "${m.remotePort}" }}` ? `{{- range $_, $v := parseNumberRangePair "${m.localPort}" "${m.remotePort}" }}`
: "" : ""
@ -67,7 +66,7 @@ ${
? "visitors" ? "visitors"
: "proxies" : "proxies"
}]] }]]
${rangePort ? "" : `name = "${m.name}"\n`} ${rangePort ? "" : `name = "${m.name}"`}
type = "${m.type}" type = "${m.type}"
`; `;
@ -75,52 +74,38 @@ type = "${m.type}"
case "tcp": case "tcp":
case "udp": case "udp":
if (rangePort) { if (rangePort) {
toml += ` toml += `name = "${m.name}-{{ $v.First }}"
name = "${m.name}-{{ $v.First }}"
localPort = {{ $v.First }} localPort = {{ $v.First }}
remotePort = {{ $v.Second }} remotePort = {{ $v.Second }}`;
`;
} else { } else {
toml += ` toml += `localIP = "${m.localIp}"
localIP = "${m.localIp}"
localPort = ${m.localPort} localPort = ${m.localPort}
remotePort = ${m.remotePort} remotePort = ${m.remotePort}`;
`;
} }
break; break;
case "http": case "http":
case "https": case "https":
const customDomains = m.customDomains.filter(f1 => f1 !== ""); const customDomains = m.customDomains.filter(f1 => f1 !== "");
if (customDomains && customDomains.length > 0) { if (customDomains && customDomains.length > 0) {
toml += ` toml += `customDomains=[${m.customDomains.map(m => `"${m}"`)}]`;
customDomains=[${m.customDomains.map(m => `"${m}"`)}]
`;
} }
if (m.subdomain) { if (m.subdomain) {
toml += ` toml += `subdomain="${m.subdomain}"`;
subdomain="${m.subdomain}"
`;
} }
if (m.basicAuth) { if (m.basicAuth) {
toml += ` toml += `httpUser = "${m.httpUser}"
httpUser = "${m.httpUser}" httpPassword = "${m.httpPassword}"`;
httpPassword = "${m.httpPassword}"
`;
} }
if (m.https2http) { if (m.https2http) {
toml += ` toml += `[proxies.plugin]
[proxies.plugin]
type = "https2http" type = "https2http"
localAddr = "${m.localIp}:${m.localPort}" localAddr = "${m.localIp}:${m.localPort}"
crtPath = "${m.https2httpCaFile}" crtPath = "${m.https2httpCaFile}"
keyPath = "${m.https2httpKeyFile}" keyPath = "${m.https2httpKeyFile}"`;
`;
} else { } else {
toml += ` toml += `localIP = "${m.localIp}"
localIP = "${m.localIp}" localPort = ${m.localPort}`;
localPort = ${m.localPort}
`;
} }
break; break;
@ -129,25 +114,19 @@ localPort = ${m.localPort}
case "sudp": case "sudp":
if (m.stcpModel === "visitors") { if (m.stcpModel === "visitors") {
// 访问者 // 访问者
toml += ` toml += `serverName = "${m.serverName}"
serverName = "${m.serverName}"
bindAddr = "${m.bindAddr}" bindAddr = "${m.bindAddr}"
bindPort = ${m.bindPort} bindPort = ${m.bindPort}`;
`;
if (m.fallbackTo) { if (m.fallbackTo) {
toml += ` toml += `fallbackTo = "${m.fallbackTo}"
fallbackTo = "${m.fallbackTo}" fallbackTimeoutMs = ${m.fallbackTimeoutMs || 500}`;
fallbackTimeoutMs = ${m.fallbackTimeoutMs || 500}
`;
} }
} else if (m.stcpModel === "visited") { } else if (m.stcpModel === "visited") {
// 被访问者 // 被访问者
toml += ` toml += `localIP = "${m.localIp}"
localIP = "${m.localIp}"
localPort = ${m.localPort}`; localPort = ${m.localPort}`;
} }
toml += ` toml += `secretKey="${m.secretKey}"
secretKey="${m.secretKey}"
`; `;
break; break;
default: default:
@ -159,87 +138,88 @@ secretKey="${m.secretKey}"
} }
return toml; return toml;
}); });
const toml = ` const toml = `serverAddr = "${config.serverAddr}"
serverAddr = "${config.serverAddr}"
serverPort = ${config.serverPort} serverPort = ${config.serverPort}
${ ${
config.authMethod === "token" config.authMethod === "token"
? ` ? `auth.method = "token"
auth.method = "token" auth.token = "${config.authToken}"`
auth.token = "${config.authToken}"
`
: "" : ""
} }
${ ${
config.authMethod === "multiuser" config.authMethod === "multiuser"
? ` ? `user = "${config.user}"
user = "${config.user}" metadatas.token = "${config.metaToken}"`
metadatas.token = "${config.metaToken}"
`
: "" : ""
} }
${
config.transportHeartbeatInterval
? `
transport.heartbeatInterval = ${config.transportHeartbeatInterval}
`
: ""
}
${
config.transportHeartbeatTimeout
? `
transport.heartbeatTimeout = ${config.transportHeartbeatTimeout}
`
: ""
}
log.to = "frpc.log" log.to = "frpc.log"
log.level = "${config.logLevel}" log.level = "${config.logLevel}"
log.maxDays = ${config.logMaxDays} log.maxDays = ${config.logMaxDays}
webServer.addr = "127.0.0.1" webServer.addr = "127.0.0.1"
webServer.port = ${config.webPort} webServer.port = ${config.webPort}
${
config.transportProtocol
? `transport.protocol = "${config.transportProtocol}"`
: ""
}
${
config.transportPoolCount
? `transport.poolCount = ${config.transportPoolCount}`
: ""
}
${
config.transportDialServerTimeout
? `transport.dialServerTimeout = ${config.transportDialServerTimeout}`
: ""
}
${
config.transportDialServerKeepalive
? `transport.dialServerKeepalive = ${config.transportDialServerKeepalive}`
: ""
}
${
config.transportHeartbeatInterval
? `transport.heartbeatInterval = ${config.transportHeartbeatInterval}`
: ""
}
${
config.transportHeartbeatTimeout
? `transport.heartbeatTimeout = ${config.transportHeartbeatTimeout}`
: ""
}
${config.transportTcpMux ? `transport.tcpMux = ${config.transportTcpMux}` : ""}
${
config.transportTcpMux && config.transportTcpMuxKeepaliveInterval
? `transport.tcpMuxKeepaliveInterval = ${config.transportTcpMuxKeepaliveInterval}`
: ""
}
transport.tls.enable = ${config.tlsConfigEnable} transport.tls.enable = ${config.tlsConfigEnable}
${ ${
config.tlsConfigEnable && config.tlsConfigCertFile config.tlsConfigEnable && config.tlsConfigCertFile
? ` ? `transport.tls.certFile = "${config.tlsConfigCertFile}"`
transport.tls.certFile = "${config.tlsConfigCertFile}"
`
: "" : ""
} }
${ ${
config.tlsConfigEnable && config.tlsConfigKeyFile config.tlsConfigEnable && config.tlsConfigKeyFile
? ` ? `transport.tls.keyFile = "${config.tlsConfigKeyFile}"`
transport.tls.keyFile = "${config.tlsConfigKeyFile}"
`
: "" : ""
} }
${ ${
config.tlsConfigEnable && config.tlsConfigTrustedCaFile config.tlsConfigEnable && config.tlsConfigTrustedCaFile
? ` ? `transport.tls.trustedCaFile = "${config.tlsConfigTrustedCaFile}"`
transport.tls.trustedCaFile = "${config.tlsConfigTrustedCaFile}"
`
: "" : ""
} }
${ ${
config.tlsConfigEnable && config.tlsConfigServerName config.tlsConfigEnable && config.tlsConfigServerName
? ` ? `transport.tls.serverName = "${config.tlsConfigServerName}"`
transport.tls.serverName = "${config.tlsConfigServerName}"
`
: "" : ""
} }
${ ${
config.proxyConfigEnable config.proxyConfigEnable
? ` ? `transport.proxyURL = "${config.proxyConfigProxyUrl}"`
transport.proxyURL = "${config.proxyConfigProxyUrl}"
`
: "" : ""
} }
${proxyToml.join("")}`;
${proxyToml.join("")}
`;
return toml; return toml;
}; };
@ -251,8 +231,7 @@ ${proxyToml.join("")}
export const genIniConfig = (config: FrpConfig, proxys: Proxy[]) => { export const genIniConfig = (config: FrpConfig, proxys: Proxy[]) => {
const proxyIni = proxys.map(m => { const proxyIni = proxys.map(m => {
const rangePort = isRangePort(m); const rangePort = isRangePort(m);
let ini = ` let ini = `[${rangePort ? "range:" : ""}${m.name}]
[${rangePort ? "range:" : ""}${m.name}]
type = "${m.type}" type = "${m.type}"
`; `;
switch (m.type) { switch (m.type) {

View File

@ -47,7 +47,13 @@ const defaultFormData = ref<FrpConfig>({
transportHeartbeatInterval: 30, transportHeartbeatInterval: 30,
transportHeartbeatTimeout: 90, transportHeartbeatTimeout: 90,
webEnable: true, webEnable: true,
webPort: 57400 webPort: 57400,
transportProtocol: "tcp",
transportDialServerTimeout: 10,
transportDialServerKeepalive: 7200,
transportPoolCount: 0,
transportTcpMux: true,
transportTcpMuxKeepaliveInterval: 30
}); });
const formData = ref<FrpConfig>(defaultFormData.value); const formData = ref<FrpConfig>(defaultFormData.value);
@ -121,6 +127,24 @@ const rules = reactive<FormRules>({
], ],
webPort: [ webPort: [
{ required: true, message: "web界面端口不能为空", trigger: "change" } { required: true, message: "web界面端口不能为空", trigger: "change" }
],
transportProtocol: [
{ required: true, message: "web界面端口不能为空", trigger: "change" }
],
transportDialServerTimeout: [
{ required: true, message: "web界面端口不能为空", trigger: "change" }
],
transportDialServerKeepalive: [
{ required: true, message: "web界面端口不能为空", trigger: "change" }
],
transportPoolCount: [
{ required: true, message: "web界面端口不能为空", trigger: "change" }
],
transportTcpMux: [
{ required: true, message: "web界面端口不能为空", trigger: "change" }
],
transportTcpMuxKeepaliveInterval: [
{ required: true, message: "web界面端口不能为空", trigger: "change" }
] ]
}); });
@ -196,9 +220,49 @@ onMounted(() => {
defaultFormData.value.transportHeartbeatTimeout; defaultFormData.value.transportHeartbeatTimeout;
} }
if (data.webEnable == null || data.webEnable == undefined) { if (data.webEnable == null || data.webEnable == undefined) {
data.webEnable = true; data.webEnable = defaultFormData.value.webEnable;
data.webPort = 57400; data.webPort = defaultFormData.value.webPort;
} }
if (
data.transportProtocol === undefined ||
data.transportProtocol == null
) {
data.transportProtocol = defaultFormData.value.transportProtocol;
}
if (
data.transportDialServerTimeout === undefined ||
data.transportDialServerTimeout == null
) {
data.transportDialServerTimeout =
defaultFormData.value.transportDialServerTimeout;
}
if (
data.transportDialServerKeepalive === undefined ||
data.transportDialServerKeepalive == null
) {
data.transportDialServerKeepalive =
defaultFormData.value.transportDialServerKeepalive;
}
if (
data.transportPoolCount === undefined ||
data.transportPoolCount == null
) {
data.transportPoolCount = defaultFormData.value.transportPoolCount;
}
if (
data.transportTcpMux === undefined ||
data.transportTcpMux == null
) {
data.transportTcpMux = defaultFormData.value.transportTcpMux;
}
if (
data.transportTcpMuxKeepaliveInterval === undefined ||
data.transportTcpMuxKeepaliveInterval == null
) {
data.transportTcpMuxKeepaliveInterval =
defaultFormData.value.transportTcpMuxKeepaliveInterval;
}
formData.value = data; formData.value = data;
} }
} }
@ -422,7 +486,7 @@ onUnmounted(() => {
:rules="rules" :rules="rules"
label-position="right" label-position="right"
ref="formRef" ref="formRef"
label-width="130" label-width="150"
> >
<el-row :gutter="10"> <el-row :gutter="10">
<el-col :span="24"> <el-col :span="24">
@ -632,6 +696,72 @@ onUnmounted(() => {
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- <el-col :span="24">
<div class="h2">TLS Config</div>
</el-col> -->
<el-col :span="24">
<div class="h2">传输配置</div>
</el-col>
<el-col :span="12">
<el-form-item label="传输协议:" prop="transportProtocol">
<template #label>
<div class="h-full flex items-center mr-1">
<el-popover width="300" placement="top" trigger="hover">
<template #default>
frps 之间的通信协议默认为 tcp<br />
对应参数<span class="font-black text-[#5A3DAA]"
>transport.protocol</span
>
</template>
<template #reference>
<IconifyIconOffline
class="text-base"
color="#5A3DAA"
icon="info"
/>
</template>
</el-popover>
</div>
传输协议
</template>
<el-select v-model="formData.transportProtocol">
<el-option label="tcp" value="tcp" />
<el-option label="kcp" value="kcp" />
<el-option label="quic" value="quic" />
<el-option label="websocket" value="websocket" />
<el-option label="wss" value="wss" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="连接池大小:" prop="transportPoolCount">
<template #label>
<div class="h-full flex items-center mr-1">
<el-popover width="300" placement="top" trigger="hover">
<template #default>
对应参数<span class="font-black text-[#5A3DAA]"
>transport.poolCount</span
>
</template>
<template #reference>
<IconifyIconOffline
class="text-base"
color="#5A3DAA"
icon="info"
/>
</template>
</el-popover>
</div>
连接池大小
</template>
<el-input-number
class="w-full"
v-model="formData.transportPoolCount"
controls-position="right"
></el-input-number>
</el-form-item>
</el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item <el-form-item
label="心跳间隔:" label="心跳间隔:"
@ -715,9 +845,176 @@ onUnmounted(() => {
<!-- </el-input>--> <!-- </el-input>-->
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="12">
<div class="h2">TLS Config</div> <el-form-item
label="连接超时:"
prop="transportDialServerTimeout"
>
<template #label>
<div class="h-full flex items-center mr-1">
<el-popover width="300" placement="top" trigger="hover">
<template #default>
与服务器建立连接的最长等待时间默认值为10秒单位
<span class="font-black text-[#5A3DAA]"></span> <br />
对应参数<span class="font-black text-[#5A3DAA]"
>transport.dialServerTimeout</span
>
</template>
<template #reference>
<IconifyIconOffline
class="text-base"
color="#5A3DAA"
icon="info"
/>
</template>
</el-popover>
</div>
连接超时
</template>
<el-input-number
class="w-full"
v-model="formData.transportDialServerTimeout"
controls-position="right"
></el-input-number>
</el-form-item>
</el-col> </el-col>
<el-col :span="12">
<el-form-item
label="保活探测间隔:"
prop="transportDialServerKeepalive"
>
<template #label>
<div class="h-full flex items-center mr-1">
<el-popover width="300" placement="top" trigger="hover">
<template #default>
客户端与服务端之间的连接在一定时间内没有任何数据传输系统会定期发送一些心跳数据包来保持连接的活跃状态如果为负则禁用保活探测
单位
<span class="font-black text-[#5A3DAA]"></span> <br />
对应参数<span class="font-black text-[#5A3DAA]"
>transport.dialServerKeepalive</span
>
</template>
<template #reference>
<IconifyIconOffline
class="text-base"
color="#5A3DAA"
icon="info"
/>
</template>
</el-popover>
</div>
保活探测间隔
</template>
<el-input-number
class="w-full"
v-model="formData.transportDialServerKeepalive"
controls-position="right"
></el-input-number>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="多路复用:" prop="transportTcpMux">
<template #label>
<div class="h-full flex items-center mr-1">
<el-popover width="300" placement="top" trigger="hover">
<template #default>
TCP 多路复用默认启用<br />
对应参数<span class="font-black text-[#5A3DAA]"
>transport.tcpMux</span
>
</template>
<template #reference>
<IconifyIconOffline
class="text-base"
color="#5A3DAA"
icon="info"
/>
</template>
</el-popover>
</div>
TCP 多路复用
</template>
<el-switch
active-text="开"
inline-prompt
inactive-text="关"
v-model="formData.transportTcpMux"
/>
</el-form-item>
</el-col>
<el-col :span="12" v-if="formData.transportTcpMux">
<el-form-item
label="多复心跳间隔:"
prop="transportTcpMuxKeepaliveInterval"
>
<template #label>
<div class="h-full flex items-center mr-1">
<el-popover width="300" placement="top" trigger="hover">
<template #default>
多路复用的保活间隔默认值为 30 单位
<span class="font-black text-[#5A3DAA]"></span> <br />
对应参数<span class="font-black text-[#5A3DAA]"
>transport.tcpMuxKeepaliveInterval</span
>
</template>
<template #reference>
<IconifyIconOffline
class="text-base"
color="#5A3DAA"
icon="info"
/>
</template>
</el-popover>
</div>
多复心跳间隔
</template>
<el-input-number
class="w-full"
v-model="formData.transportTcpMuxKeepaliveInterval"
controls-position="right"
></el-input-number>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="启用代理:" prop="proxyConfigEnable">
<el-switch
active-text="开"
inline-prompt
inactive-text="关"
v-model="formData.proxyConfigEnable"
/>
</el-form-item>
</el-col>
<template v-if="formData.proxyConfigEnable">
<el-col :span="24">
<el-form-item label="代理地址:" prop="proxyConfigProxyUrl">
<template #label>
<div class="h-full flex items-center mr-1">
<el-popover width="300" placement="top" trigger="hover">
<template #default>
对应参数<span class="font-black text-[#5A3DAA]"
>transport.proxyURL</span
>
</template>
<template #reference>
<IconifyIconOffline
class="text-base"
color="#5A3DAA"
icon="info"
/>
</template>
</el-popover>
</div>
代理地址
</template>
<el-input
v-model="formData.proxyConfigProxyUrl"
placeholder="http://user:pwd@192.168.1.128:8080"
/>
</el-form-item>
</el-col>
</template>
<el-col :span="24"> <el-col :span="24">
<el-form-item label="启用TLS" prop="tlsConfigEnable"> <el-form-item label="启用TLS" prop="tlsConfigEnable">
<el-switch <el-switch
@ -904,48 +1201,6 @@ onUnmounted(() => {
</el-form-item> </el-form-item>
</el-col> </el-col>
</template> </template>
<el-col :span="24">
<div class="h2">代理</div>
</el-col>
<el-col :span="24">
<el-form-item label="启用代理:" prop="proxyConfigEnable">
<el-switch
active-text="开"
inline-prompt
inactive-text="关"
v-model="formData.proxyConfigEnable"
/>
</el-form-item>
</el-col>
<template v-if="formData.proxyConfigEnable">
<el-col :span="24">
<el-form-item label="代理地址:" prop="proxyConfigProxyUrl">
<template #label>
<div class="h-full flex items-center mr-1">
<el-popover width="300" placement="top" trigger="hover">
<template #default>
对应参数<span class="font-black text-[#5A3DAA]"
>transport.proxyURL</span
>
</template>
<template #reference>
<IconifyIconOffline
class="text-base"
color="#5A3DAA"
icon="info"
/>
</template>
</el-popover>
</div>
代理地址
</template>
<el-input
v-model="formData.proxyConfigProxyUrl"
placeholder="http://user:pwd@192.168.1.128:8080"
/>
</el-form-item>
</el-col>
</template>
<el-col :span="24"> <el-col :span="24">
<div class="h2">Web 界面</div> <div class="h2">Web 界面</div>
@ -963,9 +1218,10 @@ onUnmounted(() => {
icon="info" icon="info"
/> />
</template> </template>
热更新等功能依赖于web界面<span class="font-black text-[#5A3DAA]" 热更新等功能依赖于web界面<span
>不可停用Web</span class="font-black text-[#5A3DAA]"
> >不可停用Web</span
>
</el-popover> </el-popover>
</div> </div>
启用Web 启用Web
@ -989,7 +1245,7 @@ onUnmounted(() => {
<template #default> <template #default>
对应参数<span class="font-black text-[#5A3DAA]" 对应参数<span class="font-black text-[#5A3DAA]"
>webServer.port</span >webServer.port</span
><br/> ><br />
自行保证端口没有被占用否则会导致启动失败 自行保证端口没有被占用否则会导致启动失败
</template> </template>
<template #reference> <template #reference>
@ -1009,6 +1265,7 @@ onUnmounted(() => {
:min="0" :min="0"
:max="65535" :max="65535"
controls-position="right" controls-position="right"
class="w-full"
></el-input-number> ></el-input-number>
</el-form-item> </el-form-item>
</el-col> </el-col>

6
types/global.d.ts vendored
View File

@ -88,6 +88,12 @@ declare global {
transportHeartbeatTimeout: number; transportHeartbeatTimeout: number;
webEnable: boolean; webEnable: boolean;
webPort: number; webPort: number;
transportProtocol: string;
transportDialServerTimeout: number;
transportDialServerKeepalive: number;
transportPoolCount: number;
transportTcpMux: boolean;
transportTcpMuxKeepaliveInterval: number;
}; };
type GitHubMirror = { type GitHubMirror = {