Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
78c87cc73f |
23
.github/ISSUE_TEMPLATE/bug_report.md
vendored
23
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,23 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**🖥️ 操作系统**
|
||||
|
||||
**🔖 Frpc-Desktop版本号**
|
||||
|
||||
**🤔 问题描述**
|
||||
|
||||
**🖼️ 错误截图**
|
||||
|
||||
**🔊 日志**
|
||||
|
||||
> 日志目录:
|
||||
* Window: `%APPDATA%\Frpc-Desktop\logs\`
|
||||
* MacOS: `~/Library/Logs/Frpc-Desktop/logs/`
|
||||
* Linux: `~/Library/Logs/Frpc-Desktop/`
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
28
.github/workflows/webpack.yml
vendored
28
.github/workflows/webpack.yml
vendored
@ -1,28 +0,0 @@
|
||||
name: NodeJS with Webpack
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18.8.0]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
npm install
|
||||
npx run release
|
23
.vscode/launch.json
vendored
23
.vscode/launch.json
vendored
@ -1,23 +0,0 @@
|
||||
{
|
||||
// 使用 IntelliSense 了解相关属性。
|
||||
// 悬停以查看现有属性的描述。
|
||||
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
{
|
||||
"type": "node-terminal",
|
||||
"name": "dev",
|
||||
"request": "launch",
|
||||
"command": "npm run dev",
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "node-terminal",
|
||||
"name": "build electron",
|
||||
"request": "launch",
|
||||
"command": "npm run build:electron",
|
||||
"cwd": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
77
README.md
77
README.md
@ -22,13 +22,9 @@
|
||||
</p>
|
||||
|
||||
<p><a href="https://jwinks.com/p/frp/#frp%E6%98%AF%E4%BB%80%E4%B9%88">使用教程</a></p>
|
||||
|
||||
<a href="https://trendshift.io/repositories/12489" target="_blank"><img src="https://trendshift.io/api/badge/repositories/12489" alt="luckjiawei%2Ffrpc-desktop | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||
<a href="https://hellogithub.com/repository/b0dc116e9f2e4b8188da5a6d3e1bd8a4" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=b0dc116e9f2e4b8188da5a6d3e1bd8a4&claim_uid=8ZMOhz30mGJAHpa" alt="Featured|HelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
</div>
|
||||
|
||||
## TODO
|
||||
|
||||
- [x] 开机自启动
|
||||
- [x] 适配多用户 user & meta_token
|
||||
- [x] 便携版
|
||||
@ -40,23 +36,14 @@
|
||||
- [x] 支持所有配置的导入导出
|
||||
- [x] 一键清空所有配置
|
||||
- [x] 支持导入识别frpc.toml
|
||||
- [x] tcp、udp协议支持批量端口
|
||||
- [ ] support multiple languages
|
||||
- [ ] tcp、udp协议支持批量端口
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Mac提示已损坏
|
||||
|
||||
执行命令:`sudo xattr -cr Frpc-Desktop.app`
|
||||
|
||||
## 里程碑
|
||||
|
||||
- 2025-01-09: 发布v1.1.6版本
|
||||
- 2024-12-04: 发布v1.1.5版本 优化体验、支持修改webport、解决github限流问题、日志优化
|
||||
- 2024-11-08: 发布v1.1.4版本 修复已知BUG
|
||||
- 2024-10-14: 发布v1.1.3版本 支持xtcp协议、优化体验
|
||||
- 2024-09-25: 发布v1.1.2版本 支持 http basic、子域名
|
||||
- 2024-09-07: 发布v1.1.0版本 支持批量端口、支持单条代理开关控制
|
||||
- 2024-08-24: 发布v1.0.9版本 支持镜像下载、导出导入配置
|
||||
- 2024-08-17: 发布v1.0.8版本 支持stcp代理
|
||||
- 2024-08-11: 发布v1.0.7版本
|
||||
@ -69,20 +56,14 @@
|
||||
- 2023-11-28: 发布v1.0版本
|
||||
|
||||
## 社区
|
||||
~~微信扫描加入开源项目交流群 广告勿进!!!~~
|
||||
|
||||
广告勿进!!!
|
||||
<img src="screenshots/wechat-qr.png" alt="二维码" width="200">
|
||||
|
||||
### TG
|
||||
**微信群超过200人无法扫码进群 关注公众号进群 **
|
||||
|
||||
[https://t.me/+4kziSBL3LxVmYzVl](https://t.me/+4kziSBL3LxVmYzVl)
|
||||
<img src="screenshots/mp_qr.jpg" alt="公众号二维码" width="200">
|
||||
|
||||
### 微信群
|
||||
|
||||
**~~微信扫描加入开源项目交流群~~ 微信群超过200人无法扫码进群 关注公众号进群**
|
||||
|
||||
|
||||
|
||||
<img src="screenshots/wechat-qr.png" alt="二维码" width="200"><img src="screenshots/mp_qr.jpg" alt="公众号二维码" width="200">
|
||||
|
||||
## 演示
|
||||
|
||||
@ -98,47 +79,11 @@
|
||||
|
||||

|
||||
|
||||
## 捐赠
|
||||
## 赞助
|
||||
|
||||
👉👉👉[点击去捐赠](https://jwinks.com/donate/)👈👈👈
|
||||
|
||||
**捐赠名单**
|
||||
|
||||
| 🕰 时间 | 📡 平台 | 🤲 捐赠者 | 💰 金额 | ✉️ 捐赠留言 |
|
||||
|------------|-------|---------------|---------|--------------------------|
|
||||
| 2024-08-06 | 微信 | 三木 | 1 元 | 无 |
|
||||
| 2024-08-25 | 微信 | 晚风 | 1 元 | 无 |
|
||||
| 2024-08-27 | 微信 | x | 1 元 | 无 |
|
||||
| 2024-10-09 | 微信 | 解脱 | 20 元 | 无 |
|
||||
| 2024-10-09 | 微信 | KMDN | 20 元 | 无 |
|
||||
| 2024-10-14 | 微信 | 121 | 5 元 | 无 |
|
||||
| 2024-10-14 | 微信 | Different | 10 元 | 感谢您的开源 |
|
||||
| 2024-10-16 | 微信 | 。 。 。 | 50 元 | 感谢开源的frp软件 |
|
||||
| 2024-11-2 | 微信 | gesoft | 10 元 | 加油 |
|
||||
| 2024-11-7 | 微信 | *进 | 10 元 | 谢谢,可见可得,省心省力 |
|
||||
| 2024-11-8 | 微信 | **创 | 10 元 | 无 |
|
||||
| 2024-11-20 | 微信 | 一東 | 20 元 | 请你喝咖啡 |
|
||||
| 2024-11-20 | 微信 | KEVINSKH | 10 元 | 感谢开发方便快捷的图形化操作界面👍 |
|
||||
| 2024-11-26 | 微信 | | 3 元 | 无 |
|
||||
| 2024-11-26 | 微信 | Kaori | 1 元 | 谢谢大佬的项目,要是能添加web控制页面就更好了 |
|
||||
| 2024-12-03 | 微信 | 17¥ | 20 元 | 谢谢,很方便的软件 |
|
||||
| 2024-12-03 | 微信 | Cr@k3r | 5 元 | 感谢你的工作 |
|
||||
| 2024-12-09 | 微信 | Vince | 20 元 | 支持国人开发! |
|
||||
| 2024-12-11 | 支付宝 | **萌 | 20 元 | 加油加油 |
|
||||
| 2024-12-11 | 支付宝 | *石 | 20 元 | 无 |
|
||||
| 2024-12-16 | 微信 | 铁汉柔情 | 1 元 | 加油支持国人 |
|
||||
| 2024-12-16 | 微信 | 亚索🌪️ | 1 元 | 无 |
|
||||
| 2024-12-17 | 微信 | ppp789 | 1.6 元 | 无 |
|
||||
| 2024-12-17 | 支付宝 | *涛 | 10 元 | 无 |
|
||||
| 2024-12-18 | 微信 | 觉远 | 6.66 元 | 开源不易 |
|
||||
| 2024-12-19 | 微信 | 官方提醒 | 1 元 | 无 |
|
||||
| 2024-12-19 | 微信 | 木~易 | 6.66 元 | 加油 |
|
||||
| 2025-01-06 | 微信 | 如是 | 2 元 | 支持开源 |
|
||||
| 2025-01-13 | 微信 | David Veith | 18.88 元 | 开源无价,么么哒 |
|
||||
| 2025-01-14 | 微信 | Xterminal SSH | 199 元 | Xterminal SSH 客户端前来支援 |
|
||||
<img src="screenshots/gratuity.jpg" alt="赞助二维码" width="200">
|
||||
|
||||
## 贡献者
|
||||
|
||||
<a href="https://github.com/luckjiawei/frpc-desktop/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=luckjiawei/frpc-desktop" />
|
||||
</a>
|
||||
@ -151,19 +96,11 @@
|
||||
|
||||
[](https://star-history.com/#luckjiawei/frpc-desktop&Date)
|
||||
<!-- MARKDOWN LINKS & IMAGES -->
|
||||
|
||||
[forks-shield]: https://img.shields.io/github/forks/luckjiawei/frpc-desktop.svg?style=for-the-badge
|
||||
|
||||
[forks-url]: https://github.com/luckjiawei/frpc-desktop/network/members
|
||||
|
||||
[stars-shield]: https://img.shields.io/github/stars/luckjiawei/frpc-desktop.svg?style=for-the-badge
|
||||
|
||||
[stars-url]: https://github.com/luckjiawei/frpc-desktop/stargazers
|
||||
|
||||
[issues-shield]: https://img.shields.io/github/issues/luckjiawei/frpc-desktop.svg?style=for-the-badge
|
||||
|
||||
[issues-url]: https://github.com/luckjiawei/frpc-desktop/issues
|
||||
|
||||
[license-shield]: https://img.shields.io/github/license/luckjiawei/frpc-desktop.svg?style=for-the-badge
|
||||
|
||||
[license-url]: https://github.com/luckjiawei/frpc-desktop/blob/master/LICENSE
|
||||
|
@ -1,11 +0,0 @@
|
||||
https://jwinks.com/p/frpc-desktop113
|
||||
https://jwinks.com/p/free-jsaqy
|
||||
https://jwinks.com/p/frpc-desktop110
|
||||
https://jwinks.com/p/frpc-desktop109
|
||||
https://jwinks.com/p/nginx-proxy-manager
|
||||
https://jwinks.com/p/frp
|
||||
https://jwinks.com/p/acme
|
||||
https://jwinks.com/archives
|
||||
https://jwinks.com/search
|
||||
https://jwinks.com/%E9%93%BE%E6%8E%A5
|
||||
https://jwinks.com/donate
|
@ -1,26 +1,16 @@
|
||||
import { app, ipcMain, shell } from "electron";
|
||||
import { logError, logInfo, LogModule, logWarn } from "../utils/log";
|
||||
import log from "electron-log";
|
||||
|
||||
export const initCommonApi = () => {
|
||||
// 打开链接
|
||||
ipcMain.on("common.openUrl", async (event, args) => {
|
||||
if (args) {
|
||||
logInfo(LogModule.APP, `Attempting to open URL: ${args}`);
|
||||
try {
|
||||
await shell.openExternal(args);
|
||||
logInfo(LogModule.APP, `Successfully opened URL: ${args}`);
|
||||
} catch (error) {
|
||||
logError(
|
||||
LogModule.APP,
|
||||
`Failed to open URL: ${args}. Error: ${error.message}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
logWarn(LogModule.APP, "No URL provided to open.");
|
||||
log.info(`打开链接:${args}`);
|
||||
shell.openExternal(args).then(() => {});
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on("common.relaunch", () => {
|
||||
logInfo(LogModule.APP, "Application is relaunching.");
|
||||
app.relaunch();
|
||||
app.quit();
|
||||
});
|
||||
|
@ -1,29 +1,31 @@
|
||||
import { app, dialog, ipcMain, shell } from "electron";
|
||||
import { app, dialog, ipcMain } from "electron";
|
||||
import { clearConfig, getConfig, saveConfig } from "../storage/config";
|
||||
import { clearVersion, listVersion } from "../storage/version";
|
||||
import { genIniConfig, genTomlConfig, stopFrpcProcess } from "./frpc";
|
||||
import {
|
||||
generateConfig,
|
||||
genIniConfig,
|
||||
genTomlConfig,
|
||||
stopFrpcProcess
|
||||
} from "./frpc";
|
||||
import { exec } from "child_process";
|
||||
import { clearProxy, insertProxy, listProxy } from "../storage/proxy";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import { logDebug, logError, logInfo, LogModule, logWarn } from "../utils/log";
|
||||
|
||||
const log = require("electron-log");
|
||||
const toml = require("@iarna/toml");
|
||||
const { v4: uuidv4 } = require("uuid");
|
||||
|
||||
export const initConfigApi = win => {
|
||||
ipcMain.on("config.saveConfig", async (event, args) => {
|
||||
logInfo(LogModule.APP, "Attempting to save configuration.");
|
||||
saveConfig(args, (err, numberOfUpdated, upsert) => {
|
||||
if (!err) {
|
||||
const start = args.systemSelfStart || false;
|
||||
logDebug(LogModule.APP, "Startup status set to: " + start);
|
||||
log.info("开启自启状态", start);
|
||||
app.setLoginItemSettings({
|
||||
openAtLogin: start, //win
|
||||
openAsHidden: start //macOs
|
||||
});
|
||||
logInfo(LogModule.APP, "Configuration saved successfully.");
|
||||
} else {
|
||||
logError(LogModule.APP, `Error saving configuration: ${err}`);
|
||||
}
|
||||
event.reply("Config.saveConfig.hook", {
|
||||
err: err,
|
||||
@ -34,11 +36,7 @@ export const initConfigApi = win => {
|
||||
});
|
||||
|
||||
ipcMain.on("config.getConfig", async (event, args) => {
|
||||
logInfo(LogModule.APP, "Requesting configuration.");
|
||||
getConfig((err, doc) => {
|
||||
if (err) {
|
||||
logError(LogModule.APP, `Error retrieving configuration: ${err}`);
|
||||
}
|
||||
event.reply("Config.getConfig.hook", {
|
||||
err: err,
|
||||
data: doc
|
||||
@ -47,11 +45,7 @@ export const initConfigApi = win => {
|
||||
});
|
||||
|
||||
ipcMain.on("config.versions", event => {
|
||||
logInfo(LogModule.APP, "Requesting version information.");
|
||||
listVersion((err, doc) => {
|
||||
if (err) {
|
||||
logError(LogModule.APP, `Error retrieving version information: ${err}`);
|
||||
}
|
||||
event.reply("Config.versions.hook", {
|
||||
err: err,
|
||||
data: doc
|
||||
@ -60,11 +54,7 @@ export const initConfigApi = win => {
|
||||
});
|
||||
|
||||
ipcMain.on("config.hasConfig", event => {
|
||||
logInfo(LogModule.APP, "Checking if configuration exists.");
|
||||
getConfig((err, doc) => {
|
||||
if (err) {
|
||||
logError(LogModule.APP, `Error checking configuration: ${err}`);
|
||||
}
|
||||
event.reply("Config.getConfig.hook", {
|
||||
err: err,
|
||||
data: doc
|
||||
@ -73,20 +63,15 @@ export const initConfigApi = win => {
|
||||
});
|
||||
|
||||
ipcMain.on("config.exportConfig", async (event, args) => {
|
||||
logInfo(LogModule.APP, "Attempting to export configuration.");
|
||||
const result = await dialog.showOpenDialog({
|
||||
properties: ["openDirectory"]
|
||||
});
|
||||
const outputDirectory = result.filePaths[0];
|
||||
if (!outputDirectory) {
|
||||
logWarn(LogModule.APP, "Export canceled by user.");
|
||||
// 取消了
|
||||
return;
|
||||
}
|
||||
|
||||
logInfo(
|
||||
LogModule.APP,
|
||||
`Exporting configuration to directory ${outputDirectory} with type: ${args}`
|
||||
);
|
||||
log.info(`导出目录 ${outputDirectory} 类型:${args}`);
|
||||
getConfig((err1, config) => {
|
||||
if (!err1 && config) {
|
||||
listProxy((err2, proxys) => {
|
||||
@ -106,44 +91,33 @@ export const initConfigApi = win => {
|
||||
configContent, // 配置文件内容
|
||||
{ flag: "w" },
|
||||
err => {
|
||||
if (err) {
|
||||
logError(
|
||||
LogModule.APP,
|
||||
`Error writing configuration file: ${err}`
|
||||
);
|
||||
if (!err) {
|
||||
// callback(filename);
|
||||
event.reply("config.exportConfig.hook", {
|
||||
data: "导出错误",
|
||||
err: err
|
||||
});
|
||||
} else {
|
||||
logInfo(
|
||||
LogModule.APP,
|
||||
"Configuration exported successfully."
|
||||
);
|
||||
event.reply("Config.exportConfig.hook", {
|
||||
data: {
|
||||
configPath: configPath
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
logError(LogModule.APP, `Error listing proxies: ${err2}`);
|
||||
event.reply("Config.exportConfig.hook", {
|
||||
data: {
|
||||
configPath: configPath
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
logError(LogModule.APP, `Error retrieving configuration: ${err1}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const parseTomlConfig = (tomlPath: string) => {
|
||||
logInfo(LogModule.APP, `Parsing TOML configuration from ${tomlPath}`);
|
||||
const importConfigPath = tomlPath;
|
||||
const tomlData = fs.readFileSync(importConfigPath, "utf-8");
|
||||
logInfo(LogModule.APP, "Configuration content read successfully.");
|
||||
log.info(`读取到配置内容 ${tomlData}`);
|
||||
const sourceConfig = toml.parse(tomlData);
|
||||
// log.info(`解析结果 ${sourceConfig}`);
|
||||
// console.log(sourceConfig, "frpcConfig");
|
||||
// 解析config
|
||||
const targetConfig: FrpConfig = {
|
||||
currentVersion: null,
|
||||
@ -169,19 +143,7 @@ export const initConfigApi = win => {
|
||||
user: sourceConfig?.user || "",
|
||||
metaToken: sourceConfig?.metadatas?.token || "",
|
||||
systemSelfStart: false,
|
||||
systemStartupConnect: false,
|
||||
systemSilentStartup: false,
|
||||
webEnable: true,
|
||||
webPort: sourceConfig?.webServer?.port || 57400,
|
||||
transportProtocol: sourceConfig?.transport?.protocol || "tcp",
|
||||
transportDialServerTimeout:
|
||||
sourceConfig?.transport?.dialServerTimeout || 10,
|
||||
transportDialServerKeepalive:
|
||||
sourceConfig?.transport?.dialServerKeepalive || 70,
|
||||
transportPoolCount: sourceConfig?.transport?.poolCount || 0,
|
||||
transportTcpMux: sourceConfig?.transport?.tcpMux || true,
|
||||
transportTcpMuxKeepaliveInterval:
|
||||
sourceConfig?.transport?.tcpMuxKeepaliveInterval || 7200
|
||||
systemStartupConnect: false
|
||||
};
|
||||
let frpcProxys = [];
|
||||
// 解析proxy
|
||||
@ -195,28 +157,17 @@ export const initConfigApi = win => {
|
||||
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,
|
||||
keepTunnelOpen: m?.keepTunnelOpen || false,
|
||||
https2http: m?.https2http || false,
|
||||
https2httpCaFile: m?.https2httpCaFile || "",
|
||||
https2httpKeyFile: m?.https2httpKeyFile || ""
|
||||
status: m?.status || true
|
||||
};
|
||||
return rm;
|
||||
});
|
||||
frpcProxys = [...frpcProxys, ...frpcProxys1];
|
||||
logInfo(LogModule.APP, "Parsed proxies from configuration.");
|
||||
}
|
||||
// 解析stcp的访问者
|
||||
if (sourceConfig?.visitors && sourceConfig.visitors.length > 0) {
|
||||
@ -229,45 +180,28 @@ export const initConfigApi = win => {
|
||||
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,
|
||||
keepTunnelOpen: m?.keepTunnelOpen || false,
|
||||
https2http: m?.https2http || false,
|
||||
https2httpCaFile: m?.https2httpCaFile || "",
|
||||
https2httpKeyFile: m?.https2httpKeyFile || ""
|
||||
status: m?.status || true
|
||||
};
|
||||
return rm;
|
||||
});
|
||||
frpcProxys = [...frpcProxys, ...frpcProxys2];
|
||||
logInfo(LogModule.APP, "Parsed visitors from configuration.");
|
||||
}
|
||||
if (targetConfig) {
|
||||
clearConfig(() => {
|
||||
logInfo(LogModule.APP, "Clearing existing configuration.");
|
||||
saveConfig(targetConfig);
|
||||
logInfo(LogModule.APP, "New configuration saved.");
|
||||
});
|
||||
}
|
||||
if (frpcProxys && frpcProxys.length > 0) {
|
||||
clearProxy(() => {
|
||||
frpcProxys.forEach(f => {
|
||||
insertProxy(f, err => {
|
||||
if (err) {
|
||||
logError(LogModule.APP, `Error inserting proxy: ${err}`);
|
||||
} else {
|
||||
logInfo(LogModule.APP, `Inserted proxy: ${JSON.stringify(f)}`);
|
||||
}
|
||||
console.log("插入", f, err);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -275,7 +209,6 @@ export const initConfigApi = win => {
|
||||
};
|
||||
|
||||
ipcMain.on("config.importConfig", async (event, args) => {
|
||||
logInfo(LogModule.APP, "Attempting to import configuration.");
|
||||
const result = await dialog.showOpenDialog(win, {
|
||||
properties: ["openFile"],
|
||||
filters: [
|
||||
@ -283,25 +216,17 @@ export const initConfigApi = win => {
|
||||
]
|
||||
});
|
||||
if (result.canceled) {
|
||||
logWarn(LogModule.APP, "Import canceled by user.");
|
||||
return;
|
||||
} else {
|
||||
const filePath = result.filePaths[0];
|
||||
const fileExtension = path.extname(filePath); // 获取文件后缀名
|
||||
logWarn(
|
||||
LogModule.APP,
|
||||
`Importing file ${filePath} with extension ${fileExtension}`
|
||||
);
|
||||
log.info(`导入文件 ${filePath} ${fileExtension}`);
|
||||
if (fileExtension === ".toml") {
|
||||
parseTomlConfig(filePath);
|
||||
event.reply("Config.importConfig.hook", {
|
||||
success: true
|
||||
});
|
||||
} else {
|
||||
logError(
|
||||
LogModule.APP,
|
||||
`Import failed, unsupported file format: ${fileExtension}`
|
||||
);
|
||||
event.reply("Config.importConfig.hook", {
|
||||
success: false,
|
||||
data: `导入失败,暂不支持 ${fileExtension} 格式文件`
|
||||
@ -311,27 +236,11 @@ export const initConfigApi = win => {
|
||||
});
|
||||
|
||||
ipcMain.on("config.clearAll", async (event, args) => {
|
||||
logInfo(LogModule.APP, "Clearing all configurations.");
|
||||
stopFrpcProcess(() => {
|
||||
clearConfig();
|
||||
clearProxy();
|
||||
clearVersion();
|
||||
event.reply("Config.clearAll.hook", {});
|
||||
logInfo(LogModule.APP, "All configurations cleared.");
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.on("config.openDataFolder", async (event, args) => {
|
||||
const userDataPath = app.getPath("userData");
|
||||
logInfo(LogModule.APP, "Attempting to open data folder.");
|
||||
shell.openPath(userDataPath).then(errorMessage => {
|
||||
if (errorMessage) {
|
||||
logError(LogModule.APP, `Failed to open data folder: ${errorMessage}`);
|
||||
event.reply("Config.openDataFolder.hook", false);
|
||||
} else {
|
||||
logInfo(LogModule.APP, "Data folder opened successfully.");
|
||||
event.reply("Config.openDataFolder.hook", true);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@ -1,21 +1,13 @@
|
||||
import {dialog, ipcMain} from "electron";
|
||||
import { logInfo, logError, LogModule } from "../utils/log";
|
||||
|
||||
export const initFileApi = () => {
|
||||
ipcMain.handle("file.selectFile", async (event, args) => {
|
||||
logInfo(LogModule.APP, `Attempting to open file dialog with filters: ${JSON.stringify(args)}`);
|
||||
try {
|
||||
const result = dialog.showOpenDialogSync({
|
||||
properties: ['openFile'],
|
||||
filters: [
|
||||
{ name: 'Text Files', extensions: args },
|
||||
]
|
||||
});
|
||||
logInfo(LogModule.APP, `File dialog result: ${JSON.stringify(result)}`);
|
||||
return result;
|
||||
} catch (error) {
|
||||
logError(LogModule.APP, `Error opening file dialog: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
const result = dialog.showOpenDialogSync({
|
||||
properties: ['openFile'],
|
||||
filters: [
|
||||
{name: 'Text Files', extensions: args},
|
||||
]
|
||||
})
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
@ -3,12 +3,11 @@ import { getConfig } from "../storage/config";
|
||||
import { listProxy } from "../storage/proxy";
|
||||
import { getVersionById } from "../storage/version";
|
||||
import treeKill from "tree-kill";
|
||||
import { logInfo, logError, LogModule, logDebug, logWarn } from "../utils/log";
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { exec, spawn } = require("child_process");
|
||||
|
||||
const log = require("electron-log");
|
||||
export let frpcProcess = null;
|
||||
const runningCmd = {
|
||||
commandPath: null,
|
||||
@ -16,6 +15,11 @@ const runningCmd = {
|
||||
};
|
||||
let frpcStatusListener = null;
|
||||
|
||||
/**
|
||||
* 获取选择版本的工作目录
|
||||
* @param versionId 版本ID
|
||||
* @param callback
|
||||
*/
|
||||
const getFrpcVersionWorkerPath = (
|
||||
versionId: number,
|
||||
callback: (workerPath: string) => void
|
||||
@ -29,14 +33,6 @@ const getFrpcVersionWorkerPath = (
|
||||
});
|
||||
};
|
||||
|
||||
const isRangePort = (m: Proxy) => {
|
||||
return (
|
||||
(m.type === "tcp" || m.type === "udp") &&
|
||||
(String(m.localPort).indexOf("-") !== -1 ||
|
||||
String(m.localPort).indexOf(",") !== -1)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 生成toml配置文件
|
||||
* @param config
|
||||
@ -44,180 +40,133 @@ const isRangePort = (m: Proxy) => {
|
||||
*/
|
||||
export const genTomlConfig = (config: FrpConfig, proxys: Proxy[]) => {
|
||||
const proxyToml = proxys.map(m => {
|
||||
const rangePort = isRangePort(m);
|
||||
config.tlsConfigKeyFile = config.tlsConfigKeyFile.replace(/\\/g, "\\\\");
|
||||
config.tlsConfigCertFile = config.tlsConfigCertFile.replace(/\\/g, "\\\\");
|
||||
config.tlsConfigTrustedCaFile = config.tlsConfigTrustedCaFile.replace(
|
||||
/\\/g,
|
||||
"\\\\"
|
||||
);
|
||||
let toml = `${
|
||||
rangePort
|
||||
? `{{- range $_, $v := parseNumberRangePair "${m.localPort}" "${m.remotePort}" }}`
|
||||
: ""
|
||||
}
|
||||
[[${
|
||||
(m.type === "stcp" || m.type === "xtcp" || m.type === "sudp") &&
|
||||
m.stcpModel === "visitors"
|
||||
? "visitors"
|
||||
: "proxies"
|
||||
}]]
|
||||
${rangePort ? "" : `name = "${m.name}"`}
|
||||
type = "${m.type}"\n`;
|
||||
|
||||
let toml = `
|
||||
[[${m.type === "stcp" && m.stcpModel === "visitors" ? "visitors" : "proxies"}]]
|
||||
name = "${m.name}"
|
||||
type = "${m.type}"
|
||||
`;
|
||||
switch (m.type) {
|
||||
case "tcp":
|
||||
case "udp":
|
||||
if (rangePort) {
|
||||
toml += `name = "${m.name}-{{ $v.First }}"
|
||||
localPort = {{ $v.First }}
|
||||
remotePort = {{ $v.Second }}\n`;
|
||||
} else {
|
||||
toml += `localIP = "${m.localIp}"
|
||||
toml += `
|
||||
localIP = "${m.localIp}"
|
||||
localPort = ${m.localPort}
|
||||
remotePort = ${m.remotePort}\n`;
|
||||
}
|
||||
remotePort = ${m.remotePort}
|
||||
`;
|
||||
break;
|
||||
case "http":
|
||||
case "https":
|
||||
const customDomains = m.customDomains.filter(f1 => f1 !== "");
|
||||
if (customDomains && customDomains.length > 0) {
|
||||
toml += `customDomains=[${m.customDomains.map(m => `"${m}"`)}]\n`;
|
||||
}
|
||||
if (m.subdomain) {
|
||||
toml += `subdomain="${m.subdomain}"\n`;
|
||||
}
|
||||
if (m.basicAuth) {
|
||||
toml += `httpUser = "${m.httpUser}"
|
||||
httpPassword = "${m.httpPassword}"\n`;
|
||||
}
|
||||
if (m.https2http) {
|
||||
toml += `[proxies.plugin]
|
||||
type = "https2http"
|
||||
localAddr = "${m.localIp}:${m.localPort}"
|
||||
|
||||
crtPath = "${m.https2httpCaFile}"
|
||||
keyPath = "${m.https2httpKeyFile}"\n`;
|
||||
} else {
|
||||
toml += `localIP = "${m.localIp}"
|
||||
localPort = ${m.localPort}\n`;
|
||||
}
|
||||
|
||||
toml += `
|
||||
localIP = "${m.localIp}"
|
||||
localPort = ${m.localPort}
|
||||
customDomains=[${m.customDomains.map(m => `"${m}"`)}]
|
||||
`;
|
||||
break;
|
||||
case "xtcp":
|
||||
if (m.stcpModel === "visitors") {
|
||||
toml += `keepTunnelOpen = ${m.keepTunnelOpen}\n`;
|
||||
}
|
||||
case "stcp":
|
||||
case "sudp":
|
||||
if (m.stcpModel === "visitors") {
|
||||
// 访问者
|
||||
toml += `serverName = "${m.serverName}"
|
||||
toml += `
|
||||
serverName = "${m.serverName}"
|
||||
bindAddr = "${m.bindAddr}"
|
||||
bindPort = ${m.bindPort}\n`;
|
||||
if (m.fallbackTo) {
|
||||
toml += `fallbackTo = "${m.fallbackTo}"
|
||||
fallbackTimeoutMs = ${m.fallbackTimeoutMs || 500}\n`;
|
||||
}
|
||||
bindPort = ${m.bindPort}
|
||||
`;
|
||||
} else if (m.stcpModel === "visited") {
|
||||
// 被访问者
|
||||
toml += `localIP = "${m.localIp}"
|
||||
localPort = ${m.localPort}\n`;
|
||||
toml += `
|
||||
localIP = "${m.localIp}"
|
||||
localPort = ${m.localPort}`;
|
||||
}
|
||||
|
||||
toml += `secretKey="${m.secretKey}"\n`;
|
||||
toml += `
|
||||
secretKey="${m.secretKey}"
|
||||
`;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (rangePort) {
|
||||
toml += `{{- end }}\n`;
|
||||
}
|
||||
return toml;
|
||||
});
|
||||
const toml = `serverAddr = "${config.serverAddr}"
|
||||
const toml = `
|
||||
serverAddr = "${config.serverAddr}"
|
||||
serverPort = ${config.serverPort}
|
||||
${
|
||||
config.authMethod === "token"
|
||||
? `auth.method = "token"
|
||||
auth.token = "${config.authToken}"`
|
||||
? `
|
||||
auth.method = "token"
|
||||
auth.token = "${config.authToken}"
|
||||
`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
config.authMethod === "multiuser"
|
||||
? `user = "${config.user}"
|
||||
metadatas.token = "${config.metaToken}"`
|
||||
: ""
|
||||
}
|
||||
log.to = "frpc.log"
|
||||
log.level = "${config.logLevel}"
|
||||
log.maxDays = ${config.logMaxDays}
|
||||
webServer.addr = "127.0.0.1"
|
||||
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}`
|
||||
? `
|
||||
user = "${config.user}"
|
||||
metadatas.token = "${config.metaToken}"
|
||||
`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
config.transportHeartbeatInterval
|
||||
? `transport.heartbeatInterval = ${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.heartbeatTimeout = ${config.transportHeartbeatTimeout}
|
||||
`
|
||||
: ""
|
||||
}
|
||||
|
||||
|
||||
log.to = "frpc.log"
|
||||
log.level = "${config.logLevel}"
|
||||
log.maxDays = ${config.logMaxDays}
|
||||
webServer.addr = "127.0.0.1"
|
||||
webServer.port = 57400
|
||||
transport.tls.enable = ${config.tlsConfigEnable}
|
||||
${
|
||||
config.tlsConfigEnable && config.tlsConfigCertFile
|
||||
? `transport.tls.certFile = "${config.tlsConfigCertFile}"`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
config.tlsConfigEnable && config.tlsConfigKeyFile
|
||||
? `transport.tls.keyFile = "${config.tlsConfigKeyFile}"`
|
||||
? `
|
||||
transport.tls.certFile = "${config.tlsConfigCertFile}"
|
||||
`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
config.tlsConfigEnable && config.tlsConfigKeyFile
|
||||
? `
|
||||
transport.tls.keyFile = "${config.tlsConfigKeyFile}"
|
||||
`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
config.tlsConfigEnable && config.tlsConfigTrustedCaFile
|
||||
? `transport.tls.trustedCaFile = "${config.tlsConfigTrustedCaFile}"`
|
||||
? `
|
||||
transport.tls.trustedCaFile = "${config.tlsConfigTrustedCaFile}"
|
||||
`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
config.tlsConfigEnable && config.tlsConfigServerName
|
||||
? `transport.tls.serverName = "${config.tlsConfigServerName}"`
|
||||
? `
|
||||
transport.tls.serverName = "${config.tlsConfigServerName}"
|
||||
`
|
||||
: ""
|
||||
}
|
||||
|
||||
|
||||
${
|
||||
config.proxyConfigEnable
|
||||
? `transport.proxyURL = "${config.proxyConfigProxyUrl}"`
|
||||
? `
|
||||
transport.proxyURL = "${config.proxyConfigProxyUrl}"
|
||||
`
|
||||
: ""
|
||||
}
|
||||
${proxyToml.join("")}`;
|
||||
|
||||
${proxyToml.join("")}
|
||||
`;
|
||||
return toml;
|
||||
};
|
||||
|
||||
@ -228,8 +177,8 @@ ${proxyToml.join("")}`;
|
||||
*/
|
||||
export const genIniConfig = (config: FrpConfig, proxys: Proxy[]) => {
|
||||
const proxyIni = proxys.map(m => {
|
||||
const rangePort = isRangePort(m);
|
||||
let ini = `[${rangePort ? "range:" : ""}${m.name}]
|
||||
let ini = `
|
||||
[${m.name}]
|
||||
type = "${m.type}"
|
||||
`;
|
||||
switch (m.type) {
|
||||
@ -238,67 +187,35 @@ type = "${m.type}"
|
||||
ini += `
|
||||
local_ip = "${m.localIp}"
|
||||
local_port = ${m.localPort}
|
||||
remote_port = ${m.remotePort}\n`;
|
||||
remote_port = ${m.remotePort}
|
||||
`;
|
||||
break;
|
||||
case "http":
|
||||
ini += `
|
||||
local_ip = "${m.localIp}"
|
||||
local_port = ${m.localPort}
|
||||
custom_domains=[${m.customDomains.map(m => `${m}`)}]
|
||||
subdomain="${m.subdomain}"\n`;
|
||||
if (m.basicAuth) {
|
||||
ini += `
|
||||
httpUser = "${m.httpUser}"
|
||||
httpPassword = "${m.httpPassword}"\n`;
|
||||
}
|
||||
break;
|
||||
case "https":
|
||||
ini += `
|
||||
custom_domains=[${m.customDomains.map(m => `${m}`)}]
|
||||
subdomain="${m.subdomain}"\n`;
|
||||
if (m.basicAuth) {
|
||||
ini += `
|
||||
httpUser = "${m.httpUser}"
|
||||
httpPassword = "${m.httpPassword}"\n`;
|
||||
}
|
||||
if (m.https2http) {
|
||||
ini += `
|
||||
plugin = https2http
|
||||
plugin_local_addr = ${m.localIp}:${m.localPort}
|
||||
plugin_crt_path = ${m.https2httpCaFile}
|
||||
plugin_key_path = ${m.https2httpKeyFile}\n`;
|
||||
} else {
|
||||
ini += `
|
||||
local_ip = "${m.localIp}"
|
||||
local_port = ${m.localPort}\n`;
|
||||
}
|
||||
local_port = ${m.localPort}
|
||||
custom_domains=[${m.customDomains.map(m => `"${m}"`)}]
|
||||
`;
|
||||
break;
|
||||
case "xtcp":
|
||||
if (m.stcpModel === "visitors") {
|
||||
ini += `keep_tunnel_open = ${m.keepTunnelOpen}\n`;
|
||||
}
|
||||
case "stcp":
|
||||
case "sudp":
|
||||
if (m.stcpModel === "visitors") {
|
||||
// 访问者
|
||||
ini += `
|
||||
role = visitor
|
||||
server_name = "${m.serverName}"
|
||||
bind_addr = "${m.bindAddr}"
|
||||
bind_port = ${m.bindPort}\n`;
|
||||
if (m.fallbackTo) {
|
||||
ini += `
|
||||
fallback_to = ${m.fallbackTo}
|
||||
fallback_timeout_ms = ${m.fallbackTimeoutMs || 500}\n`;
|
||||
}
|
||||
bind_port = ${m.bindPort}
|
||||
`;
|
||||
} else if (m.stcpModel === "visited") {
|
||||
// 被访问者
|
||||
ini += `
|
||||
local_ip = "${m.localIp}"
|
||||
local_port = ${m.localPort}\n`;
|
||||
local_port = ${m.localPort}`;
|
||||
}
|
||||
ini += `
|
||||
sk="${m.secretKey}"\n`;
|
||||
sk="${m.secretKey}"
|
||||
`;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -314,29 +231,19 @@ ${
|
||||
config.authMethod === "token"
|
||||
? `
|
||||
authentication_method = ${config.authMethod}
|
||||
token = ${config.authToken}\n`
|
||||
token = ${config.authToken}
|
||||
`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
config.authMethod === "multiuser"
|
||||
? `
|
||||
user = ${config.user}
|
||||
meta_token = ${config.metaToken}\n`
|
||||
meta_token = ${config.metaToken}
|
||||
`
|
||||
: ""
|
||||
}
|
||||
|
||||
${config.transportProtocol ? `protocol = ${config.transportProtocol}` : ""}
|
||||
${config.transportPoolCount ? `pool_count = ${config.transportPoolCount}` : ""}
|
||||
${
|
||||
config.transportDialServerTimeout
|
||||
? `dial_server_timeout = ${config.transportDialServerTimeout}`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
config.transportDialServerKeepalive
|
||||
? `dial_server_keepalive = ${config.transportDialServerKeepalive}`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
config.transportHeartbeatInterval
|
||||
? `
|
||||
@ -351,51 +258,50 @@ heartbeat_timeout = ${config.transportHeartbeatTimeout}
|
||||
`
|
||||
: ""
|
||||
}
|
||||
${config.transportTcpMux ? `transport.tcp_mux = ${config.transportTcpMux}` : ""}
|
||||
${
|
||||
config.transportTcpMux && config.transportTcpMuxKeepaliveInterval
|
||||
? `tcp_mux_keepalive_interval = ${config.transportTcpMuxKeepaliveInterval}`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
config.tlsConfigEnable && config.tlsConfigCertFile
|
||||
? `
|
||||
tls_cert_file = ${config.tlsConfigCertFile}\n`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
config.tlsConfigEnable && config.tlsConfigKeyFile
|
||||
? `
|
||||
tls_key_file = ${config.tlsConfigKeyFile}\n`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
config.tlsConfigEnable && config.tlsConfigTrustedCaFile
|
||||
? `
|
||||
tls_trusted_ca_file = ${config.tlsConfigTrustedCaFile}\n`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
config.tlsConfigEnable && config.tlsConfigServerName
|
||||
? `
|
||||
tls_server_name = ${config.tlsConfigServerName}\n`
|
||||
: ""
|
||||
}
|
||||
|
||||
${
|
||||
config.proxyConfigEnable
|
||||
? `
|
||||
http_proxy = "${config.proxyConfigProxyUrl}"\n`
|
||||
: ""
|
||||
}
|
||||
|
||||
log_file = "frpc.log"
|
||||
log_level = ${config.logLevel}
|
||||
log_max_days = ${config.logMaxDays}
|
||||
admin_addr = 127.0.0.1
|
||||
admin_port = ${config.webPort}
|
||||
admin_port = 57400
|
||||
tls_enable = ${config.tlsConfigEnable}
|
||||
|
||||
${
|
||||
config.tlsConfigEnable && config.tlsConfigCertFile
|
||||
? `
|
||||
tls_cert_file = ${config.tlsConfigCertFile}
|
||||
`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
config.tlsConfigEnable && config.tlsConfigKeyFile
|
||||
? `
|
||||
tls_key_file = ${config.tlsConfigKeyFile}
|
||||
`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
config.tlsConfigEnable && config.tlsConfigTrustedCaFile
|
||||
? `
|
||||
tls_trusted_ca_file = ${config.tlsConfigTrustedCaFile}
|
||||
`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
config.tlsConfigEnable && config.tlsConfigServerName
|
||||
? `
|
||||
tls_server_name = ${config.tlsConfigServerName}
|
||||
`
|
||||
: ""
|
||||
}
|
||||
|
||||
${
|
||||
config.proxyConfigEnable
|
||||
? `
|
||||
http_proxy = "${config.proxyConfigProxyUrl}"
|
||||
`
|
||||
: ""
|
||||
}
|
||||
|
||||
${proxyIni.join("")}
|
||||
`;
|
||||
@ -410,65 +316,39 @@ export const generateConfig = (
|
||||
callback: (configPath: string) => void
|
||||
) => {
|
||||
listProxy((err3, proxys) => {
|
||||
if (err3) {
|
||||
logError(LogModule.FRP_CLIENT, `Failed to list proxies: ${err3.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const { currentVersion } = config;
|
||||
let filename = null;
|
||||
let configContent = "";
|
||||
const filtered = proxys
|
||||
.map(m => {
|
||||
if (m.status == null || m.status == undefined) {
|
||||
m.status = true;
|
||||
}
|
||||
return m;
|
||||
})
|
||||
.filter(f => f.status);
|
||||
|
||||
if (currentVersion < 124395282) {
|
||||
// 版本小于v0.52.0
|
||||
filename = "frp.ini";
|
||||
configContent = genIniConfig(config, filtered);
|
||||
logInfo(
|
||||
LogModule.FRP_CLIENT,
|
||||
`Using INI format for configuration: ${filename}`
|
||||
);
|
||||
} else {
|
||||
filename = "frp.toml";
|
||||
configContent = genTomlConfig(config, filtered);
|
||||
logInfo(
|
||||
LogModule.FRP_CLIENT,
|
||||
`Using TOML format for configuration: ${filename}`
|
||||
);
|
||||
}
|
||||
|
||||
const configPath = path.join(app.getPath("userData"), filename);
|
||||
logInfo(
|
||||
LogModule.FRP_CLIENT,
|
||||
`Writing configuration to file: ${configPath}`
|
||||
);
|
||||
|
||||
fs.writeFile(
|
||||
configPath, // 配置文件目录
|
||||
configContent, // 配置文件内容
|
||||
{ flag: "w" },
|
||||
err => {
|
||||
if (err) {
|
||||
logError(
|
||||
LogModule.FRP_CLIENT,
|
||||
`Failed to write configuration file: ${err.message}`
|
||||
);
|
||||
} else {
|
||||
logInfo(
|
||||
LogModule.FRP_CLIENT,
|
||||
`Configuration file written successfully: ${filename}`
|
||||
);
|
||||
callback(filename);
|
||||
}
|
||||
if (!err3) {
|
||||
const { currentVersion } = config;
|
||||
let filename = null;
|
||||
let configContent = "";
|
||||
const filtered = proxys
|
||||
.map(m => {
|
||||
if (m.status == null || m.status == undefined) {
|
||||
m.status = true;
|
||||
}
|
||||
return m;
|
||||
})
|
||||
.filter(f => f.status);
|
||||
if (currentVersion < 124395282) {
|
||||
// 版本小于v0.52.0
|
||||
filename = "frp.ini";
|
||||
configContent = genIniConfig(config, filtered);
|
||||
} else {
|
||||
filename = "frp.toml";
|
||||
configContent = genTomlConfig(config, filtered);
|
||||
}
|
||||
);
|
||||
const configPath = path.join(app.getPath("userData"), filename);
|
||||
log.info(`生成配置成功 配置路径:${configPath}`);
|
||||
fs.writeFile(
|
||||
configPath, // 配置文件目录
|
||||
configContent, // 配置文件内容
|
||||
{ flag: "w" },
|
||||
err => {
|
||||
if (!err) {
|
||||
callback(filename);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@ -479,13 +359,7 @@ export const generateConfig = (
|
||||
* @param configPath
|
||||
*/
|
||||
const startFrpcProcess = (commandPath: string, configPath: string) => {
|
||||
logInfo(
|
||||
LogModule.FRP_CLIENT,
|
||||
`Starting frpc process. Directory: ${app.getPath(
|
||||
"userData"
|
||||
)}, Command: ${commandPath}`
|
||||
);
|
||||
|
||||
log.info(`启动frpc 目录:${app.getPath("userData")} 命令:${commandPath}`);
|
||||
const command = `${commandPath} -c ${configPath}`;
|
||||
frpcProcess = spawn(command, {
|
||||
cwd: app.getPath("userData"),
|
||||
@ -493,31 +367,21 @@ const startFrpcProcess = (commandPath: string, configPath: string) => {
|
||||
});
|
||||
runningCmd.commandPath = commandPath;
|
||||
runningCmd.configPath = configPath;
|
||||
|
||||
frpcProcess.stdout.on("data", data => {
|
||||
logDebug(LogModule.FRP_CLIENT, `Frpc process output: ${data}`);
|
||||
log.debug(`启动输出:${data}`);
|
||||
});
|
||||
|
||||
frpcProcess.stdout.on("error", data => {
|
||||
logError(LogModule.FRP_CLIENT, `Frpc process error: ${data}`);
|
||||
log.error(`启动错误:${data}`);
|
||||
stopFrpcProcess(() => {});
|
||||
});
|
||||
|
||||
frpcStatusListener = setInterval(() => {
|
||||
const status = frpcProcessStatus();
|
||||
logDebug(
|
||||
LogModule.FRP_CLIENT,
|
||||
`Monitoring frpc process status: ${status}, Listener ID: ${frpcStatusListener}`
|
||||
);
|
||||
log.debug(`监听frpc子进程状态:${status} ${frpcStatusListener}`);
|
||||
if (!status) {
|
||||
new Notification({
|
||||
title: "Frpc Desktop",
|
||||
body: "Connection lost, please check the logs for details."
|
||||
body: "连接已断开,请前往日志查看原因"
|
||||
}).show();
|
||||
logError(
|
||||
LogModule.FRP_CLIENT,
|
||||
"Frpc process status check failed. Connection lost."
|
||||
);
|
||||
clearInterval(frpcStatusListener);
|
||||
}
|
||||
}, 3000);
|
||||
@ -528,58 +392,20 @@ const startFrpcProcess = (commandPath: string, configPath: string) => {
|
||||
*/
|
||||
export const reloadFrpcProcess = () => {
|
||||
if (frpcProcess && !frpcProcess.killed) {
|
||||
logDebug(
|
||||
LogModule.FRP_CLIENT,
|
||||
"Attempting to reload frpc process configuration."
|
||||
);
|
||||
getConfig((err1, config) => {
|
||||
if (!err1) {
|
||||
if (config) {
|
||||
generateConfig(config, configPath => {
|
||||
const command = `${runningCmd.commandPath} reload -c ${configPath}`;
|
||||
logInfo(
|
||||
LogModule.FRP_CLIENT,
|
||||
`Reloading configuration: ${command}`
|
||||
);
|
||||
exec(
|
||||
command,
|
||||
{
|
||||
cwd: app.getPath("userData"),
|
||||
shell: true
|
||||
},
|
||||
(error, stdout, stderr) => {
|
||||
if (error) {
|
||||
logError(
|
||||
LogModule.FRP_CLIENT,
|
||||
`Error reloading configuration: ${error.message}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
logDebug(
|
||||
LogModule.FRP_CLIENT,
|
||||
`Configuration reload output: ${stdout}`
|
||||
);
|
||||
if (stderr) {
|
||||
logWarn(
|
||||
LogModule.FRP_CLIENT,
|
||||
`Configuration reload warnings: ${stderr}`
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
log.info(`重载配置:${command}`);
|
||||
exec(command, {
|
||||
cwd: app.getPath("userData"),
|
||||
shell: true
|
||||
});
|
||||
});
|
||||
} else {
|
||||
logWarn(LogModule.FRP_CLIENT, "No configuration found to reload.");
|
||||
}
|
||||
} else {
|
||||
logError(LogModule.FRP_CLIENT, `Error getting configuration: ${err1}`);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
logDebug(
|
||||
LogModule.FRP_CLIENT,
|
||||
"frpc process is not running or has been killed."
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@ -590,27 +416,16 @@ export const stopFrpcProcess = (callback?: () => void) => {
|
||||
if (frpcProcess) {
|
||||
treeKill(frpcProcess.pid, (error: Error) => {
|
||||
if (error) {
|
||||
logError(
|
||||
LogModule.FRP_CLIENT,
|
||||
`Failed to stop frpc process with pid: ${frpcProcess.pid}. Error: ${error.message}`
|
||||
);
|
||||
log.error(`关闭frpc子进程失败 pid:${frpcProcess.pid} error:${error}`);
|
||||
callback();
|
||||
} else {
|
||||
logInfo(
|
||||
LogModule.FRP_CLIENT,
|
||||
`Successfully stopped frpc process with pid: ${frpcProcess.pid}.`
|
||||
);
|
||||
log.info(`关闭frpc子进程成功`);
|
||||
frpcProcess = null;
|
||||
clearInterval(frpcStatusListener);
|
||||
callback();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
logWarn(
|
||||
LogModule.FRP_CLIENT,
|
||||
"Attempted to stop frpc process, but no process is running."
|
||||
);
|
||||
logWarn(LogModule.FRP_CLIENT, "No frpc process to stop.");
|
||||
callback();
|
||||
}
|
||||
};
|
||||
@ -620,23 +435,14 @@ export const stopFrpcProcess = (callback?: () => void) => {
|
||||
*/
|
||||
export const frpcProcessStatus = () => {
|
||||
if (!frpcProcess) {
|
||||
logDebug(LogModule.FRP_CLIENT, "frpc process is not running.");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
// 发送信号给进程,如果进程存在,会正常返回
|
||||
process.kill(frpcProcess.pid, 0);
|
||||
logDebug(
|
||||
LogModule.FRP_CLIENT,
|
||||
`frpc process is running with pid: ${frpcProcess.pid}`
|
||||
);
|
||||
return true;
|
||||
} catch (error) {
|
||||
// 进程不存在,抛出异常
|
||||
logError(
|
||||
LogModule.FRP_CLIENT,
|
||||
`frpc process not found. Error: ${error.message}`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@ -646,47 +452,29 @@ export const frpcProcessStatus = () => {
|
||||
* @param config
|
||||
*/
|
||||
export const startFrpWorkerProcess = async (config: FrpConfig) => {
|
||||
logInfo(LogModule.FRP_CLIENT, "Starting frpc worker process...");
|
||||
getFrpcVersionWorkerPath(config.currentVersion, (frpcVersionPath: string) => {
|
||||
if (frpcVersionPath) {
|
||||
logInfo(
|
||||
LogModule.FRP_CLIENT,
|
||||
`Found frpc version path: ${frpcVersionPath}`
|
||||
);
|
||||
generateConfig(config, configPath => {
|
||||
const platform = process.platform;
|
||||
if (platform === "win32") {
|
||||
logInfo(LogModule.FRP_CLIENT, "Starting frpc on Windows.");
|
||||
startFrpcProcess(path.join(frpcVersionPath, "frpc.exe"), configPath);
|
||||
} else {
|
||||
logInfo(
|
||||
LogModule.FRP_CLIENT,
|
||||
"Starting frpc on non-Windows platform."
|
||||
);
|
||||
startFrpcProcess(path.join(frpcVersionPath, "frpc"), configPath);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
logError(LogModule.FRP_CLIENT, "frpc version path not found.");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const initFrpcApi = () => {
|
||||
ipcMain.handle("frpc.running", async (event, args) => {
|
||||
logDebug(LogModule.FRP_CLIENT, "Checking if frpc process is running...");
|
||||
return frpcProcessStatus();
|
||||
});
|
||||
|
||||
ipcMain.on("frpc.start", async (event, args) => {
|
||||
logInfo(LogModule.FRP_CLIENT, "Received request to start frpc process.");
|
||||
getConfig((err1, config) => {
|
||||
if (!err1) {
|
||||
if (!config) {
|
||||
logWarn(
|
||||
LogModule.FRP_CLIENT,
|
||||
"Configuration not found. Prompting user to set configuration."
|
||||
);
|
||||
event.reply(
|
||||
"Home.frpc.start.error.hook",
|
||||
"请先前往设置页面,修改配置后再启动"
|
||||
@ -694,10 +482,6 @@ export const initFrpcApi = () => {
|
||||
return;
|
||||
}
|
||||
if (!config.currentVersion) {
|
||||
logWarn(
|
||||
LogModule.FRP_CLIENT,
|
||||
"Current version not set in configuration. Prompting user."
|
||||
);
|
||||
event.reply(
|
||||
"Home.frpc.start.error.hook",
|
||||
"请先前往设置页面,修改配置后再启动"
|
||||
@ -705,18 +489,13 @@ export const initFrpcApi = () => {
|
||||
return;
|
||||
}
|
||||
startFrpWorkerProcess(config);
|
||||
} else {
|
||||
logError(LogModule.FRP_CLIENT, `Error getting configuration: ${err1}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.on("frpc.stop", () => {
|
||||
logInfo(LogModule.FRP_CLIENT, "Received request to stop frpc process.");
|
||||
if (frpcProcess && !frpcProcess.killed) {
|
||||
stopFrpcProcess(() => {});
|
||||
} else {
|
||||
logWarn(LogModule.FRP_CLIENT, "No frpc process to stop.");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -1,14 +1,6 @@
|
||||
import electron, {
|
||||
app,
|
||||
dialog,
|
||||
BrowserWindow,
|
||||
ipcMain,
|
||||
net,
|
||||
shell
|
||||
} from "electron";
|
||||
import electron, { app, BrowserWindow, ipcMain, net, shell } from "electron";
|
||||
import {
|
||||
deleteVersionById,
|
||||
getVersionById,
|
||||
insertVersion,
|
||||
listVersion
|
||||
} from "../storage/version";
|
||||
@ -18,11 +10,7 @@ const path = require("path");
|
||||
const zlib = require("zlib");
|
||||
const { download } = require("electron-dl");
|
||||
const AdmZip = require("adm-zip");
|
||||
import frpReleasesJson from "../json/frp-releases.json";
|
||||
import frpChecksums from "../json/frp_all_sha256_checksums.json";
|
||||
import { logInfo, logError, LogModule, logDebug, logWarn } from "../utils/log";
|
||||
import { calculateFileChecksum, formatBytes } from "../utils/file";
|
||||
import { el } from "element-plus/es/locale";
|
||||
const log = require("electron-log");
|
||||
|
||||
const versionRelation = {
|
||||
win32_x64: ["window", "amd64"],
|
||||
@ -42,44 +30,23 @@ const frpArch = versionRelation[currArch];
|
||||
const unTarGZ = (tarGzPath: string, targetPath: string) => {
|
||||
const tar = require("tar");
|
||||
const unzip = zlib.createGunzip();
|
||||
logInfo(
|
||||
LogModule.APP,
|
||||
`Starting to extract tar.gz: ${tarGzPath} to ${targetPath}`
|
||||
);
|
||||
|
||||
log.debug(`开始解压tar.gz:${tarGzPath} 目标目录:${targetPath}`);
|
||||
const readStream = fs.createReadStream(tarGzPath);
|
||||
if (!fs.existsSync(unzip)) {
|
||||
fs.mkdirSync(targetPath, { recursive: true, mode: 0o777 });
|
||||
logInfo(LogModule.APP, `Created target directory: ${targetPath}`);
|
||||
fs.mkdirSync(targetPath, { recursive: true });
|
||||
}
|
||||
|
||||
readStream.on("error", err => {
|
||||
logError(LogModule.APP, `Error reading tar.gz file: ${err.message}`);
|
||||
});
|
||||
|
||||
readStream
|
||||
.pipe(unzip)
|
||||
.on("error", err => {
|
||||
logError(LogModule.APP, `Error during gunzip: ${err.message}`);
|
||||
readStream.pipe(unzip).pipe(
|
||||
tar.extract({
|
||||
cwd: targetPath,
|
||||
filter: filePath => path.basename(filePath) === "frpc"
|
||||
})
|
||||
.pipe(
|
||||
tar
|
||||
.extract({
|
||||
cwd: targetPath,
|
||||
filter: filePath => path.basename(filePath) === "frpc"
|
||||
})
|
||||
.on("error", err => {
|
||||
logError(LogModule.APP, `Error extracting tar file: ${err.message}`);
|
||||
})
|
||||
)
|
||||
.on("finish", () => {
|
||||
const frpcPath = path.join("frp", path.basename(tarGzPath, ".tar.gz"));
|
||||
logInfo(
|
||||
LogModule.APP,
|
||||
`Extraction completed. Extracted directory: ${frpcPath}`
|
||||
);
|
||||
});
|
||||
return path.join("frp", path.basename(tarGzPath, ".tar.gz"));
|
||||
);
|
||||
const frpcPath = path.join("frp", path.basename(tarGzPath, ".tar.gz"));
|
||||
log.debug(`解压完成 解压后目录:${frpcPath}`);
|
||||
return frpcPath;
|
||||
// .on("finish", () => {
|
||||
// console.log("解压完成!");
|
||||
// });
|
||||
};
|
||||
|
||||
const unZip = (zipPath: string, targetPath: string) => {
|
||||
@ -87,300 +54,126 @@ const unZip = (zipPath: string, targetPath: string) => {
|
||||
fs.mkdirSync(path.join(targetPath, path.basename(zipPath, ".zip")), {
|
||||
recursive: true
|
||||
});
|
||||
logInfo(LogModule.APP, `Created target directory: ${targetPath}`);
|
||||
logInfo(
|
||||
LogModule.APP,
|
||||
`Created directory for zip extraction: ${path.basename(zipPath, ".zip")}`
|
||||
);
|
||||
}
|
||||
|
||||
logDebug(
|
||||
LogModule.APP,
|
||||
`Starting to unzip: ${zipPath} to target directory: ${targetPath}`
|
||||
);
|
||||
logInfo(LogModule.APP, `Starting to extract zip file: ${zipPath}`);
|
||||
log.debug(`开始解压zip:${zipPath} 目标目录:${targetPath}`);
|
||||
/**
|
||||
* unzipper解压
|
||||
*/
|
||||
// fs.createReadStream(zipPath)
|
||||
// .pipe(unzipper.Extract({ path: targetPath }))
|
||||
// // 只解压frpc.exe
|
||||
// // .pipe(unzipper.ParseOne('frpc'))
|
||||
// // .pipe(fs.createWriteStream(path.join(targetPath, path.basename(zipPath, ".zip"), "frpc.exe")))
|
||||
// .on('finish', () => {
|
||||
// console.log('File extracted successfully.');
|
||||
// })
|
||||
// .on('error', (err) => {
|
||||
// console.error('Error extracting file:', err);
|
||||
// });
|
||||
|
||||
const zip = new AdmZip(zipPath);
|
||||
try {
|
||||
zip.extractAllTo(targetPath, true); // 第二个参数为 true,表示覆盖已存在的文件
|
||||
const frpcPath = path.join("frp", path.basename(zipPath, ".zip"));
|
||||
logInfo(
|
||||
LogModule.APP,
|
||||
`Extraction completed. Extracted directory: ${frpcPath}`
|
||||
);
|
||||
logDebug(
|
||||
LogModule.APP,
|
||||
`Unzip completed. Extracted directory: ${frpcPath}`
|
||||
);
|
||||
return frpcPath;
|
||||
} catch (error) {
|
||||
logError(LogModule.APP, `Error extracting zip file: ${error.message}`);
|
||||
}
|
||||
|
||||
return null;
|
||||
zip.extractAllTo(targetPath, true); // 第二个参数为 true,表示覆盖已存在的文件
|
||||
const frpcPath = path.join("frp", path.basename(zipPath, ".zip"));
|
||||
log.debug(`解压完成 解压后目录:${frpcPath}`);
|
||||
return frpcPath;
|
||||
};
|
||||
|
||||
export const initGitHubApi = win => {
|
||||
export const initGitHubApi = () => {
|
||||
// 版本
|
||||
let versions: FrpVersion[] = [];
|
||||
|
||||
const getVersionByGithubVersionId = versionId => {
|
||||
logDebug(LogModule.APP, `Attempting to get version with ID: ${versionId}`);
|
||||
const version = versions.find(f => f.id === versionId);
|
||||
if (version) {
|
||||
logInfo(
|
||||
LogModule.APP,
|
||||
`Version details ID:${version.id}, Name:${version.name}, Published At:${version.published_at}`
|
||||
);
|
||||
} else {
|
||||
logWarn(LogModule.APP, `No version found for ID: ${versionId}`);
|
||||
}
|
||||
return version;
|
||||
};
|
||||
|
||||
const getVersionByAssetName = (assetName: string) => {
|
||||
logDebug(
|
||||
LogModule.APP,
|
||||
`Attempting to get version with asset name: ${assetName}`
|
||||
);
|
||||
const version = versions.find(f =>
|
||||
f.assets.some(asset => asset.name === assetName)
|
||||
);
|
||||
if (version) {
|
||||
logInfo(
|
||||
LogModule.APP,
|
||||
`Version details ID:${version.id}, Name:${version.name}, Published At:${version.published_at}`
|
||||
);
|
||||
} else {
|
||||
logWarn(LogModule.APP, `No version found for asset name: ${assetName}`);
|
||||
}
|
||||
return version;
|
||||
const getVersion = versionId => {
|
||||
return versions.find(f => f.id === versionId);
|
||||
};
|
||||
|
||||
const getAdaptiveAsset = versionId => {
|
||||
const { assets } = getVersionByGithubVersionId(versionId);
|
||||
if (!assets || assets.length === 0) {
|
||||
logWarn(LogModule.GITHUB, `No assets found for version ID: ${versionId}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const { assets } = getVersion(versionId);
|
||||
const asset = assets.find(f => {
|
||||
// const a = versionRelation[currArch]
|
||||
const a = frpArch;
|
||||
if (a) {
|
||||
const flag = a.every(item => f.name.includes(item));
|
||||
if (flag) {
|
||||
logInfo(
|
||||
LogModule.GITHUB,
|
||||
`Found matching asset: ${f.name} for version ID: ${versionId}`
|
||||
);
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
logWarn(
|
||||
LogModule.GITHUB,
|
||||
`No architecture match found for asset: ${f.name}`
|
||||
);
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!asset) {
|
||||
logError(
|
||||
LogModule.GITHUB,
|
||||
`No adaptive asset found for version ID: ${versionId}`
|
||||
);
|
||||
if (asset) {
|
||||
log.info(`找到对应版本 ${asset.name}`);
|
||||
}
|
||||
return asset;
|
||||
};
|
||||
|
||||
/**
|
||||
* handle github api release json
|
||||
* @param githubReleaseJsonStr jsonStr
|
||||
* @returns versions
|
||||
*/
|
||||
const handleApiResponse = (githubReleaseJsonStr: string) => {
|
||||
const downloadPath = path.join(app.getPath("userData"), "download");
|
||||
const frpPath = path.join(app.getPath("userData"), "frp");
|
||||
logInfo(LogModule.GITHUB, "Parsing GitHub release JSON response.");
|
||||
const formatBytes = (bytes: number, decimals: number = 2): string => {
|
||||
if (bytes === 0) return "0 Bytes";
|
||||
const k = 1024; // 1 KB = 1024 Bytes
|
||||
const dm = decimals < 0 ? 0 : decimals; // 确保小数位数不小于 0
|
||||
const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
|
||||
|
||||
versions = JSON.parse(githubReleaseJsonStr);
|
||||
if (versions) {
|
||||
logInfo(
|
||||
LogModule.GITHUB,
|
||||
"Successfully parsed versions from GitHub response."
|
||||
);
|
||||
|
||||
const returnVersionsData = versions
|
||||
.filter(f => getAdaptiveAsset(f.id))
|
||||
.map(m => {
|
||||
const asset = getAdaptiveAsset(m.id);
|
||||
const download_count = m.assets.reduce(
|
||||
(sum, item) => sum + item.download_count,
|
||||
0
|
||||
);
|
||||
if (asset) {
|
||||
const absPath = path.join(
|
||||
frpPath,
|
||||
asset.name.replace(/(\.tar\.gz|\.zip)$/, "")
|
||||
);
|
||||
m.absPath = absPath;
|
||||
m.download_completed = fs.existsSync(absPath);
|
||||
m.download_count = download_count;
|
||||
m.size = formatBytes(asset.size);
|
||||
logInfo(
|
||||
LogModule.GITHUB,
|
||||
`Asset found: ${asset.name}, download count: ${download_count}`
|
||||
);
|
||||
} else {
|
||||
logWarn(LogModule.GITHUB, `No asset found for version ID: ${m.id}`);
|
||||
}
|
||||
return m;
|
||||
});
|
||||
logDebug(
|
||||
LogModule.GITHUB,
|
||||
`Retrieved FRP versions: ${JSON.stringify(returnVersionsData)}`
|
||||
);
|
||||
return returnVersionsData;
|
||||
} else {
|
||||
logError(
|
||||
LogModule.GITHUB,
|
||||
"Failed to parse versions: No versions found in response."
|
||||
);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* conventMirrorUrl
|
||||
* @param mirror mirror
|
||||
* @returns mirrorUrl
|
||||
*/
|
||||
const conventMirrorUrl = (mirror: string) => {
|
||||
switch (mirror) {
|
||||
case "github":
|
||||
return {
|
||||
api: "https://api.github.com",
|
||||
asset: "https://github.com"
|
||||
};
|
||||
default:
|
||||
return {
|
||||
api: "https://api.github.com",
|
||||
asset: "https://github.com"
|
||||
};
|
||||
}
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k)); // 计算单位索引
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i]; // 返回格式化的字符串
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取github上的frp所有版本
|
||||
*/
|
||||
ipcMain.on("github.getFrpVersions", async (event, mirror: string) => {
|
||||
const { api } = conventMirrorUrl(mirror);
|
||||
const mirrorUrl = api;
|
||||
logInfo(LogModule.GITHUB, `Requesting mirror URL: ${mirrorUrl}`);
|
||||
ipcMain.on("github.getFrpVersions", async event => {
|
||||
const request = net.request({
|
||||
method: "get",
|
||||
url: `${mirrorUrl}/repos/fatedier/frp/releases?page=1&per_page=1000`
|
||||
url: "https://api.github.com/repos/fatedier/frp/releases?page=1&per_page=1000"
|
||||
});
|
||||
|
||||
let githubReleaseJsonStr = null;
|
||||
request.on("response", response => {
|
||||
logInfo(
|
||||
LogModule.GITHUB,
|
||||
`Received response with status code: ${response.statusCode}`
|
||||
);
|
||||
let responseData: Buffer = Buffer.alloc(0);
|
||||
response.on("data", (data: Buffer) => {
|
||||
responseData = Buffer.concat([responseData, data]);
|
||||
});
|
||||
response.on("end", () => {
|
||||
if (response.statusCode === 200) {
|
||||
githubReleaseJsonStr = responseData.toString();
|
||||
logInfo(
|
||||
LogModule.GITHUB,
|
||||
"Successfully retrieved GitHub release data."
|
||||
);
|
||||
} else {
|
||||
logWarn(
|
||||
LogModule.GITHUB,
|
||||
"Failed to retrieve data, using local JSON instead. Status code: " +
|
||||
response.statusCode
|
||||
);
|
||||
githubReleaseJsonStr = JSON.stringify(frpReleasesJson);
|
||||
}
|
||||
const versions = handleApiResponse(githubReleaseJsonStr);
|
||||
event.reply("Download.frpVersionHook", versions);
|
||||
versions = JSON.parse(responseData.toString());
|
||||
// const borderContent: Electron.WebContents =
|
||||
// BrowserWindow.getFocusedWindow().webContents;
|
||||
const downloadPath = path.join(app.getPath("userData"), "download");
|
||||
log.info(
|
||||
`开始获取frp版本 当前架构:${currArch} 对应frp架构:${frpArch}`
|
||||
);
|
||||
const returnVersionsData = versions
|
||||
.filter(f => getAdaptiveAsset(f.id))
|
||||
.map(m => {
|
||||
const asset = getAdaptiveAsset(m.id);
|
||||
const download_count = m.assets.reduce(
|
||||
(sum, item) => sum + item.download_count,
|
||||
0
|
||||
);
|
||||
if (asset) {
|
||||
const absPath = `${downloadPath}/${asset.name}`;
|
||||
m.absPath = absPath;
|
||||
m.download_completed = fs.existsSync(absPath);
|
||||
m.download_count = download_count;
|
||||
m.size = formatBytes(asset.size);
|
||||
}
|
||||
return m;
|
||||
});
|
||||
// log.debug(`获取到frp版本:${JSON.stringify(returnVersionsData)}`)
|
||||
event.reply("Download.frpVersionHook", returnVersionsData);
|
||||
});
|
||||
});
|
||||
|
||||
request.on("error", error => {
|
||||
logError(
|
||||
LogModule.GITHUB,
|
||||
"Error occurred while requesting GitHub releases: " + error
|
||||
);
|
||||
githubReleaseJsonStr = JSON.stringify(frpReleasesJson);
|
||||
const versions = handleApiResponse(githubReleaseJsonStr);
|
||||
event.reply("Download.frpVersionHook", versions);
|
||||
});
|
||||
|
||||
request.end();
|
||||
});
|
||||
|
||||
const decompressFrp = (frpFilename: string, compressedFilePath: string) => {
|
||||
const targetPath = path.resolve(path.join(app.getPath("userData"), "frp"));
|
||||
const ext = path.extname(frpFilename);
|
||||
let frpcVersionPath = "";
|
||||
try {
|
||||
if (ext === ".zip") {
|
||||
unZip(
|
||||
// path.join(
|
||||
// path.join(app.getPath("userData"), "download"),
|
||||
// `${frpFilename}`
|
||||
// ),
|
||||
compressedFilePath,
|
||||
targetPath
|
||||
);
|
||||
logInfo(LogModule.APP, `Unzipped file to path: ${frpcVersionPath}`);
|
||||
frpcVersionPath = path.join("frp", path.basename(frpFilename, ".zip"));
|
||||
} else if (ext === ".gz" && frpFilename.includes(".tar.gz")) {
|
||||
unTarGZ(
|
||||
// path.join(
|
||||
// path.join(app.getPath("userData"), "download"),
|
||||
// `${frpFilename}`
|
||||
// ),
|
||||
compressedFilePath,
|
||||
targetPath
|
||||
);
|
||||
frpcVersionPath = path.join(
|
||||
"frp",
|
||||
path.basename(frpFilename, ".tar.gz")
|
||||
);
|
||||
logInfo(LogModule.APP, `Untarred file to path: ${frpcVersionPath}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logError(LogModule.APP, `Error during extraction: ${error.message}`);
|
||||
}
|
||||
|
||||
return frpcVersionPath;
|
||||
};
|
||||
|
||||
/**
|
||||
* 下载请求
|
||||
*/
|
||||
ipcMain.on("github.download", async (event, args) => {
|
||||
const { versionId, mirror } = args;
|
||||
const version = getVersionByGithubVersionId(versionId);
|
||||
const version = getVersion(versionId);
|
||||
const asset = getAdaptiveAsset(versionId);
|
||||
const { browser_download_url } = asset;
|
||||
|
||||
let url = browser_download_url.replace(
|
||||
"https://github.com",
|
||||
conventMirrorUrl(mirror).asset
|
||||
);
|
||||
|
||||
logDebug(
|
||||
LogModule.GITHUB,
|
||||
`Starting download for versionId: ${versionId}, mirror: ${mirror}, download URL: ${url}`
|
||||
);
|
||||
let url = browser_download_url;
|
||||
if (mirror === "ghproxy") {
|
||||
url = "https://mirror.ghproxy.com/" + url;
|
||||
}
|
||||
|
||||
log.info(`开始下载frp url:${url} asset:${asset.name}`);
|
||||
// 数据目录
|
||||
await download(BrowserWindow.getFocusedWindow(), url, {
|
||||
filename: `${asset.name}`,
|
||||
directory: path.join(app.getPath("userData"), "download"),
|
||||
@ -389,26 +182,32 @@ export const initGitHubApi = win => {
|
||||
id: versionId,
|
||||
progress: progress
|
||||
});
|
||||
logDebug(
|
||||
LogModule.GITHUB,
|
||||
`Download progress for versionId: ${versionId} is ${
|
||||
progress.percent * 100
|
||||
}%`
|
||||
);
|
||||
},
|
||||
onCompleted: () => {
|
||||
logInfo(
|
||||
LogModule.GITHUB,
|
||||
`Download completed for versionId: ${versionId}, asset: ${asset.name}`
|
||||
log.info(`frp下载完成 url:${url} asset:${asset.name}`);
|
||||
const targetPath = path.resolve(
|
||||
path.join(app.getPath("userData"), "frp")
|
||||
);
|
||||
const ext = path.extname(asset.name);
|
||||
let frpcVersionPath = "";
|
||||
if (ext === ".zip") {
|
||||
frpcVersionPath = unZip(
|
||||
path.join(
|
||||
path.join(app.getPath("userData"), "download"),
|
||||
`${asset.name}`
|
||||
),
|
||||
targetPath
|
||||
);
|
||||
} else if (ext === ".gz" && asset.name.includes(".tar.gz")) {
|
||||
frpcVersionPath = unTarGZ(
|
||||
path.join(
|
||||
path.join(app.getPath("userData"), "download"),
|
||||
`${asset.name}`
|
||||
),
|
||||
targetPath
|
||||
);
|
||||
}
|
||||
|
||||
const frpcVersionPath = decompressFrp(
|
||||
asset.name,
|
||||
path.join(
|
||||
path.join(app.getPath("userData"), "download"),
|
||||
`${asset.name}`
|
||||
)
|
||||
);
|
||||
version["frpcVersionPath"] = frpcVersionPath;
|
||||
insertVersion(version, (err, document) => {
|
||||
if (!err) {
|
||||
@ -416,13 +215,7 @@ export const initGitHubApi = win => {
|
||||
event.reply("Config.versions.hook", { err, data: doc });
|
||||
event.reply("Download.frpVersionDownloadOnCompleted", versionId);
|
||||
version.download_completed = true;
|
||||
logInfo(
|
||||
LogModule.GITHUB,
|
||||
`Version ${versionId} has been inserted successfully.`
|
||||
);
|
||||
});
|
||||
} else {
|
||||
logError(LogModule.GITHUB, `Error inserting version: ${err}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -434,39 +227,17 @@ export const initGitHubApi = win => {
|
||||
*/
|
||||
ipcMain.on("github.deleteVersion", async (event, args) => {
|
||||
const { absPath, id } = args;
|
||||
logDebug(
|
||||
LogModule.GITHUB,
|
||||
`Attempting to delete version with ID: ${id} and path: ${absPath}`
|
||||
);
|
||||
if (fs.existsSync(absPath)) {
|
||||
// if (process.platform === 'darwin') {
|
||||
// fs.unlinkSync(absPath.replace(/ /g, '\\ '));
|
||||
// }else{
|
||||
// fs.unlinkSync(absPath);
|
||||
// }
|
||||
fs.rmSync(absPath, { recursive: true, force: true });
|
||||
deleteVersionById(id, () => {
|
||||
logInfo(
|
||||
LogModule.GITHUB,
|
||||
`Successfully deleted version with ID: ${id}`
|
||||
);
|
||||
fs.unlinkSync(absPath);
|
||||
});
|
||||
} else {
|
||||
logWarn(
|
||||
LogModule.GITHUB,
|
||||
`Version with ID: ${id} not found at path: ${absPath}`
|
||||
);
|
||||
}
|
||||
listVersion((err, doc) => {
|
||||
if (err) {
|
||||
logError(LogModule.GITHUB, `Error listing versions: ${err}`);
|
||||
} else {
|
||||
event.reply("Config.versions.hook", { err, data: doc });
|
||||
event.reply("Download.deleteVersion.hook", {
|
||||
err: null,
|
||||
data: "删除成功"
|
||||
});
|
||||
}
|
||||
event.reply("Config.versions.hook", { err, data: doc });
|
||||
event.reply("Download.deleteVersion.hook", {
|
||||
err: null,
|
||||
data: "删除成功"
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -474,7 +245,6 @@ export const initGitHubApi = win => {
|
||||
* 获取最后版本
|
||||
*/
|
||||
ipcMain.on("github.getFrpcDesktopLastVersions", async event => {
|
||||
logInfo(LogModule.GITHUB, "Requesting the latest version from GitHub.");
|
||||
const request = net.request({
|
||||
method: "get",
|
||||
url: "https://api.github.com/repos/luckjiawei/frpc-desktop/releases/latest"
|
||||
@ -485,114 +255,26 @@ export const initGitHubApi = win => {
|
||||
responseData = Buffer.concat([responseData, data]);
|
||||
});
|
||||
response.on("end", () => {
|
||||
try {
|
||||
versions = JSON.parse(responseData.toString());
|
||||
logInfo(
|
||||
LogModule.GITHUB,
|
||||
"Successfully retrieved the latest version."
|
||||
);
|
||||
event.reply("github.getFrpcDesktopLastVersionsHook", versions);
|
||||
} catch (error) {
|
||||
logError(
|
||||
LogModule.GITHUB,
|
||||
`Error parsing response data: ${error.message}`
|
||||
);
|
||||
}
|
||||
versions = JSON.parse(responseData.toString());
|
||||
// const borderContent: Electron.WebContents =
|
||||
// BrowserWindow.getFocusedWindow().webContents;
|
||||
// const downloadPath = path.join(app.getPath("userData"), "download");
|
||||
// log.info(`开始获取frp版本 当前架构:${currArch} 对应frp架构:${frpArch}`)
|
||||
// const returnVersionsData = versions
|
||||
// .filter(f => getAdaptiveAsset(f.id))
|
||||
// .map(m => {
|
||||
// const asset = getAdaptiveAsset(m.id);
|
||||
// if (asset) {
|
||||
// const absPath = `${downloadPath}/${asset.name}`;
|
||||
// m.absPath = absPath;
|
||||
// m.download_completed = fs.existsSync(absPath);
|
||||
// }
|
||||
// return m;
|
||||
// });
|
||||
// log.debug(`获取到frp版本:${JSON.stringify(returnVersionsData)}`)
|
||||
event.reply("github.getFrpcDesktopLastVersionsHook", versions);
|
||||
});
|
||||
});
|
||||
request.on("error", error => {
|
||||
logError(LogModule.GITHUB, `Request error: ${error.message}`);
|
||||
});
|
||||
request.end();
|
||||
});
|
||||
|
||||
ipcMain.on(
|
||||
"download.importFrpFile",
|
||||
async (event, filePath: string, targetPath: string) => {
|
||||
const result = await dialog.showOpenDialog(win, {
|
||||
properties: ["openFile"],
|
||||
filters: [
|
||||
{ name: "Frp 文件", extensions: ["tar.gz", "zip"] } // 允许选择的文件类型,分开后缀以确保可以选择
|
||||
]
|
||||
});
|
||||
if (result.canceled) {
|
||||
logWarn(LogModule.APP, "Import canceled by user.");
|
||||
logWarn(LogModule.GITHUB, "User canceled the file import operation.");
|
||||
return;
|
||||
} else {
|
||||
const filePath = result.filePaths[0];
|
||||
// const fileExtension = path.extname(filePath);
|
||||
logInfo(LogModule.APP, `User selected file: ${filePath}`);
|
||||
const checksum = calculateFileChecksum(filePath);
|
||||
logInfo(LogModule.APP, `Calculated checksum for the file: ${checksum}`);
|
||||
const frpName = frpChecksums[checksum];
|
||||
if (frpName) {
|
||||
logInfo(LogModule.APP, `FRP file name found: ${frpName}`);
|
||||
if (frpArch.every(item => frpName.includes(item))) {
|
||||
logInfo(
|
||||
LogModule.APP,
|
||||
`Architecture matches for FRP file: ${frpName}`
|
||||
);
|
||||
const version = getVersionByAssetName(frpName);
|
||||
getVersionById(version.id, (err, existingVersion) => {
|
||||
if (!err && existingVersion) {
|
||||
logInfo(
|
||||
LogModule.APP,
|
||||
`Version already exists: ${JSON.stringify(existingVersion)}`
|
||||
);
|
||||
event.reply("Download.importFrpFile.hook", {
|
||||
success: false,
|
||||
data: `导入失败,版本已存在`
|
||||
});
|
||||
return; // 终止后续执行
|
||||
}
|
||||
|
||||
const frpcVersionPath = decompressFrp(frpName, filePath);
|
||||
logInfo(
|
||||
LogModule.APP,
|
||||
`Successfully decompressed FRP file: ${frpName} to path: ${frpcVersionPath}`
|
||||
);
|
||||
version["frpcVersionPath"] = frpcVersionPath;
|
||||
insertVersion(version, (err, document) => {
|
||||
if (!err) {
|
||||
listVersion((err, doc) => {
|
||||
event.reply("Config.versions.hook", { err, data: doc });
|
||||
version.download_completed = true;
|
||||
event.reply("Download.importFrpFile.hook", {
|
||||
success: true,
|
||||
data: `导入成功`
|
||||
});
|
||||
});
|
||||
} else {
|
||||
logError(LogModule.APP, `Error inserting version: ${err}`);
|
||||
event.reply("Download.importFrpFile.hook", {
|
||||
success: true,
|
||||
data: `导入失败,未知错误`
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
logWarn(
|
||||
LogModule.APP,
|
||||
`Architecture does not match for FRP file: ${frpName}`
|
||||
);
|
||||
event.reply("Download.importFrpFile.hook", {
|
||||
success: false,
|
||||
data: `导入失败,所选 frp 架构与操作系统不符`
|
||||
});
|
||||
}
|
||||
} else {
|
||||
logWarn(
|
||||
LogModule.APP,
|
||||
`No matching FRP file name found for checksum: ${checksum}`
|
||||
);
|
||||
event.reply("Download.importFrpFile.hook", {
|
||||
success: false,
|
||||
data: `导入失败,无法识别文件`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {ipcMain} from "electron";
|
||||
import { logDebug, logError, logInfo, LogModule, logWarn } from "../utils/log";
|
||||
import log from "electron-log";
|
||||
|
||||
const {exec, spawn} = require("child_process");
|
||||
|
||||
@ -15,19 +15,19 @@ export const initLocalApi = () => {
|
||||
: 'netstat -an | grep LISTEN';
|
||||
|
||||
ipcMain.on("local.getLocalPorts", async (event, args) => {
|
||||
logInfo(LogModule.APP, "Starting to retrieve local ports");
|
||||
log.info("开始获取本地端口")
|
||||
// 执行命令
|
||||
exec(command, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
logError(LogModule.APP, `getLocalPorts - error: ${error.message}`);
|
||||
log.error(`getLocalPorts - error ${error.message}`)
|
||||
return;
|
||||
}
|
||||
if (stderr) {
|
||||
logWarn(LogModule.APP, `getLocalPorts - stderr: ${stderr}`);
|
||||
log.error(`getLocalPorts - stderr ${stderr}`)
|
||||
return;
|
||||
}
|
||||
|
||||
logDebug(LogModule.APP, `Command output: ${stdout}`);
|
||||
log.debug(`sc ${stdout}`)
|
||||
let ports = [];
|
||||
if (stdout) {
|
||||
if (process.platform === 'win32') {
|
||||
@ -72,6 +72,7 @@ export const initLocalApi = () => {
|
||||
}
|
||||
return singe;
|
||||
})
|
||||
// .filter(f => f.indexOf('TCP') > 0 || f.indexOf('UDP') > 0)
|
||||
|
||||
} else if (process.platform === 'linux') {
|
||||
ports = stdout.split('\n')
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { app, ipcMain, shell } from "electron";
|
||||
import { logInfo, logError, LogModule } from "../utils/log";
|
||||
import { app, ipcMain } from "electron";
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
@ -9,45 +8,24 @@ export const initLoggerApi = () => {
|
||||
const readLogger = (callback: (content: string) => void) => {
|
||||
fs.readFile(logPath, "utf-8", (error, data) => {
|
||||
if (!error) {
|
||||
logInfo(LogModule.APP, "Log file read successfully.");
|
||||
callback(data);
|
||||
} else {
|
||||
logError(LogModule.APP, `Error reading log file: ${error.message}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
ipcMain.on("logger.getLog", async (event, args) => {
|
||||
logInfo(LogModule.APP, "Received request to get log.");
|
||||
readLogger(content => {
|
||||
event.reply("Logger.getLog.hook", content);
|
||||
logInfo(LogModule.APP, "Log data sent to client.");
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.on("logger.update", (event, args) => {
|
||||
logInfo(LogModule.APP, "Watching log file for changes.");
|
||||
fs.watch(logPath, (eventType, filename) => {
|
||||
if (eventType === "change") {
|
||||
logInfo(LogModule.APP, "Log file changed, reading new content.");
|
||||
readLogger(content => {
|
||||
event.reply("Logger.update.hook", content);
|
||||
logInfo(LogModule.APP, "Updated log data sent to client.");
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.on("logger.openLog", (event, args) => {
|
||||
logInfo(LogModule.APP, "Attempting to open log file.");
|
||||
shell.openPath(logPath).then((errorMessage) => {
|
||||
if (errorMessage) {
|
||||
logError(LogModule.APP, `Failed to open Logger: ${errorMessage}`);
|
||||
event.reply("Logger.openLog.hook", false);
|
||||
} else {
|
||||
logInfo(LogModule.APP, "Logger opened successfully.");
|
||||
event.reply("Logger.openLog.hook", true);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@ -8,16 +8,10 @@ import {
|
||||
updateProxyStatus
|
||||
} from "../storage/proxy";
|
||||
import { reloadFrpcProcess } from "./frpc";
|
||||
import { logError, logInfo, LogModule, logWarn } from "../utils/log";
|
||||
|
||||
export const initProxyApi = () => {
|
||||
ipcMain.on("proxy.getProxys", async (event, args) => {
|
||||
logInfo(LogModule.APP, "Requesting to get proxies.");
|
||||
listProxy((err, documents) => {
|
||||
if (err) {
|
||||
logError(LogModule.APP, `Error retrieving proxies: ${err.message}`);
|
||||
} else {
|
||||
logInfo(LogModule.APP, "Proxies retrieved successfully.");
|
||||
}
|
||||
event.reply("Proxy.getProxys.hook", {
|
||||
err: err,
|
||||
data: documents
|
||||
@ -27,12 +21,8 @@ export const initProxyApi = () => {
|
||||
|
||||
ipcMain.on("proxy.insertProxy", async (event, args) => {
|
||||
delete args["_id"];
|
||||
logInfo(LogModule.APP, "Inserting a new proxy.");
|
||||
insertProxy(args, (err, documents) => {
|
||||
if (err) {
|
||||
logError(LogModule.APP, `Error inserting proxy: ${err.message}`);
|
||||
} else {
|
||||
logInfo(LogModule.APP, "Proxy inserted successfully.");
|
||||
if (!err) {
|
||||
reloadFrpcProcess();
|
||||
}
|
||||
event.reply("Proxy.insertProxy.hook", {
|
||||
@ -43,12 +33,8 @@ export const initProxyApi = () => {
|
||||
});
|
||||
|
||||
ipcMain.on("proxy.deleteProxyById", async (event, args) => {
|
||||
logInfo(LogModule.APP, `Deleting proxy with ID: ${args._id}`);
|
||||
deleteProxyById(args, (err, documents) => {
|
||||
if (err) {
|
||||
logError(LogModule.APP, `Error deleting proxy: ${err.message}`);
|
||||
} else {
|
||||
logInfo(LogModule.APP, "Proxy deleted successfully.");
|
||||
if (!err) {
|
||||
reloadFrpcProcess();
|
||||
}
|
||||
event.reply("Proxy.deleteProxyById.hook", {
|
||||
@ -59,13 +45,7 @@ export const initProxyApi = () => {
|
||||
});
|
||||
|
||||
ipcMain.on("proxy.getProxyById", async (event, args) => {
|
||||
logInfo(LogModule.APP, `Requesting proxy with ID: ${args._id}`);
|
||||
getProxyById(args, (err, documents) => {
|
||||
if (err) {
|
||||
logError(LogModule.APP, `Error retrieving proxy: ${err.message}`);
|
||||
} else {
|
||||
logInfo(LogModule.APP, "Proxy retrieved successfully.");
|
||||
}
|
||||
event.reply("Proxy.getProxyById.hook", {
|
||||
err: err,
|
||||
data: documents
|
||||
@ -74,16 +54,9 @@ export const initProxyApi = () => {
|
||||
});
|
||||
|
||||
ipcMain.on("proxy.updateProxy", async (event, args) => {
|
||||
if (!args._id) {
|
||||
logWarn(LogModule.APP, "No proxy ID provided for update.");
|
||||
return;
|
||||
}
|
||||
logInfo(LogModule.APP, `Updating proxy with ID: ${args._id}`);
|
||||
if (!args._id) return;
|
||||
updateProxyById(args, (err, documents) => {
|
||||
if (err) {
|
||||
logError(LogModule.APP, `Error updating proxy: ${err.message}`);
|
||||
} else {
|
||||
logInfo(LogModule.APP, "Proxy updated successfully.");
|
||||
if (!err) {
|
||||
reloadFrpcProcess();
|
||||
}
|
||||
event.reply("Proxy.updateProxy.hook", {
|
||||
@ -94,16 +67,10 @@ export const initProxyApi = () => {
|
||||
});
|
||||
|
||||
ipcMain.on("proxy.updateProxyStatus", async (event, args) => {
|
||||
logInfo(LogModule.APP, `Updating status for proxy ID: ${args._id}`);
|
||||
if (!args._id) {
|
||||
logWarn(LogModule.APP, "No proxy ID provided for status update.");
|
||||
return;
|
||||
}
|
||||
console.log("更新状态", args);
|
||||
if (!args._id) return;
|
||||
updateProxyStatus(args._id, args.status, (err, documents) => {
|
||||
if (err) {
|
||||
logError(LogModule.APP, `Error updating proxy status: ${err.message}`);
|
||||
} else {
|
||||
logInfo(LogModule.APP, "Proxy status updated successfully.");
|
||||
if (!err) {
|
||||
reloadFrpcProcess();
|
||||
}
|
||||
event.reply("Proxy.updateProxyStatus.hook", {
|
||||
|
@ -56,7 +56,10 @@ export const initUpdaterApi = (win: BrowserWindow) => {
|
||||
|
||||
})
|
||||
|
||||
|
||||
autoUpdater.on('update-downloaded', () => {
|
||||
console.log('update-downloaded')
|
||||
|
||||
dialog.showMessageBox({
|
||||
type: 'info',
|
||||
title: '应用更新',
|
||||
|
@ -1,49 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import requests
|
||||
import logging
|
||||
|
||||
# Set up logging configuration
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
|
||||
def extract_frp_releases():
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
json_file_path = os.path.join(current_dir, 'frp-releases.json')
|
||||
|
||||
logging.info('Reading JSON file: %s', json_file_path)
|
||||
with open(json_file_path, 'r', encoding='utf-8') as file:
|
||||
data = json.load(file)
|
||||
|
||||
checksums = {}
|
||||
|
||||
for i in data:
|
||||
for asset in i['assets']:
|
||||
if asset['name'] == 'frp_sha256_checksums.txt':
|
||||
logging.info('Found checksum file: %s', asset['browser_download_url'])
|
||||
content = fetch_txt_content(asset['browser_download_url'])
|
||||
if content:
|
||||
lines = content.splitlines()
|
||||
for line in lines:
|
||||
if line.strip(): # Ensure the line is not empty
|
||||
parts = line.split()
|
||||
if len(parts) == 2:
|
||||
checksums[parts[0]] = parts[1] # 反转映射关系
|
||||
logging.debug('Added checksum: %s -> %s', parts[0], parts[1])
|
||||
|
||||
output_file_path = os.path.join(current_dir, 'frp_all_sha256_checksums.json')
|
||||
logging.info('Writing checksums to file: %s', output_file_path)
|
||||
with open(output_file_path, 'w', encoding='utf-8') as output_file:
|
||||
json.dump(checksums, output_file, ensure_ascii=False, indent=4)
|
||||
|
||||
def fetch_txt_content(url):
|
||||
logging.info('Fetching content from: %s', url)
|
||||
response = requests.get(url)
|
||||
if response.status_code == 200:
|
||||
logging.info('Successfully fetched content')
|
||||
return response.text
|
||||
else:
|
||||
logging.error('Failed to fetch content, status code: %d', response.status_code)
|
||||
return None
|
||||
|
||||
if __name__ == "__main__":
|
||||
extract_frp_releases()
|
File diff suppressed because it is too large
Load Diff
@ -1,440 +0,0 @@
|
||||
{
|
||||
"013179d5fd3a5c1a48cf8b2da99c8d48f0f95b96907ad77483deb4d8615578f2": "frp_0.61.1_android_arm64.tar.gz",
|
||||
"403a0ee5e92f083a863d984b7af1e9d70ba2aaa28e87f42f1fe085adf76b8491": "frp_0.61.1_darwin_amd64.tar.gz",
|
||||
"3e65f13a17a284bd6013e6bb6856bc2720074cea6094cc446c1f4c3932154c2d": "frp_0.61.1_darwin_arm64.tar.gz",
|
||||
"cb5edf41be38bd47bd7f68210c34500f5dac74423d8084ed8d7e0f3bce050cf5": "frp_0.61.1_freebsd_amd64.tar.gz",
|
||||
"bff260b68ca7b1461182a46c4f34e9709ba32764eed30a15dd94ac97f50a2c40": "frp_0.61.1_linux_amd64.tar.gz",
|
||||
"8c7ebb07916e69d959d2a90f80900850c41b6dc1decdb87ccba4bc2f3574f855": "frp_0.61.1_linux_arm.tar.gz",
|
||||
"af6366f2b43920ebfe6235dba6060770399ed1fb18601e5818552bd46a7621f8": "frp_0.61.1_linux_arm64.tar.gz",
|
||||
"f956807c4af5cc0fa3b06f2365f342abcb4b8f780b6f948db5a9d8c02bb3e7ba": "frp_0.61.1_linux_arm_hf.tar.gz",
|
||||
"e4b096fe86b09fa4b2c292b9c833dd9d3f322cbc4b67e8f73d40267f9451edc2": "frp_0.61.1_linux_loong64.tar.gz",
|
||||
"8c0e0c1384a0e8601153f767392c23c5cd90e8112c522d4d0852c8b477909f8b": "frp_0.61.1_linux_mips.tar.gz",
|
||||
"96d94c7676aad45b3cb1fb9bb7ee8cfc36ba4db9803fe77636d4bf711e79e0fb": "frp_0.61.1_linux_mips64.tar.gz",
|
||||
"febb306ffbaa7a74e27fdd37919a342d148f27d33afc13a0ecbec73d68e5e204": "frp_0.61.1_linux_mips64le.tar.gz",
|
||||
"539d8d91091410abbca80efae36f8a693a9132094f2340ff781b390c7eac5a50": "frp_0.61.1_linux_mipsle.tar.gz",
|
||||
"7f75d6fc124e94da1703fa94cd9fec87bfe9c2a8c6c6c5bdda53266d5c0b82ca": "frp_0.61.1_linux_riscv64.tar.gz",
|
||||
"e0094cd0baf03d5ff9ce9739199406871ad8788cf51e766f00ad3a9e7a836f3a": "frp_0.61.1_windows_amd64.zip",
|
||||
"099914b6f66b1d983a9402e3a9a116bccc683c877f6dbfbbb5c0f171b584675f": "frp_0.61.1_windows_arm64.zip",
|
||||
"6e4e4c3b73b4637658e8a19bd214efa67d24d18213d0470d8ab564b279935ab4": "frp_0.61.0_android_arm64.tar.gz",
|
||||
"731bab6f15ac2dcfc63d9b3c2040ee68c465f8520c5369a8a6f37c467568280b": "frp_0.61.0_darwin_amd64.tar.gz",
|
||||
"dafa23f2424ec1926f689a4d36d37e6e5b0050858755d2977a9e6171e1c8fb25": "frp_0.61.0_darwin_arm64.tar.gz",
|
||||
"bc257408cc57d5982638f6aecda0ff2d3d46597b5b12d780c5135a8b72ea38c5": "frp_0.61.0_freebsd_amd64.tar.gz",
|
||||
"720a9fe2a3299346572544909a78c023344c88bde13c55b921e298e8c5ded21f": "frp_0.61.0_linux_amd64.tar.gz",
|
||||
"38b2d2f9a46b636dcdf4d656373de86f6c869da98a4e323bd9587989c1c06db0": "frp_0.61.0_linux_arm.tar.gz",
|
||||
"8d54b8faae5df02268bd784f78a155494893c6eb00070a185022198c1997ec7f": "frp_0.61.0_linux_arm64.tar.gz",
|
||||
"f151b5087870a72faa13c026336f3a6b97f0df2dcae3f3b122c43e604772cd23": "frp_0.61.0_linux_arm_hf.tar.gz",
|
||||
"19928aeeca3806606958ea4843390cd427c05631e00afbd91b9a48855a18ba79": "frp_0.61.0_linux_loong64.tar.gz",
|
||||
"601a8b19647db6258381d388861f4d64081fabae419788ae16bb8cd46390f2f7": "frp_0.61.0_linux_mips.tar.gz",
|
||||
"fbbbab9339c87cfff52b4a7cedaaca0623921599fee10f845139ad7bdd6aa384": "frp_0.61.0_linux_mips64.tar.gz",
|
||||
"b37c43ec11ae2ab50610eaf0251c9b3f694291d529afc757cd4645f7b8eb111a": "frp_0.61.0_linux_mips64le.tar.gz",
|
||||
"4defa5cd6372d06188284f61fee90865222d8297a51005253dd6a5161ea538e4": "frp_0.61.0_linux_mipsle.tar.gz",
|
||||
"0131890f58576631cd3dc8ea937ccd18f70f3162fb39840f56df8339f5799138": "frp_0.61.0_linux_riscv64.tar.gz",
|
||||
"ab7ae0633df538b35d0490212eabfd6d3a0051e9e446ae0053c9f63921a8c46e": "frp_0.61.0_windows_amd64.zip",
|
||||
"939f3f3c78be9221a9bc3a2380c1168fb5797ba66504d7188890bf93838ee6d5": "frp_0.61.0_windows_arm64.zip",
|
||||
"4937324806d0c5aed7720d4e43f2a3fe1fb0e40d9b1b1c18c33417568d168c84": "frp_0.60.0_android_arm64.tar.gz",
|
||||
"e0109b707168c98b95d2c0382637d0e8e83918d8fe0cdcb1794999f1750d33b9": "frp_0.60.0_darwin_amd64.tar.gz",
|
||||
"e87da68fb5d3f9af6e7209b0c64cb6f22d3c9c3adfaad509c78c263ecaed7906": "frp_0.60.0_darwin_arm64.tar.gz",
|
||||
"05222274b4577f71087e57273223a86e9dacfb6c53f9371c0c0e67f371aff146": "frp_0.60.0_freebsd_amd64.tar.gz",
|
||||
"be5e957d20358d9998407204a03abc27d894f3cd61691f8d8ebe1e5f0eea4ca3": "frp_0.60.0_linux_amd64.tar.gz",
|
||||
"e9d66ce99b14cadef5e850a6ca63b131486898ecaee580e6b90c5418ac4ccf72": "frp_0.60.0_linux_arm.tar.gz",
|
||||
"bd52be08eaf872b07a48d9a3f46d7dd8f7e9340d76f12d316f39717f05d644e1": "frp_0.60.0_linux_arm64.tar.gz",
|
||||
"75627af508bb31c37d13f6559b4acccdd6779304d56324ffc1cc737037055b40": "frp_0.60.0_linux_arm_hf.tar.gz",
|
||||
"b58952173a2274759059ffa0d7ed3997f953852cddd4144bc3002611e23811a5": "frp_0.60.0_linux_loong64.tar.gz",
|
||||
"9e3a587a164b24613fc587d3d17270d1b6623729b56135e6d63eebf56c41bb4c": "frp_0.60.0_linux_mips.tar.gz",
|
||||
"2747e75da664dc9ca54baea63e499a36c0bd3ee34fc7950cec868e7502f7b192": "frp_0.60.0_linux_mips64.tar.gz",
|
||||
"bfa63bb8f3d5f96033e5c58d41c8fb7bcdbbdbb6f790a77de5b84bf466f24529": "frp_0.60.0_linux_mips64le.tar.gz",
|
||||
"ad7fc34df2e951a8de187ed7ae667dcb85bae68009f33a2a607da1b8a4caea1e": "frp_0.60.0_linux_mipsle.tar.gz",
|
||||
"0f29c256bc20942ea5a19d8e6d3caacdd2f4d5134e632c50bfb384046666ae28": "frp_0.60.0_linux_riscv64.tar.gz",
|
||||
"5243b70972af6416af85779b943945a80050b1733b31ab41fa94f3617f655492": "frp_0.60.0_windows_amd64.zip",
|
||||
"1a41b2226a7478d3d86e275ca50f43e3587874973141bc43b552d258530f0443": "frp_0.60.0_windows_arm64.zip",
|
||||
"5d3b92e28e0e293d87d32540bcc724a43ba2f4ebcc0549a1411d733e62f561cf": "frp_0.59.0_android_arm64.tar.gz",
|
||||
"a784616e949fd9a00dc457e1b2e5fac871a9e420206f50200b5751f967af2d7d": "frp_0.59.0_darwin_amd64.tar.gz",
|
||||
"54886307b97f60d698ddc0dd940514fc7653764580918f60a470a758eebf2323": "frp_0.59.0_darwin_arm64.tar.gz",
|
||||
"bac63201e778139da931916328daf53ca575ab88f391c304b12550f04e63eb87": "frp_0.59.0_freebsd_amd64.tar.gz",
|
||||
"54927eb5c07bd51850771ea55ca23338cdebdbe227d1acf7e2f0d530bd5e09c7": "frp_0.59.0_linux_amd64.tar.gz",
|
||||
"2c958e363acedd08be939016ec12d17f66470e4da601b21247af1b31ea74e606": "frp_0.59.0_linux_arm.tar.gz",
|
||||
"5b23e876071521d4f745f60fe09e81753cf84df22a0c7ab47d3692a09d758d01": "frp_0.59.0_linux_arm64.tar.gz",
|
||||
"ca0a66dc4bd732f7c39116c9bce55a2b416898d3d1584b7eee1795a879779636": "frp_0.59.0_linux_arm_hf.tar.gz",
|
||||
"a18840130e7056e9c8854ad1835d4b58b8df2c8151459a73dffdabe38b887b2b": "frp_0.59.0_linux_mips.tar.gz",
|
||||
"e99827b49e17224b07c6c0af3876aa98771813aeead4d98d4e3c71a657c29211": "frp_0.59.0_linux_mips64.tar.gz",
|
||||
"d32ae7a736082720b1672006783b5c17309287ee13bab552dddc99e17537bed7": "frp_0.59.0_linux_mips64le.tar.gz",
|
||||
"6dec7136bc1ef26e14e5190d1c4809088f4173ffd140daf84d623bd180949eaa": "frp_0.59.0_linux_mipsle.tar.gz",
|
||||
"e373ae9a57a286343e9e7fe1c4d1d4ff676680a0aea7041b75dde1ee24494158": "frp_0.59.0_linux_riscv64.tar.gz",
|
||||
"a53405994ad03b245b36ab98513c8b7370bc0a36be128b057dc687354d6464da": "frp_0.59.0_windows_amd64.zip",
|
||||
"f9978e5f98b22b1171b03925e73b2601aa485cdd4993dc7ef82557fa0d173ea6": "frp_0.59.0_windows_arm64.zip",
|
||||
"c31e71a4b845e8ebc34bf5ddcc116a8230322101fff2418de8be9e46f1c6047e": "frp_0.58.1_android_arm64.tar.gz",
|
||||
"e3a15197675afe2dda9ca7588d76d6597c30c7d55ed8fd2ae8c9d7425c8ce82e": "frp_0.58.1_darwin_amd64.tar.gz",
|
||||
"be615731a6f34bf92c44765abe4a7df25d82226f255b1d1fda5f36a0ea083ab4": "frp_0.58.1_darwin_arm64.tar.gz",
|
||||
"73576032ee89de285142e6d74f7bb6fb8cc5c373961e4c8910ded6aa13c3e88c": "frp_0.58.1_freebsd_amd64.tar.gz",
|
||||
"5bd9f8860b580ed9c42eed1c99dfaa03b196d0f68007dca088f6c098d498430d": "frp_0.58.1_linux_amd64.tar.gz",
|
||||
"44c59c70aaefe48dd9b0427c7e86a6a24c7e83fc31e38a7a2885dd1d53c05091": "frp_0.58.1_linux_arm.tar.gz",
|
||||
"25a77f4d7f4c5efeeaa89ed65b951a19014e79baac1efcbd57f0598b3ba95fd7": "frp_0.58.1_linux_arm64.tar.gz",
|
||||
"d1bbe041d05f48adebdd2a8b6754daf1062f66a8139a32589a10a31890348d49": "frp_0.58.1_linux_arm_hf.tar.gz",
|
||||
"8a30318eeeb996364eefe40f8f946250fe709a1c487f795643c85a7b53cf86e5": "frp_0.58.1_linux_mips.tar.gz",
|
||||
"ac445e3f75eca9bd33388ebe7c33d6124f5c3fa0c7a3419b5285dabe2e5415b9": "frp_0.58.1_linux_mips64.tar.gz",
|
||||
"291d4fb93ccaf8c79087a21744a06716b9899b1bb71e9c2cb48c487adcee3bdb": "frp_0.58.1_linux_mips64le.tar.gz",
|
||||
"db325305379dd39a82baf0910e5d01a426726231ff8775c2c6295232500d6bbb": "frp_0.58.1_linux_mipsle.tar.gz",
|
||||
"710638bde211638e7cd096811f77b994cee0081cb0c9c80c7b48d92ae2452f28": "frp_0.58.1_linux_riscv64.tar.gz",
|
||||
"7aa26c8c082c2fc7960b422811c1bbcbfae219b2a108d6604a645e8dc7476337": "frp_0.58.1_windows_amd64.zip",
|
||||
"14cb8785617bdac2af87133061ec9a303824044bc8a64850db72e6b150bf0e71": "frp_0.58.1_windows_arm64.zip",
|
||||
"d3845380ea2f5291f42c382d6d64afae694673d4a41b86be8d4c08e9f03c5b33": "frp_0.58.0_android_arm64.tar.gz",
|
||||
"7ddf254bbce297e1ffbae0fd696e9a3a3414cdc8a2c270b00b59f45c19b8c5c5": "frp_0.58.0_darwin_amd64.tar.gz",
|
||||
"0bd38fb57e46d13bd1fe7469a875a3123e298f4d0485feac8dd7a80d885f3676": "frp_0.58.0_darwin_arm64.tar.gz",
|
||||
"a347a2e294986018591c43b029d85a6911c58a60df060fdb0de11b1d70bb916d": "frp_0.58.0_freebsd_amd64.tar.gz",
|
||||
"09a6a170070ce02d7de6afa941d341a2caad0aa8e09fb840a64da4acba24d73a": "frp_0.58.0_linux_amd64.tar.gz",
|
||||
"5e4b8b5ac36fe62f26ef37430aa56b11e562b046849796c51a61fd90251af18a": "frp_0.58.0_linux_arm.tar.gz",
|
||||
"ceef6df44f3a64b4a2076dfbb0ac02a3750b48fa0ac568478190ef477d4f2ec9": "frp_0.58.0_linux_arm64.tar.gz",
|
||||
"379ad22bf406186d8665e9be73b8b0600625abf7e8f6403fe3ef9d59e97c011c": "frp_0.58.0_linux_arm_hf.tar.gz",
|
||||
"3ef58b278d97290540912692f053c1a5d48fc382a85a774f0b8b3ed060af067e": "frp_0.58.0_linux_mips.tar.gz",
|
||||
"2b90360468e67cebb9a5e656585da0e9de84ee1a811ee9d02bb275b9e736bf7e": "frp_0.58.0_linux_mips64.tar.gz",
|
||||
"3519353b094158f59f1f10a3bdbac473cf04bd09f572a0225c2ae5f545ee45bd": "frp_0.58.0_linux_mips64le.tar.gz",
|
||||
"3ccccc5f76b3750137e9227ee904635266a0a434f4d6fae7d431382477372388": "frp_0.58.0_linux_mipsle.tar.gz",
|
||||
"f024440f7987fc845814ecdf058f4363690a53850c241af5c7c01fba6632c78e": "frp_0.58.0_linux_riscv64.tar.gz",
|
||||
"f827466f3f0e256350b66881721bd52753ef5243765e6c22347ec83a271d555f": "frp_0.58.0_windows_amd64.zip",
|
||||
"8bb04979969040c4c495450bc7921b23054f2d7418f2d8378bd65ecbd8b381b2": "frp_0.58.0_windows_arm64.zip",
|
||||
"e20d90b0670c637a65125f89467170efb3fc227a78f44ee585a6d3fb55b6a881": "frp_0.57.0_android_arm64.tar.gz",
|
||||
"9a12912bfbf7dad0ebe5fb3b0229b318a8670d078137f2384f81c1aa87bc0fb0": "frp_0.57.0_darwin_amd64.tar.gz",
|
||||
"b18153bc7a6d6627f402380a6e5ac01b631207df54d7fcc0d89a8f6f81521401": "frp_0.57.0_darwin_arm64.tar.gz",
|
||||
"22df14e317c351bda4bfaf256c46b6ec281304135ea24c00bb2a71a5e14d4f22": "frp_0.57.0_freebsd_amd64.tar.gz",
|
||||
"3fd70ccfab20e75b8517627ec58e30b33003a24ca4629ed42650ef1b98f17e7d": "frp_0.57.0_linux_amd64.tar.gz",
|
||||
"3c9e03e28899ba18e42f51006f7d94192fbae009885fd91cfc75b354cffebf58": "frp_0.57.0_linux_arm.tar.gz",
|
||||
"e2f75360702bcdc390997de7b2557f21a1f28d7ebd4d1ca74cf2e38849185bcb": "frp_0.57.0_linux_arm64.tar.gz",
|
||||
"795defca4853f7cded6625d792eae33b45987856b961a82c8b6cc44a8d0b3bc7": "frp_0.57.0_linux_mips.tar.gz",
|
||||
"a41466714ba9463978139a62d241893a034425235b61ecf2efd868857e1c83b5": "frp_0.57.0_linux_mips64.tar.gz",
|
||||
"d5d2ee272caa314a731dcc59ed4474c9f34953c617e8c29fdd86ea8c017f2e91": "frp_0.57.0_linux_mips64le.tar.gz",
|
||||
"2fae90ae2544f8b46582cfb7d46984d837b193601b35aa9d63c2f4f52007e32b": "frp_0.57.0_linux_mipsle.tar.gz",
|
||||
"4e155fcf4f0c7e186ccd2be94a2e036bb62790c9bc00d9145a2999b5e3f38717": "frp_0.57.0_linux_riscv64.tar.gz",
|
||||
"18b6a345f7d4fb9250b8d751a99f58a0a2daace02a1f7a4e7bb567237e681335": "frp_0.57.0_windows_amd64.zip",
|
||||
"6fe6b708ab65d61293fb7f1669a3dceab6d8a7d06f9f9b93db68025873f51c44": "frp_0.57.0_windows_arm64.zip",
|
||||
"ef44189d246b4a95e0eabbf1d6d86ba94002e6f2bb5eefca8e3e8b8292abc085": "frp_0.56.0_darwin_amd64.tar.gz",
|
||||
"93afeb34c835796508383b70028216eb3d43b2bf63bb3f7493acd1ec533d588e": "frp_0.56.0_darwin_arm64.tar.gz",
|
||||
"531be6e910202087c61e10e57e28eee9a079fee380b8a42432de55d570bb25cb": "frp_0.56.0_freebsd_amd64.tar.gz",
|
||||
"084d3c601a9f5d100ad3be26d94b643f2843fa64dcc5f2f2057c612bf7f9d4f1": "frp_0.56.0_linux_amd64.tar.gz",
|
||||
"cf5cc61f68d705860538b8d3e865ae026a7b27e4da8c1c1a3f50c5e7827cd097": "frp_0.56.0_linux_arm.tar.gz",
|
||||
"8805d70a692b0c5e20271214af085ffc3d8ea2176ce5dbe06fd6e4de59d8206f": "frp_0.56.0_linux_arm64.tar.gz",
|
||||
"03fce0574a2df7993efff8bf3d1e45250b08692081cff53dfd266745db772f27": "frp_0.56.0_linux_mips.tar.gz",
|
||||
"db2975501126fc0f61097acdff7484655e5d37b01de8c509c2c5e0e88591fb42": "frp_0.56.0_linux_mips64.tar.gz",
|
||||
"4cdca1cc3d298a5e6628ec40e174882e26039d953492eaef6c0d25cef065ace5": "frp_0.56.0_linux_mips64le.tar.gz",
|
||||
"0679e059dfca6cd022caf808ffe2709207377463a31ccddee1bcb75c161b341c": "frp_0.56.0_linux_mipsle.tar.gz",
|
||||
"fdbcc2a7d73552e690bc9ca7fccb69b9efdf10fc4d78f0f7c63b14a9129bb116": "frp_0.56.0_linux_riscv64.tar.gz",
|
||||
"572872fec378f423b141faa205b44faa07bbf06f7272b0a6a3235c7992a69998": "frp_0.56.0_windows_amd64.zip",
|
||||
"b1c9ee1dff229639c43c60e39a6023798b5c96ccd38df7e3edd41cfb6990c90a": "frp_0.56.0_windows_arm64.zip",
|
||||
"efd2156b1477d88b8ce1d9428cdeb1689bd12cefb4b31ca81b70eb7d65e22e59": "frp_0.55.1_darwin_amd64.tar.gz",
|
||||
"220583e20edd98369dbe929d215a387ceea937b0e0637f62558506b2a6c603a2": "frp_0.55.1_darwin_arm64.tar.gz",
|
||||
"c20f8abf5e0933bfd88fa974ad3a005c72f494aafc021916927774ab0ce6ca46": "frp_0.55.1_freebsd_amd64.tar.gz",
|
||||
"4d13675c330ca07d532f7a2ebc72fdc011487fe318f2ee645842a3fa4b23c966": "frp_0.55.1_linux_amd64.tar.gz",
|
||||
"bc283cb6e280e5fd5089216c8362003235dcf371e9f99bbc14462a0ef05c0b53": "frp_0.55.1_linux_arm.tar.gz",
|
||||
"f14655042086ef4653c0351a6464fb7d73473baf26e15a5f59c298bd3df23d1c": "frp_0.55.1_linux_arm64.tar.gz",
|
||||
"550e7d04aa4d00fb81b1cd566c58b056a3da8bcfd05631e5f4edd673232b9062": "frp_0.55.1_linux_mips.tar.gz",
|
||||
"8a0f1ef0b8723089613e2754d965ac9059eed027064bdd484f417fa6f5756d12": "frp_0.55.1_linux_mips64.tar.gz",
|
||||
"21b32cdaf6e4c74a88a0b6c3c377a3d40a23f73c0313625fa63ba4a6542616fe": "frp_0.55.1_linux_mips64le.tar.gz",
|
||||
"b09684adfae58733bc12cd0ee3cf1e20d6b888c3e5280cf9f9e7a6467cf87a71": "frp_0.55.1_linux_mipsle.tar.gz",
|
||||
"52fabafac257ef8ca28e53cc4f210789cfd882946d0f9d2f9457d63f0344a602": "frp_0.55.1_linux_riscv64.tar.gz",
|
||||
"eeb4247038f58d6b89bd5608782489eeaa7bcfb83d61b5475284ab612978b328": "frp_0.55.1_windows_amd64.zip",
|
||||
"b48943e9641fde4b91e0032fa031599fdbe3f9cebdd8612cec9e3477aecf2866": "frp_0.55.1_windows_arm64.zip",
|
||||
"b509e7d50b164aaa62b30efb189caf965615ce266d51c243e494bca14d2f2864": "frp_0.54.0_darwin_amd64.tar.gz",
|
||||
"dd8057968d3560e9ecb42b2ed50b796ec09573d5263f689c8e0633a8b8a7127a": "frp_0.54.0_darwin_arm64.tar.gz",
|
||||
"ed25f0c61c45c7f013f2f5ef9194cb2854805db9c692f656e2b30a6ad1681436": "frp_0.54.0_freebsd_amd64.tar.gz",
|
||||
"13102618f84a2efa07a90733d9bae72e48b897c29f4df4b38bdacebb99517e52": "frp_0.54.0_linux_amd64.tar.gz",
|
||||
"0ccc051693da612b7c4eed265598d3c8878019cb21e6ec9e3869f94b93e6ca80": "frp_0.54.0_linux_arm.tar.gz",
|
||||
"a3f01a59bca7cb330bf680019595bbbf5f8167494fab4c46eaaf836fdc3a1902": "frp_0.54.0_linux_arm64.tar.gz",
|
||||
"1a1a729fe607c59dae787bc5322efcf8cc5a9e87623c6d10e2a08531829bb9fb": "frp_0.54.0_linux_mips.tar.gz",
|
||||
"24fccce2e9c6684480bfd8ac0e9ea3e36d4203922fa5a39ae9f63bc0542f68f5": "frp_0.54.0_linux_mips64.tar.gz",
|
||||
"18ee2a78c352eeceb07d55ba572955af64b14282914fe77edf632baf4ce0f967": "frp_0.54.0_linux_mips64le.tar.gz",
|
||||
"e73e6a2bc3fc1900fb2810bf53bed0471149fb07c60917027661d9d654c0f6e8": "frp_0.54.0_linux_mipsle.tar.gz",
|
||||
"3961db6d3c5951da49b40cfdae22c8fd53ea87a2ff97245d8aadd4d4206c6fea": "frp_0.54.0_linux_riscv64.tar.gz",
|
||||
"95f0d8c8f4781fc8e42b7d644024c647032e3f6cd0ffe425e8f7d5a46d601557": "frp_0.54.0_windows_amd64.zip",
|
||||
"61b4d21b669ceb671b298a4ed4aa3c70b33d6e3e4281f7417336a76f684424ca": "frp_0.54.0_windows_arm64.zip",
|
||||
"91b1b306c1a538dd6d60857a1da9019241034bcaf0cc19e0c07abfaa8f6a8f75": "frp_0.53.2_darwin_amd64.tar.gz",
|
||||
"76d2a7bc7ceb5f542ed5be5208f68253261a36d1f4206fc4689296d9033a59a2": "frp_0.53.2_darwin_arm64.tar.gz",
|
||||
"3f9462f9c7aad6fec22159529b1db7382acd7254605894fbc44c7a7c464e148b": "frp_0.53.2_freebsd_amd64.tar.gz",
|
||||
"df7356db409cc406294211063bf387a8b590289370811b1d10d6fdd1023c3250": "frp_0.53.2_linux_amd64.tar.gz",
|
||||
"0f7acf26d92d39a2e3965ee91bf60e7c331844a1d7e81078ede526cf0459eccd": "frp_0.53.2_linux_arm.tar.gz",
|
||||
"e67faadd41e6236f2bd67d35c9dfd807ff2941027686632f6f4c339dea8ef263": "frp_0.53.2_linux_arm64.tar.gz",
|
||||
"5b8d4fddcbe0c9e1e82bf8ca30b97bde3fff668741e49a260d6c13c55584bbc9": "frp_0.53.2_linux_mips.tar.gz",
|
||||
"ec8938be2d1b535eeaf7ba803dae2b6fa1059c6106791d59d98600928dfcc057": "frp_0.53.2_linux_mips64.tar.gz",
|
||||
"0950dbcd22a110b50c7636f2ff7ca73ee120568d375d75539546c6590cd75ce9": "frp_0.53.2_linux_mips64le.tar.gz",
|
||||
"f2f9c488451676a58566f6daf2a8a1c85aea193abdc7d7241ef0e12675238bc9": "frp_0.53.2_linux_mipsle.tar.gz",
|
||||
"351b90825fb48695f36208f0e6cfbbd53f9539306119b5ca0aeb949bd255066a": "frp_0.53.2_linux_riscv64.tar.gz",
|
||||
"043cd981e81f756123ea4501569ad8d1fbb8166d1046b349ca423aa6ddc0ce31": "frp_0.53.2_windows_amd64.zip",
|
||||
"26eb992318437fad2d122ef76cfb3086f1339201486a1cdec910fe1a457ac383": "frp_0.53.2_windows_arm64.zip",
|
||||
"2c02d8f219e83bea4bb4c9ddf1222bdabc068f656992e967dc702e70a1aafd80": "frp_0.53.0_darwin_amd64.tar.gz",
|
||||
"a148f12a5261ef3186322b08cf1b1907d987505ec5485adb290a350bb2083f63": "frp_0.53.0_darwin_arm64.tar.gz",
|
||||
"8b0067e658dcbb21313ae8192aa7e1d364af8e96aeb7893ba7422ea0844e8bd5": "frp_0.53.0_freebsd_amd64.tar.gz",
|
||||
"662d62af7744b9b639b3473bbdd2c4c70dfa5ac5fe1d058d13ce3cc7ea059500": "frp_0.53.0_linux_amd64.tar.gz",
|
||||
"e33075389b77f94a816ac45bf1d0ce2b540fd98dafac9828602625088967762f": "frp_0.53.0_linux_arm.tar.gz",
|
||||
"1d5b17f54911bc22816b0d72b32c258b259eb912d9d0484fdc949a315f5a5d42": "frp_0.53.0_linux_arm64.tar.gz",
|
||||
"f0439788bbeda72664259defbc0edb12825cbf2928c922e06103b7b715bae88a": "frp_0.53.0_linux_mips.tar.gz",
|
||||
"32665745aaf03d263a9ce87f0ea7a17eb3476328c25c1a1fcccd0925934f7313": "frp_0.53.0_linux_mips64.tar.gz",
|
||||
"ad977caa79c00c082206f46f521b8f99a44a051425dbb69ec9da1a152aac6279": "frp_0.53.0_linux_mips64le.tar.gz",
|
||||
"718c0f0820f65782bc19af479f2406c9654fc564b9999a0936581b4ed1d91bb2": "frp_0.53.0_linux_mipsle.tar.gz",
|
||||
"0e8f1915a1e2b1b2d37b11e831e49fb5f5fc2a14eea086f7ea5a1e4112095728": "frp_0.53.0_linux_riscv64.tar.gz",
|
||||
"dfd7bc3410c018dc8bcf897696ddfb10e7aaf5a584b8220ae3949ec87205ea4c": "frp_0.53.0_windows_amd64.zip",
|
||||
"23bafd6bf4ac0e631b37bcdc68827f4b36f06c3dcf0bd754f5d0f9acb4606a3b": "frp_0.53.0_windows_arm64.zip",
|
||||
"07a0651b2053508bab9370df884096effa653cb24cfd8c454c438b15971ece63": "frp_0.52.3_linux_riscv64.tar.gz",
|
||||
"0bf96f473385bbeb64faad3caec3ad721187b328f2228820e49838e187da0e22": "frp_0.52.3_linux_mipsle.tar.gz",
|
||||
"24395170dfc41544eceeb78529c8de5b57b65250c27a02e058cd013e6f66097f": "frp_0.52.3_windows_arm64.zip",
|
||||
"25431755a121c12dab3c28fec18eaef027a73aa5e9780b33f6801e152e42ab36": "frp_0.52.3_freebsd_amd64.tar.gz",
|
||||
"3fcf04657f8efd6c6418047bb8c219878c913c4bdc678a8c4bbc8a49d3a389d1": "frp_0.52.3_windows_amd64.zip",
|
||||
"46b6b8e83ccbbbc2e639c852dae9a41e79f8523d444fe39f9d8f7cc5e7661081": "frp_0.52.3_linux_arm.tar.gz",
|
||||
"5e041b19ba9ca6a5255679b353099946065edfdf951d807db2587fa8c95b1447": "frp_0.52.3_linux_amd64.tar.gz",
|
||||
"8e05baa844d928b6239bd9f43cd3e065fc2af971930bc6344e2c899d7eea14db": "frp_0.52.3_darwin_arm64.tar.gz",
|
||||
"9aab5a4936295d13f2602c8e087fd789a7910b3b3c9a47b9fb799ec99020192b": "frp_0.52.3_linux_mips64.tar.gz",
|
||||
"a249c503a622599ba68330f323de22a457e058157cb8e38cd3e59581993c03d2": "frp_0.52.3_linux_arm64.tar.gz",
|
||||
"b64b34521d1942f05b9224bb21d025af5c0ae99fa2e2dff635f26f91d91a6188": "frp_0.52.3_linux_mips.tar.gz",
|
||||
"c3b011e15c03348592d4a2adcdb90994e7ed29a43f572945505a429c12645215": "frp_0.52.3_linux_mips64le.tar.gz",
|
||||
"c992b9a8a53c53465f035d5e254ecc1a9455f260fd110fe1600d5da4a37df413": "frp_0.52.3_darwin_amd64.tar.gz",
|
||||
"05e2ba6184dcebe6fa334c2a1d4534433e8ff9372636ff98eef96e414212903c": "frp_0.52.2_freebsd_amd64.tar.gz",
|
||||
"11f2af35bdaa799a38a180a1b73083d68843cf731ecea118a33597a14289589e": "frp_0.52.2_windows_amd64.zip",
|
||||
"5ad396bc221aefa47d1192d6df11193240891ea3a88d0f0b941e1cb2967e2a01": "frp_0.52.2_linux_arm64.tar.gz",
|
||||
"6add94e2916fd776bc2fd62a01fa6fd282f040e2f05ba42962e823eac821ae81": "frp_0.52.2_linux_amd64.tar.gz",
|
||||
"8c47d8f1ad960d0f0459bd0fae7bc33c9266943d04549145b969c9107c59703f": "frp_0.52.2_darwin_amd64.tar.gz",
|
||||
"94169b8d725d30bb0ddf19db73d18b99544dcc52521507419eb7fb42823ea8ac": "frp_0.52.2_linux_arm.tar.gz",
|
||||
"b09d38e5eba230a6bb04f144f5d32d26ce69f1424bbbb1058d43c712ff558679": "frp_0.52.2_linux_mips.tar.gz",
|
||||
"bc886aea03ddb2d4201501904a25816ac962cd3fbe6bc7fab3ca05357069666d": "frp_0.52.2_linux_mips64.tar.gz",
|
||||
"c32b3159a8aa089b08222987a32b9856c046c276898613c75eec62d370df7e01": "frp_0.52.2_linux_mipsle.tar.gz",
|
||||
"c7b22ed0a87596cd839b555e4992d80691359e75409063b6dca2dda96e7da480": "frp_0.52.2_darwin_arm64.tar.gz",
|
||||
"ce70a9a044271be4336d7376aa1d5c5f8de8497b1e284b083f6d2184d6f57042": "frp_0.52.2_windows_arm64.zip",
|
||||
"dc3220af2b22469da26209d4b376858c11160127e83bce09f85cd0c27a44d5d0": "frp_0.52.2_linux_riscv64.tar.gz",
|
||||
"f1985ce963979371360df27054ba07df4d4ee35338880bed83ef609a4648c420": "frp_0.52.2_linux_mips64le.tar.gz",
|
||||
"076d9ce5c8644dbeb313e2d90349ad33d3b718b2701899480573266b3f6f0e6a": "frp_0.52.1_linux_mips64.tar.gz",
|
||||
"136cc6be28c798b2493875f498b5956a876c24cdbd028773aa9194c8bd846442": "frp_0.52.1_linux_amd64.tar.gz",
|
||||
"13f227bc915c43961e1f3831f155c6934e7d5a65434af3b29bf494b1d5d276b7": "frp_0.52.1_linux_mipsle.tar.gz",
|
||||
"1b3c61129cf7b45ad41a6b297f4425b9e700cf6302c8969232c7587ae7e727d9": "frp_0.52.1_darwin_arm64.tar.gz",
|
||||
"69c08bae93e16aaf57debbe2b10df6824f5dfef32ce21b5d57d750b0698999ee": "frp_0.52.1_freebsd_amd64.tar.gz",
|
||||
"73f3e7037e5f06e8f6fc30aa47aabbc815b4173decdcab149c647126a4aa6370": "frp_0.52.1_linux_riscv64.tar.gz",
|
||||
"a7626329b690c269d640555033e156a55cffb967f11556eb782ff130d0ad7982": "frp_0.52.1_linux_arm.tar.gz",
|
||||
"aff5412e89e7164b5083909f2b5a81d8edaa644a3bb6ef696843a6ee0d129fc3": "frp_0.52.1_linux_arm64.tar.gz",
|
||||
"b2cb915a6e66c99fcceceae07b08d28002c575a3bc2c6aa8ea88c9ae45294be3": "frp_0.52.1_windows_arm64.zip",
|
||||
"b993db8bf609419a850d3233f97bf422de7e5e54576120c36de0ad703e541bf2": "frp_0.52.1_darwin_amd64.tar.gz",
|
||||
"d7c2ffe601af16d168d881b88817df81e9bc8646e56643545bd9a11f01ebac6a": "frp_0.52.1_windows_amd64.zip",
|
||||
"e61df02bd13c250267ded9f0db8ef0e0f3a3eea63efbb8d041190883b0cee0cb": "frp_0.52.1_linux_mips.tar.gz",
|
||||
"f64a03af886034ad8380631ef1d65728175f5af79674af39c29978a86c181c7a": "frp_0.52.1_linux_mips64le.tar.gz",
|
||||
"1411f74ca4f05e63963448b9d0c972e16cbf98ba81864e1c04de0492ebd0c6fa": "frp_0.52.0_linux_mipsle.tar.gz",
|
||||
"14c37cbee05947b2c67fe8064c132652b363c8b0d72fa401ddaf93efdc9538e3": "frp_0.52.0_linux_mips.tar.gz",
|
||||
"1a8d2c5bfe3a0367068cdf890b025258e5614c3fef308985c001500902692817": "frp_0.52.0_windows_arm64.zip",
|
||||
"4669cb8c374ff0ec48c0f6d15a939c59390c2109645914dd52d4deca519c084d": "frp_0.52.0_linux_riscv64.tar.gz",
|
||||
"4794997fffc632dd8d357e9d00ca616e9efb2741e0f0acd1599f90be6281b9e6": "frp_0.52.0_linux_arm.tar.gz",
|
||||
"5953e84b6a1590568b6d77a0b75093552577aa61484aff41b3ad0fb35c68719f": "frp_0.52.0_windows_amd64.zip",
|
||||
"80228ba9bd43db42713f682032c0d4c2faa07ecb01be848bb57f6d51f24fa138": "frp_0.52.0_linux_amd64.tar.gz",
|
||||
"8a5b86e7ea67bd1355ca5b9ddda60ecfdfb7c0b13cf06af71c1e72e88371016d": "frp_0.52.0_linux_arm64.tar.gz",
|
||||
"91f46654fd8eae9fcc5a7189c6629a7e4b8f49654d996bbb45432cb4a46ac8f7": "frp_0.52.0_linux_mips64le.tar.gz",
|
||||
"ad61f4285ae98dd4b8bad622888e97bb290e2ca667cd9ad52ad2877cc2ec6807": "frp_0.52.0_darwin_arm64.tar.gz",
|
||||
"c17291696d623106324b9bad894599325a90148d7d19970b9142a445b789b571": "frp_0.52.0_freebsd_amd64.tar.gz",
|
||||
"c68f67a262cf61a81945326e0e0c9e2a3dce209c3125bb0f05a16921141f4231": "frp_0.52.0_darwin_amd64.tar.gz",
|
||||
"d21b617081093f98de5fc1e57700d4a104df67c4965f3fb99dc2650aefbce86f": "frp_0.52.0_linux_mips64.tar.gz",
|
||||
"0108697c36c88f6ae776f923064236f4e890f3c887a94e798222e5ba3c08c568": "frp_0.51.3_linux_mips64.tar.gz",
|
||||
"081e0f8ba995218e30ad3c0fa7a12493f17dcbbbac73fdae4391fddf8af2f918": "frp_0.51.3_freebsd_amd64.tar.gz",
|
||||
"2e1a85c3cfa7cbbcb8747f53de4d7c913cd8ace7475988d823ca0e30bdcfa44e": "frp_0.51.3_linux_arm64.tar.gz",
|
||||
"30b14705cdfcc4fbc654b55863d110a99deaa92a1490561e8dfd84326f9a9e9c": "frp_0.51.3_linux_riscv64.tar.gz",
|
||||
"3fabb19b2157709cb6baea755513f38b2d5674539b54f7853454c48c5a9f22bf": "frp_0.51.3_linux_amd64.tar.gz",
|
||||
"4cafe6451efd64e50a28f2533055b1f68fc59426838214d20341acba515b0eb5": "frp_0.51.3_windows_arm64.zip",
|
||||
"5fc4a7caff50594c717e7d8e5929d4cb3e1674d81fd345a29abadce0a86d22f3": "frp_0.51.3_linux_mips.tar.gz",
|
||||
"62170484c4d450fa47d86ed8b1dd20659b22cd7bc5a36caab330f244d6ea4d97": "frp_0.51.3_linux_mipsle.tar.gz",
|
||||
"6abdb7353ae5562e16d28e1da142f5f97bd51964359901aafd694b4638f85739": "frp_0.51.3_darwin_arm64.tar.gz",
|
||||
"bf8ab462d70a288b7ff2e9dda8151d16340ec4758843a619a936b7541f52fe54": "frp_0.51.3_freebsd_386.tar.gz",
|
||||
"c35dcc7b9549eacce4d5b34a07a3d102b0c631ef4b72682ce0472f65b8777d4a": "frp_0.51.3_darwin_amd64.tar.gz",
|
||||
"cf873001de9c33445213818c5844992e1a3a02486bd3defce556b95e9b0f4af0": "frp_0.51.3_linux_386.tar.gz",
|
||||
"d1d9b02741e5d8742853665aad6a36a74a977fb82108b894712008db8d170276": "frp_0.51.3_windows_386.zip",
|
||||
"d6373caf2bb26e7956c976d7d9142a082a0c259525bac3d5bb2fcfcbbfa63bc6": "frp_0.51.3_windows_amd64.zip",
|
||||
"f300f69fe05b47e3b3e571a1fd83c7c0f7d69667d50a78ccbaa551bda3078169": "frp_0.51.3_linux_arm.tar.gz",
|
||||
"ffa8edd59c275f6c592835b11b1f00e7c83c7d1e91aa8d9f6d666d286e902017": "frp_0.51.3_linux_mips64le.tar.gz",
|
||||
"0b938c1c8389829602f511b4d8ebbe8f6d2ae6fb4e5a88540b1699c922a63610": "frp_0.51.2_linux_arm.tar.gz",
|
||||
"13ac5e018ec166c098c2d67635068ad1b18247aaf02a8537532f52b4fda2dd29": "frp_0.51.2_windows_arm64.zip",
|
||||
"3ce4df319c7ea35f8cfa13d1e03a0309fc4f57aeaaa02d05fb9fd560443e67ba": "frp_0.51.2_linux_riscv64.tar.gz",
|
||||
"81930048c93d8db07af024cd0355809248501dec0ce182a734d16e6bd48055a3": "frp_0.51.2_freebsd_amd64.tar.gz",
|
||||
"895b5c7ece8b458dff80ed790fc1633675a05fc9c4bd994ac89cf8e9d83bd32b": "frp_0.51.2_freebsd_386.tar.gz",
|
||||
"9774490a0a4f822960a8da99a214cec6e2320622c2c20cd6b713e0e52806031c": "frp_0.51.2_linux_mipsle.tar.gz",
|
||||
"99196195845422f6ac5962782fa3676f34fff343e0fed0f354cb6600d894afd8": "frp_0.51.2_linux_amd64.tar.gz",
|
||||
"a41b7612e1057aff1743cdd0c9cf2dddd07f7e4e0340d419f05c42612b118a02": "frp_0.51.2_darwin_arm64.tar.gz",
|
||||
"b430c31a107a7c5e48899e3ee800f39aa50300d3d76f87bb7afb7ede58875cfe": "frp_0.51.2_linux_mips64.tar.gz",
|
||||
"b68640e6866a22639186095138657c53b0bb6626ec0438b488d1a2ffdde23155": "frp_0.51.2_linux_386.tar.gz",
|
||||
"b83a269ce5fb9ff099695165a5d3565646f6032579c4bc6925c63fe8100aee0f": "frp_0.51.2_linux_arm64.tar.gz",
|
||||
"c35d5b705e2b321cf612bcdeb44ee27392d6a1202248e8ec30bf178adf00f9da": "frp_0.51.2_windows_amd64.zip",
|
||||
"cc928db0c984d3a7e9822ebb7ac897ddb90f43848488a5c3261b5704085fa92a": "frp_0.51.2_linux_mips.tar.gz",
|
||||
"d458887ece9050b08d1d58c2718110643b87f254981cda6c86f25dd5559e3867": "frp_0.51.2_darwin_amd64.tar.gz",
|
||||
"df37d932eb846e608187b0aca6d182467ff24c548a044b9206a93913ec93c752": "frp_0.51.2_windows_386.zip",
|
||||
"f56461c7a75839fa5ab3f8be2988f9f5d57c8121c4d7c31e17d2d3a7447d2a7d": "frp_0.51.2_linux_mips64le.tar.gz",
|
||||
"030544b09aff990592772ae508a62396c5648a267a14e5f2fad08324c3d9eb9a": "frp_0.51.1_linux_mips.tar.gz",
|
||||
"03dae058d9b192aab4e119e620c40253f7693bfae095820ddd0313403d207d82": "frp_0.51.1_freebsd_amd64.tar.gz",
|
||||
"1837335417e0bfa4c1caf7ce94047e1ba8020983c246b25679dc5efced9dae75": "frp_0.51.1_darwin_arm64.tar.gz",
|
||||
"18740144d6c91dea850c695590973733ababc0634ca18073d2faec296f572b07": "frp_0.51.1_linux_mipsle.tar.gz",
|
||||
"2379c3dc7bf783334051c06aec97ffb50007c9d17572aae45500f07c764ab99a": "frp_0.51.1_windows_arm64.zip",
|
||||
"291fa7918aa575802ced2fb77e45f33a3cf7fc4b5c27c4ac31a68b2506c50a30": "frp_0.51.1_linux_mips64le.tar.gz",
|
||||
"2d07711a0e24e3da968ad69aeeb458854572788e7869d276fcfb1189c824f9ff": "frp_0.51.1_linux_arm64.tar.gz",
|
||||
"429b1032624f2fa211d31521f1d7f3703c022e476f6e225325842500eb3a37c6": "frp_0.51.1_linux_riscv64.tar.gz",
|
||||
"4ce2100f0e9907d9dc152f94f56bf33bc44d029b2f83efde32b586a57bf55809": "frp_0.51.1_freebsd_386.tar.gz",
|
||||
"70f57deb3ce57eb890104fe14d6fe442a815e095122a9c2b584e34d3c54f5563": "frp_0.51.1_windows_amd64.zip",
|
||||
"74df509decd6953a77543ae8febcdc05379bb2bd0614ad2fe53a4a6cfac86caf": "frp_0.51.1_linux_arm.tar.gz",
|
||||
"9f27cec3b7e600c0223c0de06b65feafa9ed6bf82a8b1dfe338aef6b03bac097": "frp_0.51.1_linux_mips64.tar.gz",
|
||||
"adbfe65938517a8024565569825526643eac2d3294f4524d12a2846611107e08": "frp_0.51.1_linux_amd64.tar.gz",
|
||||
"b7a7814aedd230b66e11f3626aa505a2a701d6afc19bc8be2143955bfa3c1d6e": "frp_0.51.1_linux_386.tar.gz",
|
||||
"e0b8976e986ef0ed0901560810a81cc80cf8c332e087edd35f50e9a5a88c79ae": "frp_0.51.1_darwin_amd64.tar.gz",
|
||||
"fe1eaa0c7066ad45a8a13838d15a6a6535e69250ecc3ed8c48bfb480c8b87e5a": "frp_0.51.1_windows_386.zip",
|
||||
"025bf967e37ce095f31bc45d886156d365a0e9dc7aa0e7f3bbc91bd1c9717145": "frp_0.51.0_windows_amd64.zip",
|
||||
"26acab3487be8980460ef86f0fdc7a446cfdadab02a5a0b27dc760ecce15ffc2": "frp_0.51.0_linux_riscv64.tar.gz",
|
||||
"26c48aa4fa4458ad29d0de364904e24be40424d4f6c37005c2c2d9c6e41e2b06": "frp_0.51.0_windows_386.zip",
|
||||
"3f75d981d58670ce7e0e3f5ead2bd3359cdd1f33b96da726c62013567a884639": "frp_0.51.0_linux_mipsle.tar.gz",
|
||||
"5feca5a4d601ed393a3cc04d8bf3c41194ef56af155c326cf1e7fdfd130ef17a": "frp_0.51.0_freebsd_386.tar.gz",
|
||||
"6c9628cb8382894dc0a928df8fcea9dad9cb763ff161e31f94f816443c7419e0": "frp_0.51.0_linux_arm.tar.gz",
|
||||
"7174a1328325da89ed6aabcf522131db9928222154e9607b0d5a2f7b2977ae93": "frp_0.51.0_linux_amd64.tar.gz",
|
||||
"7402fc76816fd653bbe050a3f8a2dfd7c1363c980e2cc3dc369c60c3f0d502a7": "frp_0.51.0_windows_arm64.zip",
|
||||
"7ebff99259931e26c3baf8dd78c1af671d73a6c91a1d6ec9107c0c225df76bf0": "frp_0.51.0_freebsd_amd64.tar.gz",
|
||||
"886ac7c8c0e01bddcb808947f76a5f904572e337fa4023cce4bad71a7ae9ca1c": "frp_0.51.0_linux_mips64le.tar.gz",
|
||||
"9b3e4c64089c3b78ea1f666f11551e4ae6a435fc0797e39ab4fb07fd633b400c": "frp_0.51.0_linux_mips.tar.gz",
|
||||
"b4a40bfaca19d5b8570be95ea2839fa82c7814c561510c3e3807ce273ee7c7cf": "frp_0.51.0_linux_mips64.tar.gz",
|
||||
"b7f2414b1d8be99157e5b25ea578938520c45d094534fffb2e515796559b9b29": "frp_0.51.0_darwin_amd64.tar.gz",
|
||||
"b8a22f70d3451a7f4b8e1718da28ef02dfb38d37193bcbdc1df39eb52d0da40b": "frp_0.51.0_darwin_arm64.tar.gz",
|
||||
"ca7baeb243b5c264847067f6e5619311223f1741f73d5371ff7fa90698ff5a3b": "frp_0.51.0_linux_arm64.tar.gz",
|
||||
"e377afeb481b30d9979fcbf636df6b5c4f9449b44f6c3d21a768aa5cb8767cb6": "frp_0.51.0_linux_386.tar.gz",
|
||||
"1084631215170fc83b2de13f156a3b0e2ea02f2a0955fc94d3c6c5015391922c": "frp_0.50.0_linux_riscv64.tar.gz",
|
||||
"1cda556f00b20f5b575ba40f83d8a007a8fa3308ef502c62fb7510989c3b7b10": "frp_0.50.0_freebsd_amd64.tar.gz",
|
||||
"33893a93b57e6509132b4d6ae29f3e8a1f4c105c21746f0f0f036df0cf8d1979": "frp_0.50.0_linux_mipsle.tar.gz",
|
||||
"4e2b06bd978472dd092c166b43ec56ab22c1347710fd77616283d2c27ee9ae56": "frp_0.50.0_freebsd_386.tar.gz",
|
||||
"4f2088aff3460c9bd278121de7781985734969399d408f0c9e3f794165e0a407": "frp_0.50.0_linux_386.tar.gz",
|
||||
"7bb651eec86e0126af3bd515235901a64b5490115defa10972e703c05bc65345": "frp_0.50.0_windows_amd64.zip",
|
||||
"7fac327360b72613dec67583e4b939b65af0b88b676660821647b161ec2173fd": "frp_0.50.0_linux_mips64le.tar.gz",
|
||||
"94e608af6d6f96619de403bf3aed4db8ab602999e0335380279e0d8aca1c6040": "frp_0.50.0_windows_386.zip",
|
||||
"a5496a0364e4e071aa6a1cbcfd519e35ac8dcb4eac9a24e6a22340c4d4cf1914": "frp_0.50.0_linux_arm.tar.gz",
|
||||
"b2768608b33e964fc7067657f385ba15a69762b0a875db47981953d70dd36af7": "frp_0.50.0_linux_mips.tar.gz",
|
||||
"c57526a8a0010b811b9bd367704125033fc71774f6a66dcfd4224ec5478e0490": "frp_0.50.0_darwin_arm64.tar.gz",
|
||||
"d33d83e8b98ce5413603f71b1c0b38c1b5bbe1d1c826b7ada84a7543a6cc6ea6": "frp_0.50.0_linux_arm64.tar.gz",
|
||||
"db80349f17c39f502a631afda7cf5b95b2a85cdcafa92359b9f4d0375772c440": "frp_0.50.0_linux_mips64.tar.gz",
|
||||
"e2047b43e87456568a505b84c45f52e0d2ed146896ec1e3fceb72e818200f11f": "frp_0.50.0_linux_amd64.tar.gz",
|
||||
"ee8cdc63c2993ce8ab2bf918a56169a815254cd5f5a9a57567a904ec5dbf0145": "frp_0.50.0_windows_arm64.zip",
|
||||
"f4cb27fb222cdd87a30674270614adfd0aa8350034a8bdbc50fc1967c0f0cb66": "frp_0.50.0_darwin_amd64.tar.gz",
|
||||
"09329200234dd56722e095ee5b0b3d31bf8d39f3bdacb4a473b9144a7e8e8b7d": "frp_0.49.0_windows_arm64.zip",
|
||||
"183ee0c672409cdd8b421f31e2b81753a4713bee962e1edf97f1455cda97173d": "frp_0.49.0_linux_amd64.tar.gz",
|
||||
"1ca8187c73c3c75ace29675193659f9d6ddff3e5ddf2131f49f156844ca7d778": "frp_0.49.0_darwin_amd64.tar.gz",
|
||||
"429aab2804d7431f684c6d409342af57381dbcafc4b37c49606063be2f92d4a3": "frp_0.49.0_linux_arm64.tar.gz",
|
||||
"5b4204056ae94aa8281218656a1b3566eaaea2ddf4874eccb4a9c23cf9bc0fd0": "frp_0.49.0_linux_arm.tar.gz",
|
||||
"76c7a4f5e35f32b726c48fdd32e292f63c7b374ba019a28dc44b04140f03e6de": "frp_0.49.0_darwin_arm64.tar.gz",
|
||||
"7b6c9cf91ad9d00385d47139ffc69c0c9d72270886dbdb4f71f599efaec2cb64": "frp_0.49.0_freebsd_386.tar.gz",
|
||||
"9033c6def481bde4bf7f2361966ae0ea92dfda5763a167460dcf0e231a2d02b8": "frp_0.49.0_linux_386.tar.gz",
|
||||
"94ac6a42a165d913b79a0dcfb2d55a686e81b776697580e113aecd8815607076": "frp_0.49.0_freebsd_amd64.tar.gz",
|
||||
"a343c8f23ba35c943e1c9311df17eb12f84c682d2ba0e965e244a49759b65f28": "frp_0.49.0_linux_mips64le.tar.gz",
|
||||
"b117ea60954ad0c8d4e92eb60ca8e748806978506c377d59b4f5bc5295c4e3d1": "frp_0.49.0_linux_mips64.tar.gz",
|
||||
"c44853992b0d6d3f9f5c777038590ee6a5869dbeb6362dfa5537e9d730aa26f6": "frp_0.49.0_windows_386.zip",
|
||||
"d3a481b40889bf4c6fd35b18941de04ddaa2316ad51977a5af7bdddf3650f808": "frp_0.49.0_linux_mipsle.tar.gz",
|
||||
"daf162e5cc90599aab036b7bb4ed6d4c521b2f5732a6cb40b08a00e6714deaa3": "frp_0.49.0_linux_riscv64.tar.gz",
|
||||
"f39f10c0867a52eb9e4d2adf0bfa821993c950feca35437e84d274fba00bc595": "frp_0.49.0_linux_mips.tar.gz",
|
||||
"fc5c5c5ff93300cea3141ff55fbccccb07cd0017d4e9cd4bcd324563f88f53fd": "frp_0.49.0_windows_amd64.zip",
|
||||
"042fa197c0f91b27404c086eabfb62dad3ffaaad7101046f518abf58ae42ee1b": "frp_0.48.0_linux_mips64.tar.gz",
|
||||
"0cd33dcfe9a38441eda2c60675f05ab3c3875b1e54608583d50d0835c567a30e": "frp_0.48.0_linux_arm.tar.gz",
|
||||
"1e5b997597bacce1d971b83416c2f8c9cde0cbd294e6b11d91a3939f9c6356a9": "frp_0.48.0_darwin_arm64.tar.gz",
|
||||
"2ab7b66c09391d9d76bd7a4818e85fb3818a10a46c91a804b982d7d4c9fddce3": "frp_0.48.0_linux_arm64.tar.gz",
|
||||
"41c75d72848375144e46b9b9fe56168f365ce4bee56280757dada6c92bb8abc0": "frp_0.48.0_linux_riscv64.tar.gz",
|
||||
"5de51fda0577a049945e42f386df70a8e9eb2769af96bb6b7471cb5072605be0": "frp_0.48.0_windows_386.zip",
|
||||
"7a9fd341e0deb467ba0ab4913852adc965a0df2ba38e18ec80ab7ef61a9e99e8": "frp_0.48.0_linux_mips64le.tar.gz",
|
||||
"7dba4f6e942502f0eca2ec37206671734eeb87c40a29f16b96ce14045da9e833": "frp_0.48.0_windows_arm64.zip",
|
||||
"8ad8905b9296f3c26632f3bfc66302bc082b62295f6bbbb5b78e31d1e6649f26": "frp_0.48.0_windows_amd64.zip",
|
||||
"a792cd515589050d475a28b714276a2960ed7ef8e0e5baeea3d38301a775fbb4": "frp_0.48.0_linux_mips.tar.gz",
|
||||
"acd9f040fc6fb2a595f20bfb4faa66d9244615a0feaf9d2e4b03a994ca126a32": "frp_0.48.0_linux_386.tar.gz",
|
||||
"d48623a74a00577be0409d912f8197a110f13192eab99d3959ceb11496ed0903": "frp_0.48.0_linux_mipsle.tar.gz",
|
||||
"db53bdef3b270e45fb9efc489af2948be7c7fa1e3a5cae9698f2832e628bcd3b": "frp_0.48.0_linux_amd64.tar.gz",
|
||||
"dd781cfd710345cca2df4d306245298efb61dc447d8004dd5542c1b2083e39a7": "frp_0.48.0_freebsd_amd64.tar.gz",
|
||||
"e2dd4933cc48caba288be96ba5b226c7edb5be940c0452d9bc7faa28ab66847f": "frp_0.48.0_darwin_amd64.tar.gz",
|
||||
"fdc0bca8460360346991a0f13e25233c87805bdc0f055f221f9c57c33b3b60fa": "frp_0.48.0_freebsd_386.tar.gz",
|
||||
"00ffd863c32645660a29db758db4ea89f7c3eb616b3488cceca55345d8a5d11d": "frp_0.47.0_linux_arm.tar.gz",
|
||||
"04d9eaf4997d1407feca0324beedaca577c63fa900ef04e6a97de9e8e2391e34": "frp_0.47.0_freebsd_386.tar.gz",
|
||||
"125f87d334addd8ec7dacaf2a321a9f1c9a8b31c8a673d2d02808162cd67f997": "frp_0.47.0_linux_riscv64.tar.gz",
|
||||
"3b9f8b80f13f20194490851b076186124b67b9a7845b32e5e035ae4aed2e45dc": "frp_0.47.0_linux_mips64.tar.gz",
|
||||
"41a3a760ab0e04271f8bee1fd80011ce8e93a8455f78919864bcb13200f758f5": "frp_0.47.0_windows_amd64.zip",
|
||||
"5b7c15f9e14042a99c38515ddfa694f188f59d72bde10ce341d86cbf7f801b19": "frp_0.47.0_windows_386.zip",
|
||||
"71a0f3137f02da4116ea2b7d134c38be86a1229cffb0b1dac4469b561ea35985": "frp_0.47.0_linux_386.tar.gz",
|
||||
"9299c297f6c75c6aa2bbbb5de27172e367328b6f5bbb6f8d1c4ca73c4c4af415": "frp_0.47.0_darwin_amd64.tar.gz",
|
||||
"95583f7a979910ff4e65a5d9802df699063472a67a1f9e6d6fd6c2fcff448a14": "frp_0.47.0_linux_mips64le.tar.gz",
|
||||
"95c0695cdf0cd8d399cabdccdff93b25aa7deb97e950bd3702bbbaf9a2baf87a": "frp_0.47.0_linux_mips.tar.gz",
|
||||
"c53b188ec3eb09f34484d2576f957e61522875c0e7a99e67722d41b2b57cdb4d": "frp_0.47.0_linux_mipsle.tar.gz",
|
||||
"cfc766cc82568e40d7198493340283cc0f4f42de97463aef863170f7e773ff9c": "frp_0.47.0_darwin_arm64.tar.gz",
|
||||
"d7a7a6085fa6a9f8de0ae2c221c1ef110b9afc2a0122a058482ef3974d031ac0": "frp_0.47.0_linux_amd64.tar.gz",
|
||||
"ee2d0d800b14ac26b8aeae4365df031e0186d23be150308735a0be753ec2d3f9": "frp_0.47.0_freebsd_amd64.tar.gz",
|
||||
"f1dc0436b7f9f3f5c5d404cf5fb4a7319ff1cc22a06a687672020af620693f70": "frp_0.47.0_linux_arm64.tar.gz",
|
||||
"0476f68f4552ae460d72f0b6c2c9fd4b6fb8dfdbafdec62695f02996d7221f81": "frp_0.46.1_darwin_arm64.tar.gz",
|
||||
"200244a2c1bc9e186f875c23d0b78c9ab59a88052f4f4132e5c28a70fdc356b6": "frp_0.46.1_windows_amd64.zip",
|
||||
"4af6b42eb79a5290d1e24e534a0ec34521dc2d30ef60898abd092ddb2e1cd55c": "frp_0.46.1_linux_riscv64.tar.gz",
|
||||
"54e364bf382cc987a962fa5db328ce8bc375bff74ff7b8afcaeb1905a295e027": "frp_0.46.1_windows_386.zip",
|
||||
"5f1660b704a8b580082b81e14a41d2da9ff1edeebc59b885acb92f1ab1f46838": "frp_0.46.1_linux_mips64le.tar.gz",
|
||||
"76e5d42d4d2971de51de652417cfe38461ef9e18672e1070a1138910c8448a2f": "frp_0.46.1_linux_arm64.tar.gz",
|
||||
"7c6208a3f7131802f24ad7bf7f02c760bba5c17443bdf328598d0758865f80df": "frp_0.46.1_linux_amd64.tar.gz",
|
||||
"7d299b5695b0076b24e93928bad255f76c8352b5002fd459ef63c0199251abe9": "frp_0.46.1_linux_mips64.tar.gz",
|
||||
"9704b24b5a58144293f7c7715b095b1ebf43b90e501050dfb9477094e6dca41b": "frp_0.46.1_linux_386.tar.gz",
|
||||
"a47d75d634790109eaa5768d4e5cb504988e3754dcfe458072ef0b46d9aea419": "frp_0.46.1_freebsd_amd64.tar.gz",
|
||||
"b330c29f6ef91302c6a2b9a0f6e86c77b498d0babb60fe182440f1b97e0554cb": "frp_0.46.1_linux_mipsle.tar.gz",
|
||||
"bbb1ab095f30e9ecf1b745579f6ecff80eff11fb712f2bc364a656fbec89f73b": "frp_0.46.1_linux_mips.tar.gz",
|
||||
"fc465df713f8c9d63c9380aa9da72b6ef639fb44917aed390d9c4d08c475a20d": "frp_0.46.1_linux_arm.tar.gz",
|
||||
"fdde1a3e82d043cdca44b13c45e7593b61707385b30e919c38615d02d53e4b36": "frp_0.46.1_freebsd_386.tar.gz",
|
||||
"ff71979ea17d481194beba325a55f5d2a319175ebc6a80df535a202a43614f24": "frp_0.46.1_darwin_amd64.tar.gz",
|
||||
"0ac137ea9061aea6b6e8e5fc228b1082e14d3e29cafe6103f542ac4ffd728843": "frp_0.46.0_linux_mips64.tar.gz",
|
||||
"1f1eefdf6a9ade3923edcd716c56941f2755848a4bd97167aaa1ceebfed95194": "frp_0.46.0_freebsd_386.tar.gz",
|
||||
"275b254a20dfda754d6aba28d335a392df74150d6945d2da20a7c5718dc2c001": "frp_0.46.0_darwin_arm64.tar.gz",
|
||||
"30199cd67bbed08c65f86c2420f0967491cad2ec791c97936666bc930d65e73e": "frp_0.46.0_linux_mipsle.tar.gz",
|
||||
"3cd7ec9209b973520d47d784a09a368bfb9e2bb195f3c543ae5311720249e315": "frp_0.46.0_linux_arm.tar.gz",
|
||||
"41f1014ee2ee7ed0a6e989deb937af9a8c01f4974fc1ef541583065475511d65": "frp_0.46.0_linux_riscv64.tar.gz",
|
||||
"53242fd2bad1e6b3039fdef38df6219710864d1c9e639208a2106326921d15fd": "frp_0.46.0_darwin_amd64.tar.gz",
|
||||
"62044b03a7bccb7e8f8f4f691f34838cd1160a643c0bb06ca8489e78d2d65897": "frp_0.46.0_linux_arm64.tar.gz",
|
||||
"6681551b9bb7311625be8f3a269c183b600e13966787a8b11a8f9e8595a3d66b": "frp_0.46.0_windows_386.zip",
|
||||
"754d66a918d3550c83e670a458f66954eec0521d6e76a20dd0a865992ad1b55e": "frp_0.46.0_linux_amd64.tar.gz",
|
||||
"a77d3fa9419c5dc12ebd94eb5b97be3cff2c12b00dbe3884adc9ffcedf73909e": "frp_0.46.0_freebsd_amd64.tar.gz",
|
||||
"b9c79acc881c58b0185465a5ded032d6210637f860712f04ecb800b66453d125": "frp_0.46.0_windows_amd64.zip",
|
||||
"c14d5be9b9d80a48354c04dd1c3f80167abae94a1854d2f5116e4e5a0da89b91": "frp_0.46.0_linux_mips.tar.gz",
|
||||
"c87ffc18bfa386cf946156f91fb8649a0cdbcd762550a0b8ab1f4774cb608455": "frp_0.46.0_linux_386.tar.gz",
|
||||
"cac2bc6fccb071789d7acc95f02470cfb935cfc9c7c6a1e6d91457e4ff11e8e1": "frp_0.46.0_linux_mips64le.tar.gz",
|
||||
"1a527c78ae25fa3e393d70fbfcea5b928ca96a689d8e82477f1b0db0cfc51e76": "frp_0.45.0_windows_386.zip",
|
||||
"40d5025cb0b0a6f26cc79fd23fc78ccdfa050bd7e80d694f2039ab98093f831d": "frp_0.45.0_windows_amd64.zip",
|
||||
"4c90633d523f467384a424bbfce211f737becbc7c4ac637e10e6c91fda8a6a26": "frp_0.45.0_linux_mips64.tar.gz",
|
||||
"4faed559dc80bc2bf43b6c3da60e19f86c42ab8ed2b19e3ff0d3f4e4cca6c50c": "frp_0.45.0_linux_386.tar.gz",
|
||||
"5eb942ba9ed0d45d2ac1ea6ed02fbff802a69c408c8eb68155dd2fb7c6fabb0e": "frp_0.45.0_linux_mips.tar.gz",
|
||||
"63035108f37cc80d6043c1fcac50f8e856791a4fb8bcef0e792d97c88d8e35c5": "frp_0.45.0_darwin_arm64.tar.gz",
|
||||
"8b2aee9d9eabc6078ae8a4c718030be85a13464becdb99f97f635e75425eb63e": "frp_0.45.0_freebsd_386.tar.gz",
|
||||
"8ecf30ac7c14f85da20c1761c6418979282bff12db4d82ade2f4a1a8037bdf6e": "frp_0.45.0_linux_riscv64.tar.gz",
|
||||
"97b4d3555734cba2af59b72b960ce10891b584dcf8d9e3db9f4f099c0a64131d": "frp_0.45.0_linux_mips64le.tar.gz",
|
||||
"987f353f6ea282e259738eeb90c20b70fe20e1a49aca498b02acc47200c082bd": "frp_0.45.0_linux_arm.tar.gz",
|
||||
"a660a94c158cb280974447efd174d3525d806ac7235f6546abeb1a57660a1125": "frp_0.45.0_darwin_amd64.tar.gz",
|
||||
"b9a1a2387b9b07ec6be9d28e5ed9639c1ea29d41a84bc3a62b39ab476459b1ff": "frp_0.45.0_linux_amd64.tar.gz",
|
||||
"e2a6179880b852366edc395685fa0e82eec542e9c8a2c3483d30d5740941a0e0": "frp_0.45.0_linux_mipsle.tar.gz",
|
||||
"e57919a0e3a63705ef452bb2a6bc440f7a6273a8205ed9ce2ccfd063ea9b2215": "frp_0.45.0_linux_arm64.tar.gz",
|
||||
"f9c6ad68a9e3903d1689cd85e84f00aa892a9e98b368a9f062599da9d2cb4967": "frp_0.45.0_freebsd_amd64.tar.gz",
|
||||
"0fd011fb817fa36fe8735e3d97df523970d9be4f56f0848840f737b63ba37fbf": "frp_0.44.0_freebsd_amd64.tar.gz",
|
||||
"23705712274935b9b223412bf731ecd672dcc8b5d0c11a39372aacedaa6a66a4": "frp_0.44.0_windows_amd64.zip",
|
||||
"3262dee2fa68eb8d9428d209b2e87c2293d007529898850874b19707088c416e": "frp_0.44.0_darwin_arm64.tar.gz",
|
||||
"3c4e769a29f03bcc9e998adcd1281142abfb5ff1dd66da5a435830a1cff34217": "frp_0.44.0_darwin_amd64.tar.gz",
|
||||
"3cc79f9fc44300aed80988b31845328b428c0999572eb7f1df949eccee0f518e": "frp_0.44.0_linux_mipsle.tar.gz",
|
||||
"45f65dafd172f3a5e05eabf3d4efbb954c92a88851a027f79c19f61a10b78287": "frp_0.44.0_linux_amd64.tar.gz",
|
||||
"4aed98c21ef4534951b6faeab4982376695ae1e10ca90aedd27a9bfcf6caea2e": "frp_0.44.0_windows_386.zip",
|
||||
"5f3f60a71fa040a36be5de818e6f95c48e8a2ba368b700a079b593f0e281dbd8": "frp_0.44.0_linux_mips.tar.gz",
|
||||
"5f7c9ad77e37a5921450c013b9792dac4ea5ef5d3114ea9276585f62e2318a79": "frp_0.44.0_freebsd_386.tar.gz",
|
||||
"60ee29ebb3683135c815b4e9b6681c92a445ac3f40e9302a70b65fca68ff5116": "frp_0.44.0_linux_mips64le.tar.gz",
|
||||
"7c55322bb55e4085ab950711f0c3406a25f95573f618ed347e8f542ecf93cb78": "frp_0.44.0_linux_mips64.tar.gz",
|
||||
"ad151125bd46fb8abf11f2a4347c7c85e102bb0e6128c69962c8d6bf9a71fca6": "frp_0.44.0_linux_arm.tar.gz",
|
||||
"ce18273ca20bd38c567b0355ca2c85575651b39249294969daa51e568077a872": "frp_0.44.0_linux_386.tar.gz",
|
||||
"f5acd6dd3812f30ed6a2a2a864231563a962d4ff09c64d21be106db6f8806af8": "frp_0.44.0_linux_arm64.tar.gz",
|
||||
"00c526bdfae8fe448b1810c1c06b2827efa1158b7e324aa69c23a57a8b29f603": "frp_0.43.0_linux_arm64.tar.gz",
|
||||
"0d05e3ebd2490c026e1b8f6780d901eedde65562af02acf3bf80d729a2aae52b": "frp_0.43.0_windows_amd64.zip",
|
||||
"1fe64b366408022e4d61c1e37f64e268f7e72f4d351425df36c35fb1cfc534fd": "frp_0.43.0_linux_mips64.tar.gz",
|
||||
"3c582f611716c77db5e4f69823fc72572006608f63d9859dea598f0dfc74ed0b": "frp_0.43.0_darwin_amd64.tar.gz",
|
||||
"4eecced7aa167279bda23afe2be0f3dd9b61080531fdbae5137bd257c334992a": "frp_0.43.0_linux_386.tar.gz",
|
||||
"618b1a0d2bfebc9bc3e59b4c39e67082a445e5aeaaaa0fec9eded436dd64a2d4": "frp_0.43.0_darwin_arm64.tar.gz",
|
||||
"6a3e20b001ab57b066a52394ba2d992ae6d93b22260b0969307966fad6214692": "frp_0.43.0_linux_mips.tar.gz",
|
||||
"6bef9db4560b6c7da2def271f7bc5bf6988fafa3e654f8a2bfb589fd7d79b2db": "frp_0.43.0_linux_mips64le.tar.gz",
|
||||
"7c1416256f7f3637e0dfed99988d08282ae0866784f1eecd53a3639e1a942867": "frp_0.43.0_freebsd_amd64.tar.gz",
|
||||
"801a1ea2bf02b9ff657c34708918397bec61408bed216f6ed45889973ee09a01": "frp_0.43.0_linux_arm.tar.gz",
|
||||
"98ab35f179091726b739c9fbb6643cc7328076bfbddd09732bb68b1cdf1b7435": "frp_0.43.0_freebsd_386.tar.gz",
|
||||
"bb8734f2be2907a2923aedf43757d6ff85a7c66af789b8dbef34ddaf2194f05f": "frp_0.43.0_windows_386.zip",
|
||||
"c14ccd69607c34707120e7c2d2df9b6c0a11c7f40e22f116d75838e2038edba3": "frp_0.43.0_linux_mipsle.tar.gz",
|
||||
"d458d70dd88048d1fc898d5422ed570e912d3f3ef3ee5928871438a08514f725": "frp_0.43.0_linux_amd64.tar.gz",
|
||||
"19ca9f2b318ea2efbe9f2b213c2edd68de54c7ed35dc3f291146c67374d8c57d": "frp_0.42.0_windows_amd64.zip",
|
||||
"35386af9e43ed1948faa7037050573eda3299d4a11061734fce5f4be51c56dd3": "frp_0.42.0_windows_386.zip",
|
||||
"4ef082c1788e972f016f00286a2054c82189cec3a1a3e2af8123240c2888b6ff": "frp_0.42.0_linux_386.tar.gz",
|
||||
"5c4828f6e89b6f2479b671d3e7644b34b6968a6017cac402144c844b48dcc621": "frp_0.42.0_linux_amd64.tar.gz",
|
||||
"7946d13b2498410bf9fb0cc32fee7ea44bde8be438eb1b1bc67c440a3671589d": "frp_0.42.0_freebsd_amd64.tar.gz",
|
||||
"7ff954d3f9f0d655be5f250ca50e8b065ddb8b4d3a1da0a55f740cc03301c6f5": "frp_0.42.0_darwin_amd64.tar.gz",
|
||||
"b53e3cba1a8a3ebaa1e7d04f647eee3aed3417740692e346dc460c813403475c": "frp_0.42.0_linux_mipsle.tar.gz",
|
||||
"bf980fa58499e947581c6b89b100d55c1d417fdda6f7544422a4a6400248e20d": "frp_0.42.0_freebsd_386.tar.gz",
|
||||
"c6f00c7458e7546b9339ce65805b2969abf55f95698f0b2f0904ed85f187b3fa": "frp_0.42.0_linux_mips64le.tar.gz",
|
||||
"c842849be22802e6500167fc34fac869c584ad1f70b6c56dcc66d7391171d567": "frp_0.42.0_linux_arm.tar.gz",
|
||||
"de3397d1084686a5ab9f82fae2aa65f417cef7d7c2cc12f7eb9da51c0a404de6": "frp_0.42.0_linux_mips.tar.gz",
|
||||
"de6262f886175411573c98fe2d5838449b4fc2472a07748964159a468ed0ccdf": "frp_0.42.0_darwin_arm64.tar.gz",
|
||||
"eb8ea449f14a20480c77d6501f8b682516fa4a9394dd15d2a49b6a957aa862a9": "frp_0.42.0_linux_mips64.tar.gz",
|
||||
"f8b9c30d3cef82aebdf5dfce8ba7d6a4943a4b51ef64223b59c5241e3023d8e5": "frp_0.42.0_linux_arm64.tar.gz"
|
||||
}
|
@ -1,43 +1,31 @@
|
||||
import {
|
||||
app,
|
||||
BrowserWindow,
|
||||
ipcMain,
|
||||
Menu,
|
||||
MenuItem,
|
||||
MenuItemConstructorOptions,
|
||||
shell,
|
||||
Tray
|
||||
} from "electron";
|
||||
import { release } from "node:os";
|
||||
import node_path, { join } from "node:path";
|
||||
import { initGitHubApi } from "../api/github";
|
||||
import { initConfigApi } from "../api/config";
|
||||
import { initProxyApi } from "../api/proxy";
|
||||
import {
|
||||
initFrpcApi,
|
||||
startFrpWorkerProcess,
|
||||
stopFrpcProcess
|
||||
} from "../api/frpc";
|
||||
import { initLoggerApi } from "../api/logger";
|
||||
import { initFileApi } from "../api/file";
|
||||
import { getConfig } from "../storage/config";
|
||||
import { initCommonApi } from "../api/common";
|
||||
import { initLocalApi } from "../api/local";
|
||||
import { initLog, logError, logInfo, LogModule } from "../utils/log";
|
||||
import { maskSensitiveData } from "../utils/desensitize";
|
||||
|
||||
import {app, BrowserWindow, ipcMain, Menu, MenuItem, MenuItemConstructorOptions, shell, Tray} from "electron";
|
||||
import {release} from "node:os";
|
||||
import node_path, {join} from "node:path";
|
||||
import {initGitHubApi} from "../api/github";
|
||||
import {initConfigApi} from "../api/config";
|
||||
import {initProxyApi} from "../api/proxy";
|
||||
import {initFrpcApi, startFrpWorkerProcess, stopFrpcProcess} from "../api/frpc";
|
||||
import {initLoggerApi} from "../api/logger";
|
||||
import {initFileApi} from "../api/file";
|
||||
import {getConfig} from "../storage/config";
|
||||
import log from "electron-log";
|
||||
import {initCommonApi} from "../api/common";
|
||||
import {initLocalApi} from "../api/local";
|
||||
// The built directory structure
|
||||
//
|
||||
// ├─┬ dist-electron
|
||||
// │ ├─┬ main
|
||||
// │ │ └── index.js > Electron-Main
|
||||
// │ └─┬ preload
|
||||
// │ └── index.js > Preload-Scripts
|
||||
// ├─┬ dist
|
||||
// │ └── index.html > Electron-Renderer
|
||||
//
|
||||
process.env.DIST_ELECTRON = join(__dirname, "..");
|
||||
process.env.DIST = join(process.env.DIST_ELECTRON, "../dist");
|
||||
process.env.VITE_PUBLIC = process.env.VITE_DEV_SERVER_URL
|
||||
? join(process.env.DIST_ELECTRON, "../public")
|
||||
: process.env.DIST;
|
||||
|
||||
let win: BrowserWindow | null = null;
|
||||
let tray = null;
|
||||
const preload = join(__dirname, "../preload/index.js");
|
||||
const url = process.env.VITE_DEV_SERVER_URL;
|
||||
const indexHtml = join(process.env.DIST, "index.html");
|
||||
let isQuiting;
|
||||
? join(process.env.DIST_ELECTRON, "../public")
|
||||
: process.env.DIST;
|
||||
|
||||
// Disable GPU Acceleration for Windows 7
|
||||
if (release().startsWith("6.1")) app.disableHardwareAcceleration();
|
||||
@ -46,259 +34,193 @@ if (release().startsWith("6.1")) app.disableHardwareAcceleration();
|
||||
if (process.platform === "win32") app.setAppUserModelId(app.getName());
|
||||
|
||||
if (!app.requestSingleInstanceLock()) {
|
||||
app.quit();
|
||||
process.exit(0);
|
||||
app.quit();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
async function createWindow(config: FrpConfig) {
|
||||
let show = true;
|
||||
if (config) {
|
||||
show = !config.systemSilentStartup;
|
||||
}
|
||||
win = new BrowserWindow({
|
||||
title: "Frpc Desktop",
|
||||
icon: join(process.env.VITE_PUBLIC, "logo/only/16x16.png"),
|
||||
width: 800,
|
||||
height: 600,
|
||||
minWidth: 800,
|
||||
minHeight: 600,
|
||||
maxWidth: 1280,
|
||||
maxHeight: 960,
|
||||
webPreferences: {
|
||||
preload,
|
||||
// Warning: Enable nodeIntegration and disable contextIsolation is not secure in production
|
||||
// Consider using contextBridge.exposeInMainWorld
|
||||
// Read more on https://www.electronjs.org/docs/latest/tutorial/context-isolation
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false
|
||||
},
|
||||
show: show
|
||||
});
|
||||
if (process.env.VITE_DEV_SERVER_URL) {
|
||||
// electron-vite-vue#298
|
||||
win.loadURL(url);
|
||||
// Open devTool if the app is not packaged
|
||||
win.webContents.openDevTools();
|
||||
} else {
|
||||
win.loadFile(indexHtml);
|
||||
}
|
||||
// Remove electron security warnings
|
||||
// This warning only shows in development mode
|
||||
// Read more on https://www.electronjs.org/docs/latest/tutorial/security
|
||||
// process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'
|
||||
|
||||
// Test actively push message to the Electron-Renderer
|
||||
win.webContents.on("did-finish-load", () => {
|
||||
win?.webContents.send("main-process-message", new Date().toLocaleString());
|
||||
});
|
||||
let win: BrowserWindow | null = null;
|
||||
let tray = null;
|
||||
// Here, you can also use other preload
|
||||
const preload = join(__dirname, "../preload/index.js");
|
||||
const url = process.env.VITE_DEV_SERVER_URL;
|
||||
const indexHtml = join(process.env.DIST, "index.html");
|
||||
let isQuiting;
|
||||
log.transports.file.level = 'debug';
|
||||
log.transports.console.level = "debug";
|
||||
|
||||
// Make all links open with the browser, not with the application
|
||||
win.webContents.setWindowOpenHandler(({ url }) => {
|
||||
if (url.startsWith("https:")) shell.openExternal(url);
|
||||
return { action: "deny" };
|
||||
});
|
||||
|
||||
// 隐藏菜单栏
|
||||
const { Menu } = require("electron");
|
||||
Menu.setApplicationMenu(null);
|
||||
// hide menu for Mac
|
||||
// if (process.platform !== "darwin") {
|
||||
// app.dock.hide();
|
||||
// }
|
||||
|
||||
win.on("minimize", function (event) {
|
||||
event.preventDefault();
|
||||
win.hide();
|
||||
});
|
||||
|
||||
win.on("close", function (event) {
|
||||
if (!isQuiting) {
|
||||
event.preventDefault();
|
||||
win.hide();
|
||||
if (process.platform === "darwin") {
|
||||
app.dock.hide();
|
||||
}
|
||||
async function createWindow() {
|
||||
win = new BrowserWindow({
|
||||
title: "Frpc Desktop",
|
||||
icon: join(process.env.VITE_PUBLIC, "logo/only/16x16.png"),
|
||||
width: 800,
|
||||
height: 600,
|
||||
minWidth: 640,
|
||||
minHeight: 480,
|
||||
maxWidth: 1280,
|
||||
maxHeight: 960,
|
||||
webPreferences: {
|
||||
preload,
|
||||
// Warning: Enable nodeIntegration and disable contextIsolation is not secure in production
|
||||
// Consider using contextBridge.exposeInMainWorld
|
||||
// Read more on https://www.electronjs.org/docs/latest/tutorial/context-isolation
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false
|
||||
}
|
||||
});
|
||||
if (process.env.VITE_DEV_SERVER_URL) {
|
||||
// electron-vite-vue#298
|
||||
win.loadURL(url);
|
||||
// Open devTool if the app is not packaged
|
||||
win.webContents.openDevTools();
|
||||
} else {
|
||||
win.loadFile(indexHtml);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// Test actively push message to the Electron-Renderer
|
||||
win.webContents.on("did-finish-load", () => {
|
||||
win?.webContents.send("main-process-message", new Date().toLocaleString());
|
||||
});
|
||||
|
||||
// Make all links open with the browser, not with the application
|
||||
win.webContents.setWindowOpenHandler(({url}) => {
|
||||
if (url.startsWith("https:")) shell.openExternal(url);
|
||||
return {action: "deny"};
|
||||
});
|
||||
|
||||
// 隐藏菜单栏
|
||||
const {Menu} = require("electron");
|
||||
Menu.setApplicationMenu(null);
|
||||
// hide menu for Mac
|
||||
// if (process.platform !== "darwin") {
|
||||
// app.dock.hide();
|
||||
// }
|
||||
|
||||
win.on('minimize', function (event) {
|
||||
event.preventDefault();
|
||||
win.hide();
|
||||
});
|
||||
|
||||
win.on('close', function (event) {
|
||||
if (!isQuiting) {
|
||||
event.preventDefault();
|
||||
win.hide();
|
||||
if (process.platform === "darwin") {
|
||||
app.dock.hide();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
export const createTray = (config: FrpConfig) => {
|
||||
let menu: Array<MenuItemConstructorOptions | MenuItem> = [
|
||||
{
|
||||
label: "显示主窗口",
|
||||
click: function () {
|
||||
export const createTray = () => {
|
||||
log.info(`当前环境 platform:${process.platform} arch:${process.arch} appData:${app.getPath("userData")} version:${app.getVersion()}`)
|
||||
let menu: Array<(MenuItemConstructorOptions) | (MenuItem)> = [
|
||||
{
|
||||
label: '显示主窗口', click: function () {
|
||||
win.show();
|
||||
if (process.platform === "darwin") {
|
||||
app.dock.show();
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '退出',
|
||||
click: () => {
|
||||
isQuiting = true;
|
||||
stopFrpcProcess(() => {
|
||||
app.quit();
|
||||
})
|
||||
}
|
||||
}
|
||||
];
|
||||
tray = new Tray(node_path.join(process.env.VITE_PUBLIC, "logo/only/16x16.png"))
|
||||
tray.setToolTip('Frpc Desktop')
|
||||
const contextMenu = Menu.buildFromTemplate(menu)
|
||||
tray.setContextMenu(contextMenu)
|
||||
|
||||
// 托盘双击打开
|
||||
tray.on('double-click', () => {
|
||||
win.show();
|
||||
if (process.platform === "darwin") {
|
||||
app.dock.show();
|
||||
})
|
||||
|
||||
getConfig((err, config) => {
|
||||
if (!err) {
|
||||
if (config) {
|
||||
if (config.systemStartupConnect) {
|
||||
log.info(`已开启自动连接 正在自动连接服务器`)
|
||||
startFrpWorkerProcess(config)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "退出",
|
||||
click: () => {
|
||||
isQuiting = true;
|
||||
stopFrpcProcess(() => {
|
||||
app.quit();
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
tray = new Tray(
|
||||
node_path.join(process.env.VITE_PUBLIC, "logo/only/16x16.png")
|
||||
);
|
||||
tray.setToolTip("Frpc Desktop");
|
||||
const contextMenu = Menu.buildFromTemplate(menu);
|
||||
tray.setContextMenu(contextMenu);
|
||||
})
|
||||
}
|
||||
|
||||
// 托盘双击打开
|
||||
tray.on("double-click", () => {
|
||||
win.show();
|
||||
});
|
||||
|
||||
logInfo(LogModule.APP, `Tray created successfully.`);
|
||||
};
|
||||
app.whenReady().then(() => {
|
||||
initLog();
|
||||
logInfo(
|
||||
LogModule.APP,
|
||||
`Application started. Current system architecture: ${
|
||||
process.arch
|
||||
}, platform: ${process.platform}, version: ${app.getVersion()}.`
|
||||
);
|
||||
|
||||
getConfig((err, config) => {
|
||||
if (err) {
|
||||
logError(LogModule.APP, `Failed to get config: ${err.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
createWindow(config)
|
||||
.then(r => {
|
||||
logInfo(LogModule.APP, `Window created successfully.`);
|
||||
createTray(config);
|
||||
|
||||
if (config) {
|
||||
logInfo(
|
||||
LogModule.APP,
|
||||
`Config retrieved: ${JSON.stringify(
|
||||
maskSensitiveData(config, [
|
||||
"serverAddr",
|
||||
"serverPort",
|
||||
"authToken",
|
||||
"user",
|
||||
"metaToken"
|
||||
])
|
||||
)}`
|
||||
);
|
||||
|
||||
if (config.systemStartupConnect) {
|
||||
startFrpWorkerProcess(config);
|
||||
}
|
||||
}
|
||||
// Initialize APIs
|
||||
try {
|
||||
initGitHubApi(win);
|
||||
logInfo(LogModule.APP, `GitHub API initialized.`);
|
||||
|
||||
initConfigApi(win);
|
||||
logInfo(LogModule.APP, `Config API initialized.`);
|
||||
|
||||
initProxyApi();
|
||||
logInfo(LogModule.APP, `Proxy API initialized.`);
|
||||
|
||||
initFrpcApi();
|
||||
logInfo(LogModule.APP, `FRPC API initialized.`);
|
||||
|
||||
initLoggerApi();
|
||||
logInfo(LogModule.APP, `Logger API initialized.`);
|
||||
|
||||
initFileApi();
|
||||
logInfo(LogModule.APP, `File API initialized.`);
|
||||
|
||||
initCommonApi();
|
||||
logInfo(LogModule.APP, `Common API initialized.`);
|
||||
|
||||
initLocalApi();
|
||||
logInfo(LogModule.APP, `Local API initialized.`);
|
||||
|
||||
// initUpdaterApi(win);
|
||||
logInfo(LogModule.APP, `Updater API initialization skipped.`);
|
||||
} catch (error) {
|
||||
logError(
|
||||
LogModule.APP,
|
||||
`Error during API initialization: ${error.message}`
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
logError(LogModule.APP, `Error creating window: ${error.message}`);
|
||||
});
|
||||
});
|
||||
createWindow().then(r => {
|
||||
createTray()
|
||||
// 初始化各个API
|
||||
initGitHubApi();
|
||||
initConfigApi(win);
|
||||
initProxyApi();
|
||||
initFrpcApi();
|
||||
initLoggerApi();
|
||||
initFileApi();
|
||||
initCommonApi();
|
||||
initLocalApi();
|
||||
// initUpdaterApi(win);
|
||||
})
|
||||
});
|
||||
|
||||
app.on("window-all-closed", () => {
|
||||
logInfo(LogModule.APP, `All windows closed.`);
|
||||
win = null;
|
||||
if (process.platform !== "darwin") {
|
||||
stopFrpcProcess(() => {
|
||||
logInfo(LogModule.APP, `FRPC process stopped. Quitting application.`);
|
||||
app.quit();
|
||||
});
|
||||
}
|
||||
win = null;
|
||||
if (process.platform !== "darwin") {
|
||||
stopFrpcProcess(() => {
|
||||
app.quit();
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
app.on("second-instance", () => {
|
||||
logInfo(LogModule.APP, `Second instance detected.`);
|
||||
if (win) {
|
||||
// Focus on the main window if the user tried to open another
|
||||
if (win.isMinimized()) win.restore();
|
||||
win.focus();
|
||||
}
|
||||
if (win) {
|
||||
// Focus on the main window if the user tried to open another
|
||||
if (win.isMinimized()) win.restore();
|
||||
win.focus();
|
||||
}
|
||||
});
|
||||
|
||||
app.on("activate", () => {
|
||||
logInfo(LogModule.APP, `Application activated.`);
|
||||
const allWindows = BrowserWindow.getAllWindows();
|
||||
if (allWindows.length) {
|
||||
allWindows[0].focus();
|
||||
} else {
|
||||
getConfig((err, config) => {
|
||||
if (err) {
|
||||
logError(
|
||||
LogModule.APP,
|
||||
`Failed to get config on activate: ${err.message}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
createWindow(config).then(r => {
|
||||
logInfo(LogModule.APP, `Window created on activate.`);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.on("before-quit", () => {
|
||||
logInfo(LogModule.APP, `Application is about to quit.`);
|
||||
stopFrpcProcess(() => {
|
||||
isQuiting = true;
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.handle("open-win", (_, arg) => {
|
||||
logInfo(LogModule.APP, `Opening new window with argument: ${arg}`);
|
||||
const childWindow = new BrowserWindow({
|
||||
webPreferences: {
|
||||
preload,
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false
|
||||
const allWindows = BrowserWindow.getAllWindows();
|
||||
if (allWindows.length) {
|
||||
allWindows[0].focus();
|
||||
} else {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('before-quit', () => {
|
||||
log.info("before-quit")
|
||||
isQuiting = true;
|
||||
})
|
||||
|
||||
// New window example arg: new windows url
|
||||
ipcMain.handle("open-win", (_, arg) => {
|
||||
const childWindow = new BrowserWindow({
|
||||
webPreferences: {
|
||||
preload,
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false
|
||||
}
|
||||
});
|
||||
|
||||
if (process.env.VITE_DEV_SERVER_URL) {
|
||||
childWindow.loadURL(`${url}#${arg}`);
|
||||
} else {
|
||||
childWindow.loadFile(indexHtml, {hash: arg});
|
||||
}
|
||||
});
|
||||
|
||||
if (process.env.VITE_DEV_SERVER_URL) {
|
||||
childWindow.loadURL(`${url}#${arg}`);
|
||||
logInfo(LogModule.APP, `Child window loaded URL: ${url}#${arg}`);
|
||||
} else {
|
||||
childWindow.loadFile(indexHtml, { hash: arg });
|
||||
logInfo(
|
||||
LogModule.APP,
|
||||
`Child window loaded file: ${indexHtml} with hash: ${arg}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -2,13 +2,13 @@ import Datastore from "nedb";
|
||||
import path from "path";
|
||||
import { app } from "electron";
|
||||
|
||||
import { logInfo, logError, LogModule, logDebug } from "../utils/log";
|
||||
import { maskSensitiveData } from "../utils/desensitize";
|
||||
const log = require("electron-log");
|
||||
|
||||
const configDB = new Datastore({
|
||||
autoload: true,
|
||||
filename: path.join(app.getPath("userData"), "config.db")
|
||||
});
|
||||
|
||||
/**
|
||||
* 保存
|
||||
*/
|
||||
@ -17,37 +17,8 @@ export const saveConfig = (
|
||||
cb?: (err: Error | null, numberOfUpdated: number, upsert: boolean) => void
|
||||
) => {
|
||||
document["_id"] = "1";
|
||||
logDebug(
|
||||
LogModule.DB,
|
||||
`Saving configuration to the database. ${JSON.stringify(
|
||||
maskSensitiveData(document, [
|
||||
"serverAddr",
|
||||
"serverPort",
|
||||
"authToken",
|
||||
"user",
|
||||
"metaToken"
|
||||
])
|
||||
)}`
|
||||
);
|
||||
configDB.update(
|
||||
{ _id: "1" },
|
||||
document,
|
||||
{ upsert: true },
|
||||
(err, numberOfUpdated, upsert) => {
|
||||
if (err) {
|
||||
logError(
|
||||
LogModule.DB,
|
||||
`Error saving configuration: ${err.message}`
|
||||
);
|
||||
} else {
|
||||
logInfo(
|
||||
LogModule.DB,
|
||||
`Configuration saved successfully. Updated: ${numberOfUpdated}, Upsert: ${upsert}`
|
||||
); // 添加成功日志
|
||||
}
|
||||
if (cb) cb(err, numberOfUpdated, upsert);
|
||||
}
|
||||
);
|
||||
log.debug(`保存日志 ${JSON.stringify(document)}`);
|
||||
configDB.update({ _id: "1" }, document, { upsert: true }, cb);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -57,34 +28,9 @@ export const saveConfig = (
|
||||
export const getConfig = (
|
||||
cb: (err: Error | null, document: FrpConfig) => void
|
||||
) => {
|
||||
logInfo(LogModule.DB, "Retrieving configuration from the database."); // 添加信息日志
|
||||
configDB.findOne({ _id: "1" }, (err, document) => {
|
||||
if (err) {
|
||||
logError(
|
||||
LogModule.DB,
|
||||
`Error retrieving configuration: ${err.message}`
|
||||
); // 添加错误日志
|
||||
} else {
|
||||
logInfo(LogModule.DB, "Configuration retrieved successfully."); // 添加成功日志
|
||||
}
|
||||
cb(err, document);
|
||||
});
|
||||
configDB.findOne({ _id: "1" }, cb);
|
||||
};
|
||||
|
||||
export const clearConfig = (cb?: (err: Error | null, n: number) => void) => {
|
||||
logInfo(LogModule.DB, "Clearing all configurations from the database."); // 添加信息日志
|
||||
configDB.remove({}, { multi: true }, (err, n) => {
|
||||
if (err) {
|
||||
logError(
|
||||
LogModule.DB,
|
||||
`Error clearing configurations: ${err.message}`
|
||||
); // 添加错误日志
|
||||
} else {
|
||||
logInfo(
|
||||
LogModule.DB,
|
||||
`Successfully cleared configurations. Number of documents removed: ${n}`
|
||||
); // 添加成功日志
|
||||
}
|
||||
if (cb) cb(err, n);
|
||||
});
|
||||
};
|
||||
configDB.remove({}, { multi: true }, cb);
|
||||
};
|
@ -2,142 +2,90 @@ import Datastore from "nedb";
|
||||
import path from "path";
|
||||
import { app } from "electron";
|
||||
|
||||
import { logInfo, logError, LogModule, logDebug } from "../utils/log";
|
||||
const log = require("electron-log");
|
||||
|
||||
const proxyDB = new Datastore({
|
||||
autoload: true,
|
||||
filename: path.join(app.getPath("userData"), "proxy.db")
|
||||
});
|
||||
|
||||
/**
|
||||
* 新增代理
|
||||
* @param proxy
|
||||
* @param cb
|
||||
*/
|
||||
export const insertProxy = (
|
||||
proxy: Proxy,
|
||||
cb?: (err: Error | null, document: Proxy) => void
|
||||
) => {
|
||||
logInfo(LogModule.DB, `Inserting proxy: ${JSON.stringify(proxy)}`);
|
||||
proxyDB.insert(proxy, (err, document) => {
|
||||
if (err) {
|
||||
logError(LogModule.DB, `Error inserting proxy: ${err.message}`);
|
||||
} else {
|
||||
logInfo(
|
||||
LogModule.DB,
|
||||
`Proxy inserted successfully: ${JSON.stringify(document)}`
|
||||
);
|
||||
}
|
||||
if (cb) cb(err, document);
|
||||
});
|
||||
log.debug(`新增代理:${JSON.stringify(proxy)}`);
|
||||
proxyDB.insert(proxy, cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除代理
|
||||
* @param _id
|
||||
* @param cb
|
||||
*/
|
||||
export const deleteProxyById = (
|
||||
_id: string,
|
||||
cb?: (err: Error | null, n: number) => void
|
||||
) => {
|
||||
logInfo(LogModule.DB, `Deleting proxy with ID: ${_id}`);
|
||||
proxyDB.remove({ _id: _id }, (err, n) => {
|
||||
if (err) {
|
||||
logError(LogModule.DB, `Error deleting proxy: ${err.message}`);
|
||||
} else {
|
||||
logInfo(
|
||||
LogModule.DB,
|
||||
`Proxy deleted successfully. Number of documents removed: ${n}`
|
||||
);
|
||||
}
|
||||
if (cb) cb(err, n);
|
||||
});
|
||||
log.debug(`删除代理:${_id}`);
|
||||
proxyDB.remove({ _id: _id }, cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改代理
|
||||
*/
|
||||
export const updateProxyById = (
|
||||
proxy: Proxy,
|
||||
cb?: (err: Error | null, numberOfUpdated: number, upsert: boolean) => void
|
||||
) => {
|
||||
logInfo(LogModule.DB, `Updating proxy: ${JSON.stringify(proxy)}`);
|
||||
proxyDB.update(
|
||||
{ _id: proxy._id },
|
||||
proxy,
|
||||
{},
|
||||
(err, numberOfUpdated, upsert) => {
|
||||
if (err) {
|
||||
logError(LogModule.DB, `Error updating proxy: ${err.message}`);
|
||||
} else {
|
||||
logInfo(
|
||||
LogModule.DB,
|
||||
`Proxy updated successfully. Updated: ${numberOfUpdated}, Upsert: ${upsert}`
|
||||
);
|
||||
}
|
||||
if (cb) cb(err, numberOfUpdated, upsert);
|
||||
}
|
||||
);
|
||||
log.debug(`修改代理:${proxy}`);
|
||||
proxyDB.update({ _id: proxy._id }, proxy, {}, cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* 查找
|
||||
* @param cb
|
||||
*/
|
||||
export const listProxy = (
|
||||
callback: (err: Error | null, documents: Proxy[]) => void
|
||||
) => {
|
||||
logInfo(LogModule.DB, `Listing all proxies`);
|
||||
proxyDB.find({}, (err, documents) => {
|
||||
if (err) {
|
||||
logError(LogModule.DB, `Error listing proxies: ${err.message}`);
|
||||
} else {
|
||||
logInfo(
|
||||
LogModule.DB,
|
||||
`Proxies listed successfully. Count: ${documents.length}`
|
||||
);
|
||||
}
|
||||
callback(err, documents);
|
||||
});
|
||||
proxyDB.find({}, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据id查询
|
||||
* @param id
|
||||
* @param callback
|
||||
*/
|
||||
export const getProxyById = (
|
||||
id: string,
|
||||
callback: (err: Error | null, document: Proxy) => void
|
||||
) => {
|
||||
logInfo(LogModule.DB, `Getting proxy by ID: ${id}`);
|
||||
proxyDB.findOne({ _id: id }, (err, document) => {
|
||||
if (err) {
|
||||
logError(LogModule.DB, `Error getting proxy by ID: ${err.message}`);
|
||||
} else {
|
||||
logInfo(
|
||||
LogModule.DB,
|
||||
`Proxy retrieved successfully: ${JSON.stringify(document)}`
|
||||
);
|
||||
}
|
||||
callback(err, document);
|
||||
});
|
||||
proxyDB.findOne({ _id: id }, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* 清空代理
|
||||
* @param cb
|
||||
*/
|
||||
export const clearProxy = (cb?: (err: Error | null, n: number) => void) => {
|
||||
logInfo(LogModule.DB, `Clearing all proxies`);
|
||||
proxyDB.remove({}, { multi: true }, (err, n) => {
|
||||
if (err) {
|
||||
logError(LogModule.DB, `Error clearing proxies: ${err.message}`);
|
||||
} else {
|
||||
logInfo(
|
||||
LogModule.DB,
|
||||
`Proxies cleared successfully. Number of documents removed: ${n}`
|
||||
);
|
||||
}
|
||||
if (cb) cb(err, n);
|
||||
});
|
||||
proxyDB.remove({}, { multi: true }, cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新代理状态
|
||||
* @param id id
|
||||
* @param st 状态
|
||||
* @param cb 回调
|
||||
*/
|
||||
export const updateProxyStatus = (
|
||||
id: string,
|
||||
st: boolean,
|
||||
cb?: (err: Error | null, numberOfUpdated: number, upsert: boolean) => void
|
||||
) => {
|
||||
logInfo(LogModule.DB, `Updating proxy status for ID: ${id} to ${st}`);
|
||||
proxyDB.update(
|
||||
{ _id: id },
|
||||
{ $set: { status: st } },
|
||||
{},
|
||||
(err, numberOfUpdated, upsert) => {
|
||||
if (err) {
|
||||
logError(LogModule.DB, `Error updating proxy status: ${err.message}`);
|
||||
} else {
|
||||
logInfo(
|
||||
LogModule.DB,
|
||||
`Proxy status updated successfully. Updated: ${numberOfUpdated}, Upsert: ${upsert}`
|
||||
);
|
||||
}
|
||||
if (cb) cb(err, numberOfUpdated, upsert);
|
||||
}
|
||||
);
|
||||
proxyDB.update({ _id: id }, { $set: { status: st } }, {}, cb);
|
||||
};
|
||||
|
@ -2,7 +2,7 @@ import Datastore from "nedb";
|
||||
import path from "path";
|
||||
import { app } from "electron";
|
||||
|
||||
import { logInfo, logError, LogModule, logDebug } from "../utils/log";
|
||||
const log = require("electron-log");
|
||||
|
||||
const versionDB = new Datastore({
|
||||
autoload: true,
|
||||
@ -10,7 +10,7 @@ const versionDB = new Datastore({
|
||||
});
|
||||
|
||||
/**
|
||||
* Insert version
|
||||
* 新增版本
|
||||
* @param version
|
||||
* @param cb
|
||||
*/
|
||||
@ -18,73 +18,35 @@ export const insertVersion = (
|
||||
version: FrpVersion,
|
||||
cb?: (err: Error | null, document: any) => void
|
||||
) => {
|
||||
logInfo(LogModule.DB, `Inserting version: ${JSON.stringify(version)}`);
|
||||
versionDB.insert(version, (err, document) => {
|
||||
if (err) {
|
||||
logError(LogModule.DB, `Error inserting version: ${err.message}`);
|
||||
} else {
|
||||
logInfo(LogModule.DB, `Version inserted successfully: ${JSON.stringify(document)}`);
|
||||
}
|
||||
if (cb) cb(err, document);
|
||||
});
|
||||
log.debug(`新增版本:${JSON.stringify(version)}`);
|
||||
versionDB.insert(version, cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* List versions
|
||||
* 查找
|
||||
* @param cb
|
||||
*/
|
||||
export const listVersion = (
|
||||
callback: (err: Error | null, documents: FrpVersion[]) => void
|
||||
) => {
|
||||
logInfo(LogModule.DB, "Listing all versions.");
|
||||
versionDB.find({}, (err, documents) => {
|
||||
if (err) {
|
||||
logError(LogModule.DB, `Error listing versions: ${err.message}`);
|
||||
} else {
|
||||
logInfo(LogModule.DB, `Successfully listed versions: ${documents.length} found.`);
|
||||
}
|
||||
callback(err, documents);
|
||||
});
|
||||
versionDB.find({}, callback);
|
||||
};
|
||||
|
||||
export const getVersionById = (
|
||||
id: number,
|
||||
callback: (err: Error | null, document: FrpVersion) => void
|
||||
) => {
|
||||
logInfo(LogModule.DB, `Retrieving version by ID: ${id}`);
|
||||
versionDB.findOne({ id: id }, (err, document) => {
|
||||
if (err) {
|
||||
logError(LogModule.DB, `Error retrieving version by ID: ${err.message}`);
|
||||
} else {
|
||||
logInfo(LogModule.DB, `Version retrieved successfully: ${JSON.stringify(document)}`);
|
||||
}
|
||||
callback(err, document);
|
||||
});
|
||||
versionDB.findOne({ id: id }, callback);
|
||||
};
|
||||
|
||||
export const deleteVersionById = (
|
||||
id: string,
|
||||
callback: (err: Error | null, document: any) => void
|
||||
) => {
|
||||
logInfo(LogModule.DB, `Deleting version: ${id}`);
|
||||
versionDB.remove({ id: id }, (err, document) => {
|
||||
if (err) {
|
||||
logError(LogModule.DB, `Error deleting version: ${err.message}`);
|
||||
} else {
|
||||
logInfo(LogModule.DB, `Version deleted successfully: ${id}`);
|
||||
}
|
||||
callback(err, document);
|
||||
});
|
||||
log.debug(`删除版本:${id}`);
|
||||
versionDB.remove({ id: id }, callback);
|
||||
};
|
||||
|
||||
export const clearVersion = (cb?: (err: Error | null, n: number) => void) => {
|
||||
logInfo(LogModule.DB, "Clearing all versions from the database.");
|
||||
versionDB.remove({}, { multi: true }, (err, n) => {
|
||||
if (err) {
|
||||
logError(LogModule.DB, `Error clearing versions: ${err.message}`);
|
||||
} else {
|
||||
logInfo(LogModule.DB, `Successfully cleared versions. Number of documents removed: ${n}`);
|
||||
}
|
||||
if (cb) cb(err, n);
|
||||
});
|
||||
versionDB.remove({}, { multi: true }, cb);
|
||||
};
|
||||
|
@ -1,12 +0,0 @@
|
||||
export const maskSensitiveData = (
|
||||
obj: Record<string, any>,
|
||||
keysToMask: string[]
|
||||
) => {
|
||||
const maskedObj = JSON.parse(JSON.stringify(obj));
|
||||
keysToMask.forEach(key => {
|
||||
if (maskedObj.hasOwnProperty(key)) {
|
||||
maskedObj[key] = "***";
|
||||
}
|
||||
});
|
||||
return maskedObj;
|
||||
};
|
@ -1,19 +0,0 @@
|
||||
import { createHash } from "crypto";
|
||||
|
||||
export const formatBytes = (bytes: number, decimals: number = 2): string => {
|
||||
if (bytes === 0) return "0 Bytes";
|
||||
const k = 1024; // 1 KB = 1024 Bytes
|
||||
const dm = decimals < 0 ? 0 : decimals; // Ensure decimal places are not less than 0
|
||||
const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k)); // Calculate unit index
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i]; // Return formatted string
|
||||
};
|
||||
|
||||
export const calculateFileChecksum = (filePath: string): string => {
|
||||
const fs = require("fs");
|
||||
const fileBuffer = fs.readFileSync(filePath);
|
||||
const hash = createHash("sha256");
|
||||
hash.update(fileBuffer);
|
||||
return hash.digest("hex");
|
||||
};
|
@ -1,30 +0,0 @@
|
||||
import log from "electron-log";
|
||||
// 定义模块枚举
|
||||
export enum LogModule {
|
||||
APP = "app",
|
||||
FRP_CLIENT = "frpc client",
|
||||
GITHUB = "github",
|
||||
DB = "db"
|
||||
}
|
||||
|
||||
export const initLog = () => {
|
||||
log.transports.file.level = "debug";
|
||||
log.transports.console.level = "debug";
|
||||
};
|
||||
|
||||
// 自定义日志输出函数,记录到指定业务模块
|
||||
export const logInfo = (module: LogModule, message: string) => {
|
||||
log.info(`[${module}] ${message}`);
|
||||
};
|
||||
|
||||
export const logError = (module: LogModule, message: string) => {
|
||||
log.error(`[${module}] ${message}`);
|
||||
};
|
||||
|
||||
export const logDebug = (module: LogModule, message: string) => {
|
||||
log.debug(`[${module}] ${message}`);
|
||||
};
|
||||
|
||||
export const logWarn = (module: LogModule, message: string) => {
|
||||
log.warn(`[${module}] ${message}`);
|
||||
};
|
13
package.json
13
package.json
@ -1,21 +1,17 @@
|
||||
{
|
||||
"name": "Frpc-Desktop",
|
||||
"version": "1.1.6",
|
||||
"version": "1.1.0",
|
||||
"main": "dist-electron/main/index.js",
|
||||
"description": "FRP跨平台桌面客户端,可视化配置,轻松实现内网穿透!",
|
||||
"repository": "github:luckjiawei/frpc-desktop",
|
||||
"author": "刘嘉伟 <8473136@qq.com>",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"type": "commonjs",
|
||||
"keywords": [
|
||||
"frp",
|
||||
"frpc",
|
||||
"proxy",
|
||||
"electron-app",
|
||||
"vue",
|
||||
"vue3",
|
||||
"vite"
|
||||
"electron-app"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
@ -30,7 +26,6 @@
|
||||
"build": "vue-tsc --noEmit && vite build",
|
||||
"build:electron": "npm run build && electron-builder --mac --win --linux",
|
||||
"build:electron:mac": "npm run build && electron-builder --mac",
|
||||
"build:electron:mac:arm": "npm run build && electron-builder --mac --arm64",
|
||||
"build:electron:win": "npm run build && electron-builder --win",
|
||||
"build:electron:linux": "npm run build && electron-builder --linux",
|
||||
"release": "npm run build && electron-builder --mac --win --linux -p always",
|
||||
@ -70,14 +65,14 @@
|
||||
"vite-plugin-electron-renderer": "^0.14.5",
|
||||
"vue": "^3.3.4",
|
||||
"vue-router": "^4.2.4",
|
||||
"vue-tsc": "2.0.22",
|
||||
"vue-tsc": "^2.0.22",
|
||||
"vue-types": "^5.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"adm-zip": "^0.5.14",
|
||||
"animate.css": "^4.1.1",
|
||||
"electron-dl": "3.5.1",
|
||||
"electron-dl": "^3.5.1",
|
||||
"electron-log": "^5.1.7",
|
||||
"intro.js": "^8.0.0-beta.1",
|
||||
"isbinaryfile": "4.0.10",
|
||||
|
@ -1,5 +1,10 @@
|
||||
import {addIcon} from "@iconify/vue/dist/offline";
|
||||
|
||||
/**
|
||||
* 这里存放本地图标,在 src/layout/index.vue 文件中加载,避免在首启动加载
|
||||
*/
|
||||
|
||||
// 本地菜单图标,后端在路由的icon中返回对应的图标字符串并且前端在此处使用addIcon添加即可渲染菜单图标
|
||||
import Cloud from "@iconify-icons/material-symbols/cloud";
|
||||
import RocketLaunchRounded from "@iconify-icons/material-symbols/rocket-launch-rounded";
|
||||
import Download from "@iconify-icons/material-symbols/download-2";
|
||||
@ -12,6 +17,7 @@ import MoreVert from "@iconify-icons/material-symbols/more-vert";
|
||||
import Add from "@iconify-icons/material-symbols/add";
|
||||
import BringYourOwnIpRounded from "@iconify-icons/material-symbols/bring-your-own-ip-rounded";
|
||||
import DeleteRounded from "@iconify-icons/material-symbols/delete-rounded";
|
||||
import RefreshRounded from "@iconify-icons/material-symbols/refresh-rounded";
|
||||
import CancelPresentation from "@iconify-icons/material-symbols/cancel-presentation";
|
||||
import GestureSelect from "@iconify-icons/material-symbols/gesture-select";
|
||||
import SaveRounded from "@iconify-icons/material-symbols/save-rounded";
|
||||
@ -29,16 +35,7 @@ import downloadRounded from "@iconify-icons/material-symbols/download-rounded";
|
||||
import deviceReset from "@iconify-icons/material-symbols/device-reset";
|
||||
import switchAccessOutlineRounded from "@iconify-icons/material-symbols/switch-access-outline-rounded";
|
||||
import switchAccessRounded from "@iconify-icons/material-symbols/switch-access-rounded";
|
||||
import chargerRounded from "@iconify-icons/material-symbols/charger-rounded";
|
||||
import fileOpenRounded from "@iconify-icons/material-symbols/file-open-rounded";
|
||||
import attachMoneyRounded from "@iconify-icons/material-symbols/attach-money-rounded";
|
||||
import volunteerActivismSharp from "@iconify-icons/material-symbols/volunteer-activism-sharp";
|
||||
import description from "@iconify-icons/material-symbols/description";
|
||||
import folderRounded from "@iconify-icons/material-symbols/folder-rounded";
|
||||
import link from "@iconify-icons/material-symbols/link";
|
||||
import unarchive from "@iconify-icons/material-symbols/unarchive";
|
||||
import fileSaveRounded from "@iconify-icons/material-symbols/file-save-rounded";
|
||||
|
||||
import localFireDepartment from "@iconify-icons/material-symbols/local-fire-department";
|
||||
addIcon("cloud", Cloud);
|
||||
addIcon("rocket-launch-rounded", RocketLaunchRounded);
|
||||
addIcon("download", Download);
|
||||
@ -49,11 +46,11 @@ addIcon("refresh-rounded", refreshRounded);
|
||||
addIcon("more-vert", MoreVert);
|
||||
addIcon("add", Add);
|
||||
addIcon("bring-your-own-ip-rounded", BringYourOwnIpRounded);
|
||||
addIcon("charger-rounded", chargerRounded);
|
||||
addIcon("delete-rounded", DeleteRounded);
|
||||
addIcon("cancel-presentation", CancelPresentation);
|
||||
addIcon("gesture-select", GestureSelect);
|
||||
addIcon("save-rounded", SaveRounded);
|
||||
addIcon("refresh-rounded", RefreshRounded);
|
||||
addIcon("info", Info);
|
||||
addIcon("question-mark", QuestionMark);
|
||||
addIcon("check-circle-rounded", CheckCircleRounded);
|
||||
@ -68,12 +65,6 @@ addIcon("downloadRounded", downloadRounded);
|
||||
addIcon("deviceReset", deviceReset);
|
||||
addIcon("switchAccessOutlineRounded", switchAccessOutlineRounded);
|
||||
addIcon("switchAccessRounded", switchAccessRounded);
|
||||
addIcon("file-open-rounded", fileOpenRounded);
|
||||
addIcon("attach-money-rounded", attachMoneyRounded);
|
||||
addIcon("volunteer-activism-sharp", volunteerActivismSharp);
|
||||
addIcon("description", description);
|
||||
addIcon("folder-rounded", folderRounded);
|
||||
addIcon("link", link);
|
||||
addIcon("unarchive", unarchive);
|
||||
addIcon("file-save-rounded", fileSaveRounded);
|
||||
addIcon("localFireDepartment", localFireDepartment);
|
||||
|
||||
|
||||
|
@ -95,7 +95,7 @@ onMounted(() => {
|
||||
<div class="logo-container">
|
||||
<img
|
||||
src="/logo/only/128x128.png"
|
||||
class="logo animate__animated animate__bounceInLeft"
|
||||
class="logo animate__animated animate__flip"
|
||||
alt="Logo"
|
||||
/>
|
||||
</div>
|
||||
@ -118,28 +118,14 @@ onMounted(() => {
|
||||
></IconifyIconOffline>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="menu-footer mb-2">
|
||||
<!-- <div-->
|
||||
<!-- class="menu animate__animated"-->
|
||||
<!-- @click="handleOpenGitHubReleases"-->
|
||||
<!-- :data-step="guideSteps.Version?.step"-->
|
||||
<!-- :data-intro="guideSteps.Version?.intro"-->
|
||||
<!-- data-position="top"-->
|
||||
<!-- >-->
|
||||
<!-- <IconifyIconOffline-->
|
||||
<!-- class="animate__animated"-->
|
||||
<!-- icon="attach-money-rounded"-->
|
||||
<!-- ></IconifyIconOffline>-->
|
||||
<!-- </div>-->
|
||||
<div
|
||||
class="version animate__animated"
|
||||
@click="handleOpenGitHubReleases"
|
||||
:data-step="guideSteps.Version?.step"
|
||||
:data-intro="guideSteps.Version?.intro"
|
||||
data-position="top"
|
||||
>
|
||||
{{ pkg.version }}
|
||||
</div>
|
||||
<div
|
||||
class="version mb-2 animate__animated"
|
||||
@click="handleOpenGitHubReleases"
|
||||
:data-step="guideSteps.Version?.step"
|
||||
:data-intro="guideSteps.Version?.intro"
|
||||
data-position="top"
|
||||
>
|
||||
{{ pkg.version }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -60,6 +60,17 @@ const routes: RouteRecordRaw[] = [
|
||||
},
|
||||
component: () => import("@/views/logger/index.vue")
|
||||
},
|
||||
{
|
||||
path: "/welfare",
|
||||
name: "Welfare",
|
||||
meta: {
|
||||
title: "公益",
|
||||
icon: "localFireDepartment",
|
||||
keepAlive: true,
|
||||
hidden: false
|
||||
},
|
||||
component: () => import("@/views/welfare/index.vue")
|
||||
},
|
||||
{
|
||||
path: "/about",
|
||||
name: "About",
|
||||
|
@ -1,8 +1,8 @@
|
||||
@use "reset";
|
||||
@use "layout";
|
||||
@use "element";
|
||||
@use "tailwind";
|
||||
@use "scrollbar";
|
||||
@import "reset";
|
||||
@import "layout";
|
||||
@import "element";
|
||||
@import "tailwind";
|
||||
@import "scrollbar";
|
||||
/* 自定义全局 CssVar */
|
||||
:root {
|
||||
--pure-transition-duration: 0.016s;
|
||||
|
@ -54,18 +54,11 @@ $danger-color: #F56C6C;
|
||||
display: flex; /* 设置为 flexbox */
|
||||
flex-direction: column; /* 纵向排列子元素 */
|
||||
|
||||
.menu-footer {
|
||||
margin-top: auto;
|
||||
|
||||
}
|
||||
|
||||
.version {
|
||||
height: 40px;
|
||||
width: 60px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: $primary-color;
|
||||
text-align: center;
|
||||
margin-top: auto;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
@ -75,7 +68,7 @@ $danger-color: #F56C6C;
|
||||
animation: heartBeat 1s;
|
||||
}
|
||||
|
||||
.menu-container, .menu-footer {
|
||||
.menu-container {
|
||||
|
||||
.menu {
|
||||
display: flex;
|
||||
|
@ -44,22 +44,6 @@ const handleOpenGitHub = () => {
|
||||
ipcRenderer.send("common.openUrl", "https://github.com/luckjiawei/frpc-desktop")
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开捐赠界面
|
||||
*/
|
||||
const handleOpenDonate = () => {
|
||||
ipcRenderer.send("common.openUrl", "https://jwinks.com/donate")
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 打开文档
|
||||
*/
|
||||
const handleOpenDoc = () => {
|
||||
ipcRenderer.send("common.openUrl", "https://jwinks.com/p/frp")
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取最后一个版本
|
||||
*/
|
||||
@ -140,14 +124,6 @@ defineComponent({
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-[12px]">
|
||||
<el-button plain type="success" @click="handleOpenDoc">
|
||||
<IconifyIconOffline class="cursor-pointer mr-2" icon="description"/>
|
||||
使用教程
|
||||
</el-button>
|
||||
<el-button plain type="success" @click="handleOpenDonate">
|
||||
<IconifyIconOffline class="cursor-pointer mr-2" icon="volunteer-activism-sharp"/>
|
||||
捐赠名单
|
||||
</el-button>
|
||||
<el-button plain type="primary" @click="handleOpenGitHub">
|
||||
<Icon class="cursor-pointer mr-2" icon="logos:github-icon"/>
|
||||
仓库地址
|
||||
|
@ -41,19 +41,10 @@ const defaultFormData = ref<FrpConfig>({
|
||||
proxyConfigProxyUrl: "",
|
||||
systemSelfStart: false,
|
||||
systemStartupConnect: false,
|
||||
systemSilentStartup: false,
|
||||
user: "",
|
||||
metaToken: "",
|
||||
transportHeartbeatInterval: 30,
|
||||
transportHeartbeatTimeout: 90,
|
||||
webEnable: true,
|
||||
webPort: 57400,
|
||||
transportProtocol: "tcp",
|
||||
transportDialServerTimeout: 10,
|
||||
transportDialServerKeepalive: 7200,
|
||||
transportPoolCount: 0,
|
||||
transportTcpMux: true,
|
||||
transportTcpMuxKeepaliveInterval: 30
|
||||
transportHeartbeatTimeout: 90
|
||||
});
|
||||
|
||||
const formData = ref<FrpConfig>(defaultFormData.value);
|
||||
@ -110,41 +101,11 @@ const rules = reactive<FormRules>({
|
||||
systemSelfStart: [
|
||||
{ required: true, message: "请选择是否开机自启", trigger: "change" }
|
||||
],
|
||||
systemSilentStartup: [
|
||||
{ required: true, message: "请选择是否开启静默启动", trigger: "change" }
|
||||
],
|
||||
systemStartupConnect: [
|
||||
{ required: true, message: "请选择是否开启自动连接", trigger: "change" }
|
||||
],
|
||||
transportHeartbeatInterval: [
|
||||
{ required: true, message: "心跳间隔时间不能为空", trigger: "change" }
|
||||
],
|
||||
transportHeartbeatTimeout: [
|
||||
{ required: true, message: "心跳超时时间不能为空", trigger: "change" }
|
||||
],
|
||||
webEnable: [
|
||||
{ required: true, message: "web界面开关不能为空", trigger: "change" }
|
||||
],
|
||||
webPort: [
|
||||
{ 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" }
|
||||
]
|
||||
});
|
||||
|
||||
@ -219,50 +180,6 @@ onMounted(() => {
|
||||
data.transportHeartbeatTimeout =
|
||||
defaultFormData.value.transportHeartbeatTimeout;
|
||||
}
|
||||
if (data.webEnable == null || data.webEnable == undefined) {
|
||||
data.webEnable = defaultFormData.value.webEnable;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -321,15 +238,6 @@ onMounted(() => {
|
||||
ElMessageBox.alert(data, `提示`);
|
||||
}
|
||||
});
|
||||
|
||||
ipcRenderer.on("Config.openDataFolder.hook", (event, args) => {
|
||||
if (args) {
|
||||
ElMessage({
|
||||
type: "success",
|
||||
message: "打开数据目录成功"
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const handleSelectFile = (type: number, ext: string[]) => {
|
||||
@ -444,36 +352,25 @@ const handleResetConfig = () => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 打开数据目录
|
||||
*/
|
||||
const handleOpenDataFolder = useDebounceFn(() => {
|
||||
ipcRenderer.send("config.openDataFolder");
|
||||
}, 1000);
|
||||
|
||||
onUnmounted(() => {
|
||||
ipcRenderer.removeAllListeners("Config.getConfig.hook");
|
||||
ipcRenderer.removeAllListeners("Config.saveConfig.hook");
|
||||
ipcRenderer.removeAllListeners("Config.versions.hook");
|
||||
ipcRenderer.removeAllListeners("Config.exportConfig.hook");
|
||||
ipcRenderer.removeAllListeners("Config.clearAll.hook");
|
||||
ipcRenderer.removeAllListeners("Config.openDataFolder.hook");
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="main">
|
||||
<breadcrumb>
|
||||
<el-button plain type="primary" @click="handleOpenDataFolder">
|
||||
<IconifyIconOffline icon="folder-rounded" />
|
||||
</el-button>
|
||||
<el-button plain type="primary" @click="handleResetConfig">
|
||||
<IconifyIconOffline icon="deviceReset" />
|
||||
</el-button>
|
||||
<el-button plain type="primary" @click="handleImportConfig">
|
||||
<IconifyIconOffline icon="file-open-rounded" />
|
||||
<IconifyIconOffline icon="uploadRounded" />
|
||||
</el-button>
|
||||
<el-button plain type="primary" @click="handleShowExportDialog">
|
||||
<IconifyIconOffline icon="file-save-rounded" />
|
||||
<IconifyIconOffline icon="downloadRounded" />
|
||||
</el-button>
|
||||
<el-button type="primary" @click="handleSubmit">
|
||||
<IconifyIconOffline icon="save-rounded" />
|
||||
@ -486,7 +383,7 @@ onUnmounted(() => {
|
||||
:rules="rules"
|
||||
label-position="right"
|
||||
ref="formRef"
|
||||
label-width="150"
|
||||
label-width="130"
|
||||
>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="24">
|
||||
@ -640,7 +537,6 @@ onUnmounted(() => {
|
||||
placeholder="token"
|
||||
type="password"
|
||||
v-model="formData.authToken"
|
||||
:show-password="true"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@ -696,72 +592,6 @@ onUnmounted(() => {
|
||||
/>
|
||||
</el-form-item>
|
||||
</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-form-item
|
||||
label="心跳间隔:"
|
||||
@ -845,178 +675,11 @@ onUnmounted(() => {
|
||||
<!-- </el-input>-->
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<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 :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 :span="24">
|
||||
<div class="h2">TLS Config</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-form-item label="启用TLS:" prop="tlsConfigEnable">
|
||||
<el-form-item label="是否启用TLS:" prop="tlsConfigEnable">
|
||||
<el-switch
|
||||
active-text="开"
|
||||
inline-prompt
|
||||
@ -1201,52 +864,29 @@ onUnmounted(() => {
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
|
||||
<el-col :span="24">
|
||||
<div class="h2">Web 界面</div>
|
||||
<div class="h2">代理</div>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="启用Web界面:" prop="webEnable">
|
||||
<template #label>
|
||||
<div class="h-full flex items-center mr-1">
|
||||
<el-popover width="300" placement="top" trigger="hover">
|
||||
<template #reference>
|
||||
<IconifyIconOffline
|
||||
class="text-base"
|
||||
color="#5A3DAA"
|
||||
icon="info"
|
||||
/>
|
||||
</template>
|
||||
热更新等功能依赖于web界面,<span
|
||||
class="font-black text-[#5A3DAA]"
|
||||
>不可停用Web</span
|
||||
>
|
||||
</el-popover>
|
||||
</div>
|
||||
启用Web:
|
||||
</template>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="是否启动代理:" prop="proxyConfigEnable">
|
||||
<el-switch
|
||||
active-text="开"
|
||||
inline-prompt
|
||||
disabled
|
||||
inactive-text="关"
|
||||
v-model="formData.webEnable"
|
||||
v-model="formData.proxyConfigEnable"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<template v-if="formData.webEnable">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="Web 端口:" prop="webPort">
|
||||
<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]"
|
||||
>webServer.port</span
|
||||
><br />
|
||||
自行保证端口没有被占用,否则会导致启动失败
|
||||
>transport.proxyURL</span
|
||||
>
|
||||
</template>
|
||||
<template #reference>
|
||||
<IconifyIconOffline
|
||||
@ -1257,16 +897,12 @@ onUnmounted(() => {
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
Web 端口:
|
||||
代理地址:
|
||||
</template>
|
||||
<el-input-number
|
||||
placeholder="57400"
|
||||
v-model="formData.webPort"
|
||||
:min="0"
|
||||
:max="65535"
|
||||
controls-position="right"
|
||||
class="w-full"
|
||||
></el-input-number>
|
||||
<el-input
|
||||
v-model="formData.proxyConfigProxyUrl"
|
||||
placeholder="http://user:pwd@192.168.1.128:8080"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
@ -1279,7 +915,7 @@ onUnmounted(() => {
|
||||
<el-select v-model="formData.logLevel">
|
||||
<el-option label="info" value="info" />
|
||||
<el-option label="debug" value="debug" />
|
||||
<el-option label="warn" value="warn" />
|
||||
<el-option label="waring" value="waring" />
|
||||
<el-option label="error" value="error" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@ -1296,7 +932,7 @@ onUnmounted(() => {
|
||||
<el-col :span="24">
|
||||
<div class="h2">系统配置</div>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开机自启:" prop="systemSelfStart">
|
||||
<template #label>
|
||||
<div class="h-full flex items-center mr-1">
|
||||
@ -1326,36 +962,7 @@ onUnmounted(() => {
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="静默启动:" prop="systemSilentStartup">
|
||||
<template #label>
|
||||
<div class="h-full flex items-center mr-1">
|
||||
<el-popover placement="top" trigger="hover">
|
||||
<template #default>
|
||||
开启后启动时<span class="font-black text-[#5A3DAA]"
|
||||
>不打开界面</span
|
||||
>
|
||||
</template>
|
||||
<template #reference>
|
||||
<IconifyIconOffline
|
||||
class="text-base"
|
||||
color="#5A3DAA"
|
||||
icon="info"
|
||||
/>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
静默启动:
|
||||
</template>
|
||||
<el-switch
|
||||
active-text="开"
|
||||
inline-prompt
|
||||
inactive-text="关"
|
||||
v-model="formData.systemSilentStartup"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="自动连接:" prop="systemStartupConnect">
|
||||
<template #label>
|
||||
<div class="h-full flex items-center mr-1">
|
||||
|
@ -20,6 +20,10 @@ const mirrors = ref<Array<GitHubMirror>>([
|
||||
{
|
||||
id: "github",
|
||||
name: "github"
|
||||
},
|
||||
{
|
||||
id: "ghproxy",
|
||||
name: "ghproxy"
|
||||
}
|
||||
]);
|
||||
|
||||
@ -27,7 +31,7 @@ const mirrors = ref<Array<GitHubMirror>>([
|
||||
* 获取版本
|
||||
*/
|
||||
const handleLoadVersions = () => {
|
||||
ipcRenderer.send("github.getFrpVersions", currMirror.value);
|
||||
ipcRenderer.send("github.getFrpVersions");
|
||||
};
|
||||
|
||||
/**
|
||||
@ -102,23 +106,6 @@ const handleInitDownloadHook = () => {
|
||||
handleLoadVersions();
|
||||
}
|
||||
});
|
||||
ipcRenderer.on("Download.importFrpFile.hook", (event, args) => {
|
||||
const { success, data } = args;
|
||||
console.log(args);
|
||||
|
||||
// if (err) {
|
||||
loading.value++;
|
||||
ElMessage({
|
||||
type: success ? "success" : "error",
|
||||
message: data
|
||||
});
|
||||
handleLoadVersions();
|
||||
// }
|
||||
});
|
||||
};
|
||||
|
||||
const handleMirrorChange = () => {
|
||||
handleLoadVersions();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
@ -129,43 +116,27 @@ onMounted(() => {
|
||||
// });
|
||||
});
|
||||
|
||||
const handleImportFrp = () => {
|
||||
ipcRenderer.send("download.importFrpFile");
|
||||
};
|
||||
|
||||
onUnmounted(() => {
|
||||
ipcRenderer.removeAllListeners("Download.frpVersionDownloadOnProgress");
|
||||
ipcRenderer.removeAllListeners("Download.frpVersionDownloadOnCompleted");
|
||||
ipcRenderer.removeAllListeners("Download.frpVersionHook");
|
||||
ipcRenderer.removeAllListeners("Download.deleteVersion.hook");
|
||||
ipcRenderer.removeAllListeners("Download.importFrpFile.hook");
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="main">
|
||||
<!-- <breadcrumb> -->
|
||||
<breadcrumb>
|
||||
<div class="flex">
|
||||
<div class="h-full flex items-center justify-center mr-4">
|
||||
<span class="text-sm font-bold">下载源: </span>
|
||||
<el-select
|
||||
class="w-40"
|
||||
v-model="currMirror"
|
||||
@change="handleMirrorChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="m in mirrors"
|
||||
:label="m.name"
|
||||
:key="m.id"
|
||||
:value="m.id"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<el-button class="mr-2" type="primary" @click="handleImportFrp">
|
||||
<IconifyIconOffline icon="unarchive" />
|
||||
</el-button>
|
||||
<div class="h-full flex items-center justify-center">
|
||||
<span class="text-sm font-bold">下载源: </span>
|
||||
<el-select class="w-40" v-model="currMirror">
|
||||
<el-option
|
||||
v-for="m in mirrors"
|
||||
:label="m.name"
|
||||
:key="m.id"
|
||||
:value="m.id"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<!-- <div-->
|
||||
<!-- class="cursor-pointer h-[36px] w-[36px] bg-[#5f3bb0] rounded text-white flex justify-center items-center"-->
|
||||
<!-- @click="handleOpenInsert"-->
|
||||
|
@ -1,10 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { createVNode, defineComponent, onMounted, onUnmounted, ref } from "vue";
|
||||
import {defineComponent, onMounted, onUnmounted, ref} from "vue";
|
||||
import Breadcrumb from "@/layout/compoenets/Breadcrumb.vue";
|
||||
import { ipcRenderer } from "electron";
|
||||
import IconifyIconOffline from "@/components/IconifyIcon/src/iconifyIconOffline";
|
||||
import { useDebounce, useDebounceFn } from "@vueuse/core";
|
||||
import { ElMessage } from "element-plus";
|
||||
import {ipcRenderer} from "electron";
|
||||
|
||||
defineComponent({
|
||||
name: "Logger"
|
||||
@ -14,30 +11,25 @@ const loggerContent = ref('<div class="text-white">暂无日志</div>');
|
||||
|
||||
const handleLog2Html = (logContent: string) => {
|
||||
const logs = logContent
|
||||
.split("\n")
|
||||
.filter(f => f)
|
||||
.map(m => {
|
||||
if (m.indexOf("[E]") !== -1) {
|
||||
return `<div class="text-[#FF0006]">${m}</div> `;
|
||||
} else if (m.indexOf("[I]") !== -1) {
|
||||
return `<div class="text-[#48BB31]">${m}</div> `;
|
||||
} else if (m.indexOf("[D]") !== -1) {
|
||||
return `<div class="text-[#0070BB]">${m}</div> `;
|
||||
} else if (m.indexOf("[W]") !== -1) {
|
||||
return `<div class="text-[#BBBB23]">${m}</div> `;
|
||||
} else {
|
||||
return `<div class="text-[#BBBBBB]">${m}</div> `;
|
||||
}
|
||||
});
|
||||
.split("\n")
|
||||
.filter(f => f)
|
||||
.map(m => {
|
||||
if (m.indexOf("[E]") !== -1) {
|
||||
return `<div class="text-[#FF0006]">${m}</div> `;
|
||||
} else if (m.indexOf("[I]") !== -1) {
|
||||
return `<div class="text-[#48BB31]">${m}</div> `;
|
||||
} else if (m.indexOf("[D]") !== -1) {
|
||||
return `<div class="text-[#0070BB]">${m}</div> `;
|
||||
} else if (m.indexOf("[W]") !== -1) {
|
||||
return `<div class="text-[#BBBB23]">${m}</div> `;
|
||||
} else {
|
||||
return `<div class="text-[#BBBBBB]">${m}</div> `;
|
||||
}
|
||||
});
|
||||
return logs.reverse().join("");
|
||||
};
|
||||
|
||||
const refreshStatus = ref(false);
|
||||
|
||||
const logLoading = ref(true);
|
||||
|
||||
onMounted(() => {
|
||||
console.log('logger mounted')
|
||||
ipcRenderer.send("logger.getLog");
|
||||
ipcRenderer.on("Logger.getLog.hook", (event, args) => {
|
||||
// console.log("日志", args, args.indexOf("\n"));
|
||||
@ -46,69 +38,26 @@ onMounted(() => {
|
||||
if (args) {
|
||||
loggerContent.value = handleLog2Html(args);
|
||||
}
|
||||
logLoading.value = false;
|
||||
if (refreshStatus.value) {
|
||||
// 刷新逻辑
|
||||
ElMessage({
|
||||
type: "success",
|
||||
message: "刷新成功"
|
||||
});
|
||||
} else {
|
||||
ipcRenderer.send("logger.update");
|
||||
}
|
||||
ipcRenderer.send("logger.update");
|
||||
});
|
||||
ipcRenderer.on("Logger.update.hook", (event, args) => {
|
||||
console.log("logger update hook", 1);
|
||||
if (args) {
|
||||
loggerContent.value = handleLog2Html(args);
|
||||
}
|
||||
});
|
||||
|
||||
ipcRenderer.on("Logger.openLog.hook", (event, args) => {
|
||||
if (args) {
|
||||
ElMessage({
|
||||
type: "success",
|
||||
message: "打开日志成功"
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const openLocalLog = useDebounceFn(() => {
|
||||
ipcRenderer.send("logger.openLog");
|
||||
}, 1000);
|
||||
|
||||
const refreshLog = useDebounceFn(() => {
|
||||
// ElMessage({
|
||||
// type: "warning",
|
||||
// icon: "<IconifyIconOffline icon=\"file-open-rounded\" />",
|
||||
// message: "正在刷新日志..."
|
||||
// });
|
||||
refreshStatus.value = true;
|
||||
logLoading.value = true;
|
||||
ipcRenderer.send("logger.getLog");
|
||||
}, 300);
|
||||
|
||||
onUnmounted(() => {
|
||||
console.log('logger unmounted')
|
||||
ipcRenderer.removeAllListeners("Logger.getLog.hook");
|
||||
ipcRenderer.removeAllListeners("Logger.openLog.hook");
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="main">
|
||||
<breadcrumb>
|
||||
<el-button plain type="primary" @click="refreshLog">
|
||||
<IconifyIconOffline icon="refresh-rounded" />
|
||||
</el-button>
|
||||
<el-button plain type="primary" @click="openLocalLog">
|
||||
<IconifyIconOffline icon="file-open-rounded" />
|
||||
</el-button>
|
||||
</breadcrumb>
|
||||
<div class="app-container-breadcrumb" v-loading="logLoading">
|
||||
<breadcrumb/>
|
||||
<div class="app-container-breadcrumb">
|
||||
<div
|
||||
class="w-full h-full p-2 bg-[#2B2B2B] rounded drop-shadow-lg overflow-y-auto"
|
||||
v-html="loggerContent"
|
||||
class="w-full h-full p-2 bg-[#2B2B2B] rounded drop-shadow-lg overflow-y-auto"
|
||||
v-html="loggerContent"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,14 +1,5 @@
|
||||
[
|
||||
{
|
||||
"value": "127.0.0.1"
|
||||
},
|
||||
{
|
||||
"value": "192.168.1.1"
|
||||
},
|
||||
{
|
||||
"value": "192.168.0.1"
|
||||
},
|
||||
{
|
||||
"value": "192.168.5.1"
|
||||
"value": "127.0.0.11"
|
||||
}
|
||||
]
|
File diff suppressed because it is too large
Load Diff
49
src/views/welfare/index.vue
Normal file
49
src/views/welfare/index.vue
Normal file
@ -0,0 +1,49 @@
|
||||
<script lang="ts" setup>
|
||||
import { defineComponent, onMounted, onUnmounted, reactive, ref } from "vue";
|
||||
import Breadcrumb from "@/layout/compoenets/Breadcrumb.vue";
|
||||
import IconifyIconOffline from "@/components/IconifyIcon/src/iconifyIconOffline";
|
||||
|
||||
defineComponent({
|
||||
name: "Welfare"
|
||||
});
|
||||
|
||||
const loading = ref(1);
|
||||
|
||||
const welfareNodes = ref([]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="main">
|
||||
<breadcrumb />
|
||||
<div class="app-container-breadcrumb pr-2" v-loading="loading > 0">
|
||||
<div class="w-full">
|
||||
<template v-if="welfareNodes && welfareNodes.length > 0">
|
||||
<el-row :gutter="20">
|
||||
<el-col
|
||||
v-for="version in welfareNodes"
|
||||
:key="version.id"
|
||||
:lg="6"
|
||||
:md="8"
|
||||
:sm="12"
|
||||
:xl="6"
|
||||
:xs="12"
|
||||
class="mb-[20px]"
|
||||
>
|
||||
<div
|
||||
class="w-full download-card bg-white rounded p-4 drop-shadow flex justify-between items-center"
|
||||
>
|
||||
<div class="left">
|
||||
</div>
|
||||
|
||||
<div class="right">
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
25
types/global.d.ts
vendored
25
types/global.d.ts
vendored
@ -18,25 +18,15 @@ declare global {
|
||||
name: string;
|
||||
type: string;
|
||||
localIp: string;
|
||||
localPort: any;
|
||||
remotePort: string;
|
||||
localPort: number;
|
||||
remotePort: number;
|
||||
customDomains: string[];
|
||||
stcpModel: string;
|
||||
serverName: string;
|
||||
secretKey: string;
|
||||
bindAddr: string;
|
||||
bindPort: number;
|
||||
status: boolean;
|
||||
subdomain: string;
|
||||
basicAuth: boolean;
|
||||
httpUser: string;
|
||||
httpPassword: string;
|
||||
fallbackTo: string;
|
||||
fallbackTimeoutMs: number;
|
||||
https2http: boolean;
|
||||
https2httpCaFile: string;
|
||||
https2httpKeyFile: string;
|
||||
keepTunnelOpen: boolean;
|
||||
status: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
@ -82,19 +72,10 @@ declare global {
|
||||
proxyConfigProxyUrl: string;
|
||||
systemSelfStart: boolean;
|
||||
systemStartupConnect: boolean;
|
||||
systemSilentStartup: boolean;
|
||||
user: string;
|
||||
metaToken: string;
|
||||
transportHeartbeatInterval: number;
|
||||
transportHeartbeatTimeout: number;
|
||||
webEnable: boolean;
|
||||
webPort: number;
|
||||
transportProtocol: string;
|
||||
transportDialServerTimeout: number;
|
||||
transportDialServerKeepalive: number;
|
||||
transportPoolCount: number;
|
||||
transportTcpMux: boolean;
|
||||
transportTcpMuxKeepaliveInterval: number;
|
||||
};
|
||||
|
||||
type GitHubMirror = {
|
||||
|
Loading…
Reference in New Issue
Block a user