Merge branch 'refs/heads/main' into develop
# Conflicts: # electron/storage/config.ts # src/views/config/index.vue
This commit is contained in:
commit
c0519e7a5a
19
README.md
19
README.md
@ -16,15 +16,30 @@
|
|||||||
<h3 align="center">Frpc-Desktop</h3>
|
<h3 align="center">Frpc-Desktop</h3>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
🎉 一个Frp Client 跨平台桌面端
|
🎉 一个 Frp 跨平台桌面客户端 支持多个frp版本
|
||||||
<br />
|
<br />
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
- [x] 开机自启动
|
||||||
|
- [ ] 支持配置的导出导入
|
||||||
|
- [ ] 适配多用户 user & meta_token
|
||||||
|
- [ ] 优化配置
|
||||||
|
- [ ] 便携版
|
||||||
|
|
||||||
## 里程碑
|
## 里程碑
|
||||||
|
- 2024-07-17: 发布v1.0.3版本 修复已知bug 增加开机自启 增加删除frp版本
|
||||||
|
- 2024-01-29: 发布v1.0.2版本 增加Linux客户端和代理模式
|
||||||
- 2023-12-01: 发布v1.0.1版本
|
- 2023-12-01: 发布v1.0.1版本
|
||||||
- 2023-11-28: 发布v1.0版本
|
- 2023-11-28: 发布v1.0版本
|
||||||
|
|
||||||
|
## 社区
|
||||||
|
微信扫描加入开源项目交流群 广告勿进!!!
|
||||||
|
|
||||||
|
<img src="wechat-qr.png" alt="Logo" width="200">
|
||||||
|
|
||||||
|
|
||||||
## 演示
|
## 演示
|
||||||
|
|
||||||

|

|
||||||
@ -39,6 +54,8 @@
|
|||||||
|
|
||||||
[MIT](LICENSE)
|
[MIT](LICENSE)
|
||||||
|
|
||||||
|
## Stargazers over time
|
||||||
|
[](https://starchart.cc/luckjiawei/frpc-desktop)
|
||||||
<!-- MARKDOWN LINKS & IMAGES -->
|
<!-- MARKDOWN LINKS & IMAGES -->
|
||||||
[forks-shield]: https://img.shields.io/github/forks/luckjiawei/frpc-desktop.svg?style=for-the-badge
|
[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
|
[forks-url]: https://github.com/luckjiawei/frpc-desktop/network/members
|
||||||
|
@ -1,42 +1,50 @@
|
|||||||
import { ipcMain } from "electron";
|
import {app, ipcMain} from "electron";
|
||||||
import { getConfig, saveConfig } from "../storage/config";
|
import {getConfig, saveConfig} from "../storage/config";
|
||||||
import { listVersion } from "../storage/version";
|
import {listVersion} from "../storage/version";
|
||||||
|
|
||||||
export const initConfigApi = () => {
|
export const initConfigApi = () => {
|
||||||
ipcMain.on("config.saveConfig", async (event, args) => {
|
ipcMain.on("config.saveConfig", async (event, args) => {
|
||||||
saveConfig(args, (err, numberOfUpdated, upsert) => {
|
saveConfig(args, (err, numberOfUpdated, upsert) => {
|
||||||
event.reply("Config.saveConfig.hook", {
|
if (!err) {
|
||||||
err: err,
|
const start = args.systemSelfStart || false;
|
||||||
numberOfUpdated: numberOfUpdated,
|
console.log('开机自启', start)
|
||||||
upsert: upsert
|
app.setLoginItemSettings({
|
||||||
});
|
openAtLogin: start, //win
|
||||||
|
openAsHidden: start, //macOs
|
||||||
|
});
|
||||||
|
}
|
||||||
|
event.reply("Config.saveConfig.hook", {
|
||||||
|
err: err,
|
||||||
|
numberOfUpdated: numberOfUpdated,
|
||||||
|
upsert: upsert
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on("config.getConfig", async (event, args) => {
|
ipcMain.on("config.getConfig", async (event, args) => {
|
||||||
getConfig((err, doc) => {
|
getConfig((err, doc) => {
|
||||||
event.reply("Config.getConfig.hook", {
|
event.reply("Config.getConfig.hook", {
|
||||||
err: err,
|
err: err,
|
||||||
data: doc
|
data: doc
|
||||||
});
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on("config.versions", event => {
|
ipcMain.on("config.versions", event => {
|
||||||
listVersion((err, doc) => {
|
listVersion((err, doc) => {
|
||||||
event.reply("Config.versions.hook", {
|
event.reply("Config.versions.hook", {
|
||||||
err: err,
|
err: err,
|
||||||
data: doc
|
data: doc
|
||||||
});
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on("config.hasConfig", event => {
|
ipcMain.on("config.hasConfig", event => {
|
||||||
getConfig((err, doc) => {
|
getConfig((err, doc) => {
|
||||||
event.reply("Config.getConfig.hook", {
|
event.reply("Config.getConfig.hook", {
|
||||||
err: err,
|
err: err,
|
||||||
data: doc
|
data: doc
|
||||||
});
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {app, ipcMain, Notification} from "electron";
|
import {app, ipcMain, Notification} from "electron";
|
||||||
import {Config, getConfig} from "../storage/config";
|
import {Config, getConfig} from "../storage/config";
|
||||||
import {listProxy} from "../storage/proxy";
|
import {listProxy, Proxy} from "../storage/proxy";
|
||||||
import {getVersionById} from "../storage/version";
|
import {getVersionById} from "../storage/version";
|
||||||
import treeKill from "tree-kill";
|
import treeKill from "tree-kill";
|
||||||
|
|
||||||
@ -25,43 +25,42 @@ const getFrpcVersionWorkerPath = (
|
|||||||
) => {
|
) => {
|
||||||
getVersionById(versionId, (err2, version) => {
|
getVersionById(versionId, (err2, version) => {
|
||||||
if (!err2) {
|
if (!err2) {
|
||||||
callback(version["frpcVersionPath"]);
|
if (version) {
|
||||||
|
callback(version["frpcVersionPath"]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成配置文件
|
* 生成toml配置文件
|
||||||
|
* @param config
|
||||||
|
* @param proxys
|
||||||
*/
|
*/
|
||||||
export const generateConfig = (
|
const genTomlConfig = (config: Config, proxys: Proxy[]) => {
|
||||||
config: Config,
|
const proxyToml = proxys.map(m => {
|
||||||
callback: (configPath: string) => void
|
let toml = `
|
||||||
) => {
|
|
||||||
listProxy((err3, proxys) => {
|
|
||||||
if (!err3) {
|
|
||||||
const proxyToml = proxys.map(m => {
|
|
||||||
let toml = `
|
|
||||||
[[proxies]]
|
[[proxies]]
|
||||||
name = "${m.name}"
|
name = "${m.name}"
|
||||||
type = "${m.type}"
|
type = "${m.type}"
|
||||||
localIP = "${m.localIp}"
|
localIP = "${m.localIp}"
|
||||||
localPort = ${m.localPort}
|
localPort = ${m.localPort}
|
||||||
`;
|
`;
|
||||||
switch (m.type) {
|
switch (m.type) {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
toml += `remotePort = ${m.remotePort}`;
|
toml += `remotePort = ${m.remotePort}`;
|
||||||
break;
|
break;
|
||||||
case "http":
|
case "http":
|
||||||
case "https":
|
case "https":
|
||||||
toml += `customDomains=[${m.customDomains.map(m => `"${m}"`)}]`;
|
toml += `customDomains=[${m.customDomains.map(m => `"${m}"`)}]`;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return toml;
|
return toml;
|
||||||
});
|
});
|
||||||
let toml = `
|
const toml = `
|
||||||
serverAddr = "${config.serverAddr}"
|
serverAddr = "${config.serverAddr}"
|
||||||
serverPort = ${config.serverPort}
|
serverPort = ${config.serverPort}
|
||||||
auth.method = "${config.authMethod}"
|
auth.method = "${config.authMethod}"
|
||||||
@ -82,17 +81,92 @@ ${config.proxyConfigEnable ? `
|
|||||||
transport.proxyURL = "${config.proxyConfigProxyUrl}"
|
transport.proxyURL = "${config.proxyConfigProxyUrl}"
|
||||||
` : ""}
|
` : ""}
|
||||||
|
|
||||||
|
|
||||||
${proxyToml.join("")}
|
${proxyToml.join("")}
|
||||||
`;
|
`;
|
||||||
|
return toml;
|
||||||
|
}
|
||||||
|
|
||||||
// const configPath = path.join("frp.toml");
|
|
||||||
const filename = "frp.toml";
|
/**
|
||||||
|
* 生成ini配置
|
||||||
|
* @param config
|
||||||
|
* @param proxys
|
||||||
|
*/
|
||||||
|
const genIniConfig = (config: Config, proxys: Proxy[]) => {
|
||||||
|
const proxyIni = proxys.map(m => {
|
||||||
|
let ini = `
|
||||||
|
[${m.name}]
|
||||||
|
type = "${m.type}"
|
||||||
|
local_ip = "${m.localIp}"
|
||||||
|
local_port = ${m.localPort}
|
||||||
|
`;
|
||||||
|
switch (m.type) {
|
||||||
|
case "tcp":
|
||||||
|
ini += `remote_port = ${m.remotePort}`;
|
||||||
|
break;
|
||||||
|
case "http":
|
||||||
|
case "https":
|
||||||
|
ini += `custom_domains=[${m.customDomains.map(m => `"${m}"`)}]`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ini;
|
||||||
|
});
|
||||||
|
const ini = `
|
||||||
|
[common]
|
||||||
|
server_addr = ${config.serverAddr}
|
||||||
|
server_port = ${config.serverPort}
|
||||||
|
authentication_method = "${config.authMethod}"
|
||||||
|
auth_token = "${config.authToken}"
|
||||||
|
log_file = "frpc.log"
|
||||||
|
log_level = ${config.logLevel}
|
||||||
|
log_max_days = ${config.logMaxDays}
|
||||||
|
admin_addr = 127.0.0.1
|
||||||
|
admin_port = 57400
|
||||||
|
tls_enable = ${config.tlsConfigEnable}
|
||||||
|
${config.tlsConfigEnable ? `
|
||||||
|
tls_cert_file = ${config.tlsConfigCertFile}
|
||||||
|
tls_key_file = ${config.tlsConfigKeyFile}
|
||||||
|
tls_trusted_ca_file = ${config.tlsConfigTrustedCaFile}
|
||||||
|
tls_server_name = ${config.tlsConfigServerName}
|
||||||
|
` : ""}
|
||||||
|
${config.proxyConfigEnable ? `
|
||||||
|
http_proxy = "${config.proxyConfigProxyUrl}"
|
||||||
|
` : ""}
|
||||||
|
|
||||||
|
${proxyIni.join("")}
|
||||||
|
`
|
||||||
|
return ini;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成配置文件
|
||||||
|
*/
|
||||||
|
export const generateConfig = (
|
||||||
|
config: Config,
|
||||||
|
callback: (configPath: string) => void
|
||||||
|
) => {
|
||||||
|
listProxy((err3, proxys) => {
|
||||||
|
if (!err3) {
|
||||||
|
console.log(config, 'config')
|
||||||
|
const {currentVersion} = config;
|
||||||
|
let filename = null;
|
||||||
|
let configContent = "";
|
||||||
|
if (currentVersion < 124395282) {
|
||||||
|
// 版本小于v0.52.0
|
||||||
|
filename = "frp.ini";
|
||||||
|
configContent = genIniConfig(config, proxys)
|
||||||
|
} else {
|
||||||
|
filename = "frp.toml";
|
||||||
|
configContent = genTomlConfig(config, proxys)
|
||||||
|
}
|
||||||
const configPath = path.join(app.getPath("userData"), filename)
|
const configPath = path.join(app.getPath("userData"), filename)
|
||||||
console.debug("生成配置成功", configPath)
|
console.debug("生成配置成功", configPath)
|
||||||
fs.writeFile(
|
fs.writeFile(
|
||||||
configPath, // 配置文件目录
|
configPath, // 配置文件目录
|
||||||
toml, // 配置文件内容
|
configContent, // 配置文件内容
|
||||||
{flag: "w"},
|
{flag: "w"},
|
||||||
err => {
|
err => {
|
||||||
if (!err) {
|
if (!err) {
|
||||||
@ -111,6 +185,7 @@ ${proxyToml.join("")}
|
|||||||
* @param configPath
|
* @param configPath
|
||||||
*/
|
*/
|
||||||
const startFrpcProcess = (commandPath: string, configPath: string) => {
|
const startFrpcProcess = (commandPath: string, configPath: string) => {
|
||||||
|
console.log(commandPath, 'commandP')
|
||||||
const command = `${commandPath} -c ${configPath}`;
|
const command = `${commandPath} -c ${configPath}`;
|
||||||
console.info("启动", command)
|
console.info("启动", command)
|
||||||
frpcProcess = spawn(command, {
|
frpcProcess = spawn(command, {
|
||||||
@ -124,7 +199,8 @@ const startFrpcProcess = (commandPath: string, configPath: string) => {
|
|||||||
});
|
});
|
||||||
frpcProcess.stdout.on("error", data => {
|
frpcProcess.stdout.on("error", data => {
|
||||||
console.log("启动错误", data)
|
console.log("启动错误", data)
|
||||||
stopFrpcProcess()
|
stopFrpcProcess(() => {
|
||||||
|
})
|
||||||
});
|
});
|
||||||
frpcStatusListener = setInterval(() => {
|
frpcStatusListener = setInterval(() => {
|
||||||
const status = frpcProcessStatus()
|
const status = frpcProcessStatus()
|
||||||
@ -196,6 +272,35 @@ export const frpcProcessStatus = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动frpc流程
|
||||||
|
* @param config
|
||||||
|
*/
|
||||||
|
export const startFrpWorkerProcess = (config: Config) => {
|
||||||
|
getFrpcVersionWorkerPath(
|
||||||
|
config.currentVersion,
|
||||||
|
(frpcVersionPath: string) => {
|
||||||
|
console.log(1, '1')
|
||||||
|
if (frpcVersionPath) {
|
||||||
|
generateConfig(config, configPath => {
|
||||||
|
const platform = process.platform;
|
||||||
|
if (platform === 'win32') {
|
||||||
|
startFrpcProcess(
|
||||||
|
path.join(frpcVersionPath, "frpc.exe"),
|
||||||
|
configPath
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
startFrpcProcess(
|
||||||
|
path.join(frpcVersionPath, "frpc"),
|
||||||
|
configPath
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export const initFrpcApi = () => {
|
export const initFrpcApi = () => {
|
||||||
ipcMain.handle("frpc.running", async (event, args) => {
|
ipcMain.handle("frpc.running", async (event, args) => {
|
||||||
@ -206,38 +311,21 @@ export const initFrpcApi = () => {
|
|||||||
getConfig((err1, config) => {
|
getConfig((err1, config) => {
|
||||||
if (!err1) {
|
if (!err1) {
|
||||||
if (config) {
|
if (config) {
|
||||||
getFrpcVersionWorkerPath(
|
startFrpWorkerProcess(config)
|
||||||
config.currentVersion,
|
|
||||||
(frpcVersionPath: string) => {
|
|
||||||
generateConfig(config, configPath => {
|
|
||||||
const platform = process.platform;
|
|
||||||
if (platform === 'win32') {
|
|
||||||
startFrpcProcess(
|
|
||||||
path.join(frpcVersionPath, "frpc.exe"),
|
|
||||||
configPath
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
startFrpcProcess(
|
|
||||||
path.join(frpcVersionPath, "frpc"),
|
|
||||||
configPath
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
event.reply(
|
event.reply(
|
||||||
"Home.frpc.start.error.hook",
|
"Home.frpc.start.error.hook", "请先前往设置页面,修改配置后再启动"
|
||||||
"请先前往设置页面,修改配置后再启动"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on("frpc.stop", () => {
|
ipcMain.on("frpc.stop", () => {
|
||||||
if (frpcProcess && !frpcProcess.killed) {
|
if (frpcProcess && !frpcProcess.killed) {
|
||||||
stopFrpcProcess()
|
stopFrpcProcess(() => {
|
||||||
|
})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {app, BrowserWindow, ipcMain, net, shell} from "electron";
|
import {app, BrowserWindow, ipcMain, net, shell} from "electron";
|
||||||
import {insertVersion} from "../storage/version";
|
import {deleteVersionById, insertVersion} from "../storage/version";
|
||||||
|
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
@ -86,7 +86,7 @@ export const initGitHubApi = () => {
|
|||||||
ipcMain.on("github.getFrpVersions", async event => {
|
ipcMain.on("github.getFrpVersions", async event => {
|
||||||
const request = net.request({
|
const request = net.request({
|
||||||
method: "get",
|
method: "get",
|
||||||
url: "https://api.github.com/repos/fatedier/frp/releases"
|
url: "https://api.github.com/repos/fatedier/frp/releases?page=1&per_page=1000"
|
||||||
});
|
});
|
||||||
request.on("response", response => {
|
request.on("response", response => {
|
||||||
let responseData: Buffer = Buffer.alloc(0);
|
let responseData: Buffer = Buffer.alloc(0);
|
||||||
@ -104,6 +104,7 @@ export const initGitHubApi = () => {
|
|||||||
const asset = getAdaptiveAsset(m.id);
|
const asset = getAdaptiveAsset(m.id);
|
||||||
if (asset) {
|
if (asset) {
|
||||||
const absPath = `${downloadPath}/${asset.name}`;
|
const absPath = `${downloadPath}/${asset.name}`;
|
||||||
|
m.absPath = absPath;
|
||||||
m.download_completed = fs.existsSync(absPath);
|
m.download_completed = fs.existsSync(absPath);
|
||||||
}
|
}
|
||||||
return m;
|
return m;
|
||||||
@ -163,6 +164,23 @@ export const initGitHubApi = () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除下载
|
||||||
|
*/
|
||||||
|
ipcMain.on("github.deleteVersion", async (event, args) => {
|
||||||
|
const {absPath, id} = args;
|
||||||
|
console.log('删除下载', args)
|
||||||
|
if (fs.existsSync(absPath)) {
|
||||||
|
deleteVersionById(id, () => {
|
||||||
|
fs.unlinkSync(absPath)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
event.reply("Download.deleteVersion.hook", {
|
||||||
|
err: null,
|
||||||
|
data: "删除成功"
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 打开GitHub
|
* 打开GitHub
|
||||||
*/
|
*/
|
||||||
|
@ -4,9 +4,10 @@ import node_path, {join} from "node:path";
|
|||||||
import {initGitHubApi} from "../api/github";
|
import {initGitHubApi} from "../api/github";
|
||||||
import {initConfigApi} from "../api/config";
|
import {initConfigApi} from "../api/config";
|
||||||
import {initProxyApi} from "../api/proxy";
|
import {initProxyApi} from "../api/proxy";
|
||||||
import {initFrpcApi, stopFrpcProcess} from "../api/frpc";
|
import {initFrpcApi, startFrpWorkerProcess, stopFrpcProcess} from "../api/frpc";
|
||||||
import {initLoggerApi} from "../api/logger";
|
import {initLoggerApi} from "../api/logger";
|
||||||
import {initFileApi} from "../api/file";
|
import {initFileApi} from "../api/file";
|
||||||
|
import {getConfig} from "../storage/config";
|
||||||
// The built directory structure
|
// The built directory structure
|
||||||
//
|
//
|
||||||
// ├─┬ dist-electron
|
// ├─┬ dist-electron
|
||||||
@ -135,6 +136,16 @@ export const createTray = () => {
|
|||||||
tray.on('double-click', () => {
|
tray.on('double-click', () => {
|
||||||
win.show();
|
win.show();
|
||||||
})
|
})
|
||||||
|
|
||||||
|
getConfig((err, config) => {
|
||||||
|
if (!err) {
|
||||||
|
if (config) {
|
||||||
|
if (config.systemStartupConnect) {
|
||||||
|
startFrpWorkerProcess(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
app.whenReady().then(() => {
|
app.whenReady().then(() => {
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import Datastore from "nedb";
|
import Datastore from "nedb";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { Proxy } from "./proxy";
|
import {Proxy} from "./proxy";
|
||||||
import { app } from "electron";
|
import {app} from "electron";
|
||||||
|
|
||||||
const versionDB = new Datastore({
|
const versionDB = new Datastore({
|
||||||
autoload: true,
|
autoload: true,
|
||||||
filename: path.join(app.getPath("userData"), "version.db")
|
filename: path.join(app.getPath("userData"), "version.db")
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -14,10 +14,10 @@ const versionDB = new Datastore({
|
|||||||
* @param cb
|
* @param cb
|
||||||
*/
|
*/
|
||||||
export const insertVersion = (
|
export const insertVersion = (
|
||||||
version: any,
|
version: any,
|
||||||
cb?: (err: Error | null, document: any) => void
|
cb?: (err: Error | null, document: any) => void
|
||||||
) => {
|
) => {
|
||||||
versionDB.insert(version, cb);
|
versionDB.insert(version, cb);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -25,14 +25,18 @@ export const insertVersion = (
|
|||||||
* @param cb
|
* @param cb
|
||||||
*/
|
*/
|
||||||
export const listVersion = (
|
export const listVersion = (
|
||||||
callback: (err: Error | null, documents: any[]) => void
|
callback: (err: Error | null, documents: any[]) => void
|
||||||
) => {
|
) => {
|
||||||
versionDB.find({}, callback);
|
versionDB.find({}, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getVersionById = (
|
export const getVersionById = (
|
||||||
id: string,
|
id: string,
|
||||||
callback: (err: Error | null, document: any) => void
|
callback: (err: Error | null, document: any) => void
|
||||||
) => {
|
) => {
|
||||||
versionDB.findOne({ id: id }, callback);
|
versionDB.findOne({id: id}, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const deleteVersionById = (id: string, callback: (err: Error | null, document: any) => void) => {
|
||||||
|
versionDB.remove({id: id}, callback);
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Frpc-Desktop",
|
"name": "Frpc-Desktop",
|
||||||
"version": "1.0.2",
|
"version": "1.0.3",
|
||||||
"main": "dist-electron/main/index.js",
|
"main": "dist-electron/main/index.js",
|
||||||
"description": "一个frpc桌面客户端",
|
"description": "一个frpc桌面客户端",
|
||||||
"author": "刘嘉伟 <8473136@qq.com>",
|
"author": "刘嘉伟 <8473136@qq.com>",
|
||||||
@ -54,7 +54,7 @@
|
|||||||
"vite-plugin-electron-renderer": "^0.14.5",
|
"vite-plugin-electron-renderer": "^0.14.5",
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
"vue-router": "^4.2.4",
|
"vue-router": "^4.2.4",
|
||||||
"vue-tsc": "^1.8.8",
|
"vue-tsc": "^2.0.22",
|
||||||
"vue-types": "^5.1.1"
|
"vue-types": "^5.1.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -19,7 +19,7 @@ const currentRoute = computed(() => {
|
|||||||
* 菜单切换
|
* 菜单切换
|
||||||
* @param mi 菜单索引
|
* @param mi 菜单索引
|
||||||
*/
|
*/
|
||||||
const handleMenuChange = (route: RouteRecordRaw) => {
|
const handleMenuChange = (route: any) => {
|
||||||
if (currentRoute.value.name === route.name) {
|
if (currentRoute.value.name === route.name) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
$main-bg: #F3F3F3;
|
$main-bg: #F3F3F3;
|
||||||
$primary-color: #5F3BB0;
|
$primary-color: #5F3BB0;
|
||||||
|
$danger-color: #F56C6C;
|
||||||
|
|
||||||
.main-container {
|
.main-container {
|
||||||
background: $main-bg;
|
background: $main-bg;
|
||||||
@ -84,3 +85,7 @@ $primary-color: #5F3BB0;
|
|||||||
.primary-text {
|
.primary-text {
|
||||||
color: $primary-color;
|
color: $primary-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.danger-text {
|
||||||
|
color: $danger-color !important;
|
||||||
|
}
|
@ -229,23 +229,6 @@ onUnmounted(() => {
|
|||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24">
|
<el-col :span="24">
|
||||||
<el-form-item label="服务器地址:" prop="serverAddr">
|
<el-form-item label="服务器地址:" prop="serverAddr">
|
||||||
<template #label>
|
|
||||||
<div class="h-full flex items-center mr-1">
|
|
||||||
<el-popover
|
|
||||||
placement="top"
|
|
||||||
trigger="hover"
|
|
||||||
>
|
|
||||||
<template #default>
|
|
||||||
Frps服务端地址 <br/> 支持 <span class="font-black text-[#5A3DAA]">域名</span>、<span
|
|
||||||
class="font-black text-[#5A3DAA]">IP</span>
|
|
||||||
</template>
|
|
||||||
<template #reference>
|
|
||||||
<Icon class="text-base" color="#5A3DAA" icon="material-symbols:info"/>
|
|
||||||
</template>
|
|
||||||
</el-popover>
|
|
||||||
</div>
|
|
||||||
服务器地址:
|
|
||||||
</template>
|
|
||||||
<el-input
|
<el-input
|
||||||
v-model="formData.serverAddr"
|
v-model="formData.serverAddr"
|
||||||
placeholder="127.0.0.1"
|
placeholder="127.0.0.1"
|
||||||
@ -266,35 +249,17 @@ onUnmounted(() => {
|
|||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="验证方式:" prop="authMethod">
|
<el-form-item label="验证方式:" prop="authMethod">
|
||||||
<template #label>
|
|
||||||
<div class="h-full flex items-center mr-1">
|
|
||||||
<el-popover
|
|
||||||
width="200"
|
|
||||||
placement="top"
|
|
||||||
trigger="hover"
|
|
||||||
>
|
|
||||||
<template #default>
|
|
||||||
对应参数:<span class="font-black text-[#5A3DAA]">auth.method</span>
|
|
||||||
</template>
|
|
||||||
<template #reference>
|
|
||||||
<Icon class="text-base" color="#5A3DAA" icon="material-symbols:info"/>
|
|
||||||
</template>
|
|
||||||
</el-popover>
|
|
||||||
</div>
|
|
||||||
验证方式:
|
|
||||||
</template>
|
|
||||||
<el-select
|
<el-select
|
||||||
v-model="formData.authMethod"
|
v-model="formData.authMethod"
|
||||||
placeholder="请选择验证方式"
|
placeholder="请选择验证方式"
|
||||||
clearable
|
clearable
|
||||||
>
|
>
|
||||||
<el-option label="令牌(token)" value="token"></el-option>
|
<el-option label="token" value="token"></el-option>
|
||||||
<el-option label="多用户" value="multiuser"></el-option>
|
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" v-if="formData.authMethod === 'token'">
|
<el-col :span="24" v-if="formData.authMethod === 'token'">
|
||||||
<el-form-item label="令牌(token):" prop="authToken">
|
<el-form-item label="token:" prop="authToken">
|
||||||
<el-input
|
<el-input
|
||||||
placeholder="token"
|
placeholder="token"
|
||||||
type="password"
|
type="password"
|
||||||
@ -302,56 +267,6 @@ onUnmounted(() => {
|
|||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12" v-if="formData.authMethod === 'multiuser'">
|
|
||||||
<el-form-item label="用户:" prop="user">
|
|
||||||
<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]">user</span>
|
|
||||||
</template>
|
|
||||||
<template #reference>
|
|
||||||
<Icon class="text-base" color="#5A3DAA" icon="material-symbols:info"/>
|
|
||||||
</template>
|
|
||||||
</el-popover>
|
|
||||||
</div>
|
|
||||||
用户:
|
|
||||||
</template>
|
|
||||||
<el-input
|
|
||||||
placeholder="请输入用户"
|
|
||||||
v-model="formData.user"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="12" v-if="formData.authMethod === 'multiuser'">
|
|
||||||
<el-form-item label="用户令牌:" prop="metaToken">
|
|
||||||
<template #label>
|
|
||||||
<div class="h-full flex items-center mr-1">
|
|
||||||
<el-popover
|
|
||||||
width="200"
|
|
||||||
placement="top"
|
|
||||||
trigger="hover"
|
|
||||||
>
|
|
||||||
<template #default>
|
|
||||||
对应参数:<span class="font-black text-[#5A3DAA]">meta_token</span>
|
|
||||||
</template>
|
|
||||||
<template #reference>
|
|
||||||
<Icon class="text-base" color="#5A3DAA" icon="material-symbols:info"/>
|
|
||||||
</template>
|
|
||||||
</el-popover>
|
|
||||||
</div>
|
|
||||||
用户令牌:
|
|
||||||
</template>
|
|
||||||
<el-input
|
|
||||||
placeholder="请输入用户令牌"
|
|
||||||
type="password"
|
|
||||||
v-model="formData.metaToken"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="24">
|
<el-col :span="24">
|
||||||
<div class="h2">TSL Config</div>
|
<div class="h2">TSL Config</div>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
@ -4,6 +4,7 @@ import {ipcRenderer} from "electron";
|
|||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import Breadcrumb from "@/layout/compoenets/Breadcrumb.vue";
|
import Breadcrumb from "@/layout/compoenets/Breadcrumb.vue";
|
||||||
import {Icon} from "@iconify/vue";
|
import {Icon} from "@iconify/vue";
|
||||||
|
import {ElMessage} from "element-plus";
|
||||||
|
|
||||||
defineComponent({
|
defineComponent({
|
||||||
name: "Download"
|
name: "Download"
|
||||||
@ -18,6 +19,7 @@ type Version = {
|
|||||||
name: string;
|
name: string;
|
||||||
published_at: string;
|
published_at: string;
|
||||||
download_completed: boolean;
|
download_completed: boolean;
|
||||||
|
absPath: string;
|
||||||
assets: Asset[]
|
assets: Asset[]
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -42,6 +44,17 @@ const handleDownload = (version: Version) => {
|
|||||||
downloading.value.set(version.id, 0);
|
downloading.value.set(version.id, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除下载
|
||||||
|
* @param version
|
||||||
|
*/
|
||||||
|
const handleDeleteVersion = (version: Version) => {
|
||||||
|
ipcRenderer.send("github.deleteVersion", {
|
||||||
|
id: version.id,
|
||||||
|
absPath: version.absPath
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const handleInitDownloadHook = () => {
|
const handleInitDownloadHook = () => {
|
||||||
ipcRenderer.on("Download.frpVersionHook", (event, args) => {
|
ipcRenderer.on("Download.frpVersionHook", (event, args) => {
|
||||||
loading.value--;
|
loading.value--;
|
||||||
@ -49,6 +62,7 @@ const handleInitDownloadHook = () => {
|
|||||||
m.published_at = moment(m.published_at).format("YYYY-MM-DD HH:mm:ss")
|
m.published_at = moment(m.published_at).format("YYYY-MM-DD HH:mm:ss")
|
||||||
return m as Version;
|
return m as Version;
|
||||||
}) as Array<Version>;
|
}) as Array<Version>;
|
||||||
|
console.log(versions, 'versions')
|
||||||
});
|
});
|
||||||
// 进度监听
|
// 进度监听
|
||||||
ipcRenderer.on("Download.frpVersionDownloadOnProgress", (event, args) => {
|
ipcRenderer.on("Download.frpVersionDownloadOnProgress", (event, args) => {
|
||||||
@ -67,6 +81,18 @@ const handleInitDownloadHook = () => {
|
|||||||
version.download_completed = true;
|
version.download_completed = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
ipcRenderer.on("Download.deleteVersion.hook", (event, args) => {
|
||||||
|
const {err, data} = args
|
||||||
|
if (!err) {
|
||||||
|
loading.value++;
|
||||||
|
ElMessage({
|
||||||
|
type: "success",
|
||||||
|
message: "删除成功"
|
||||||
|
});
|
||||||
|
handleLoadVersions();
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@ -81,6 +107,7 @@ onUnmounted(() => {
|
|||||||
ipcRenderer.removeAllListeners("Download.frpVersionDownloadOnProgress");
|
ipcRenderer.removeAllListeners("Download.frpVersionDownloadOnProgress");
|
||||||
ipcRenderer.removeAllListeners("Download.frpVersionDownloadOnCompleted");
|
ipcRenderer.removeAllListeners("Download.frpVersionDownloadOnCompleted");
|
||||||
ipcRenderer.removeAllListeners("Download.frpVersionHook");
|
ipcRenderer.removeAllListeners("Download.frpVersionHook");
|
||||||
|
ipcRenderer.removeAllListeners("Download.deleteVersion.hook");
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
@ -96,7 +123,7 @@ onUnmounted(() => {
|
|||||||
<div class="left">
|
<div class="left">
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<el-tag>{{ version.name }}</el-tag>
|
<el-tag>{{ version.name }}</el-tag>
|
||||||
<!-- <el-tag class="ml-2">原文件名:{{ version.assets[0]?.name }}</el-tag>-->
|
<!-- <el-tag class="ml-2">原文件名:{{ version.assets[0]?.name }}</el-tag>-->
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
发布时间:<span class="text-gray-00">{{
|
发布时间:<span class="text-gray-00">{{
|
||||||
@ -106,11 +133,25 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<span
|
<div v-if="version.download_completed">
|
||||||
class="primary-text text-sm font-bold ml-2"
|
<el-button type="text">已下载</el-button>
|
||||||
v-if="version.download_completed"
|
<!-- <span-->
|
||||||
>已下载</span
|
<!-- class="primary-text text-sm font-bold ml-2"-->
|
||||||
>
|
<!-- >已下载</span>-->
|
||||||
|
|
||||||
|
<el-button type="text" class="danger-text" @click="handleDeleteVersion(version)">
|
||||||
|
<Icon class="mr-1" icon="material-symbols:delete"/>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
<!-- <div>-->
|
||||||
|
<!-- <Icon class="mr-1" icon="material-symbols:download-2"/>-->
|
||||||
|
<!-- <span-->
|
||||||
|
<!-- class="danger-text text-sm font-bold ml-2"-->
|
||||||
|
<!-- >删除下载</span>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="w-32" v-if="downloading.has(version.id)">
|
<div class="w-32" v-if="downloading.has(version.id)">
|
||||||
<el-progress
|
<el-progress
|
||||||
|
BIN
wechat-qr.png
Normal file
BIN
wechat-qr.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 299 KiB |
Loading…
Reference in New Issue
Block a user