Merge branch 'develop' into feat/xsl20240807-add-aboutPage

This commit is contained in:
刘嘉伟 2024-08-11 15:14:35 +08:00 committed by GitHub
commit 20bf4a78ca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 574 additions and 143 deletions

View File

@ -16,7 +16,7 @@
<h3 align="center">Frpc-Desktop</h3> <h3 align="center">Frpc-Desktop</h3>
<p align="center"> <p align="center">
🎉 一个 Frp 跨平台桌面客户端 支持多个frp版本 🎉 Frp 跨平台桌面客户端 支持所有frp版本
<br /> <br />
</p> </p>
</div> </div>
@ -24,12 +24,20 @@
## TODO ## TODO
- [x] 开机自启动 - [x] 开机自启动
- [x] 适配多用户 user & meta_token - [x] 适配多用户 user & meta_token
- [ ] 支持配置的导出导入 - [x] 便携版
- [ ] 优化配置
- [ ] 便携版
- [ ] 在线更新 - [ ] 在线更新
- [ ] 支持stcp代理类型
- [ ] 支持快速分享frps
- [ ] 支持配的导出导入
- [ ] 优化配置
## 常见问题
### Mac提示已损坏
执行命令:`sudo xattr -cr Frpc-Desktop.app`
## 里程碑 ## 里程碑
- 2024-08-09: 发布v1.0.6版本
- 2024-08-06: 发布v1.0.5版本 - 2024-08-06: 发布v1.0.5版本
- 2024-08-06: 发布v1.0.4版本 适配支持多用户插件 - 2024-08-06: 发布v1.0.4版本 适配支持多用户插件
- 2024-07-17: 发布v1.0.3版本 修复已知bug 增加开机自启 增加删除frp版本 - 2024-07-17: 发布v1.0.3版本 修复已知bug 增加开机自启 增加删除frp版本
@ -40,18 +48,18 @@
## 社区 ## 社区
微信扫描加入开源项目交流群 广告勿进!!! 微信扫描加入开源项目交流群 广告勿进!!!
<img src="wechat-qr.png" alt="Logo" width="200"> <img src="screenshots/wechat-qr.png" alt="Logo" width="200">
## 演示 ## 演示
![connect server](https://github.com/luckjiawei/frpc-desktop/blob/main/demo/conn.png?raw=true) ![connect server](https://github.com/luckjiawei/frpc-desktop/blob/main/screenshots/conn.png?raw=true)
![proxys manager](https://github.com/luckjiawei/frpc-desktop/blob/main/demo/proxys.png?raw=true) ![proxys manager](https://github.com/luckjiawei/frpc-desktop/blob/main/screenshots/proxys.png?raw=true)
![frp download](https://github.com/luckjiawei/frpc-desktop/blob/main/demo/versions.png?raw=true) ![frp download](https://github.com/luckjiawei/frpc-desktop/blob/main/screenshots/versions.png?raw=true)
![log](https://github.com/luckjiawei/frpc-desktop/blob/main/demo/log.png?raw=true) ![log](https://github.com/luckjiawei/frpc-desktop/blob/main/screenshots/log.png?raw=true)
## License ## License

View File

@ -14,33 +14,131 @@
"dist", "dist",
"dist-electron" "dist-electron"
], ],
"dmg": {
"window": {
"width": 540,
"height": 380
},
"contents": [
{
"x": 410,
"y": 230,
"type": "link",
"path": "/Applications"
},
{
"x": 130,
"y": 230,
"type": "file"
}
]
},
"mac": { "mac": {
"target": [ "target": [
"dmg" {
target: "dmg",
"arch": [
"x64",
"arm64",
"universal"
]
},
{
"target": "zip",
"arch": [
"x64",
"arm64",
"universal"
], ],
"artifactName": "${productName}-Mac-${arch}-${version}-Installer.${ext}" }
],
// "artifactName": "${productName}-mac-${arch}-${version}.${ext}",
category: "public.app-category.utilities"
}, },
"win": { "win": {
"target": [ "target": [
{ {
"target": "nsis", "target": "nsis",
"arch": [ "arch": [
"x64" "x64",
"ia32"
]
},
// {
// "target": "appx",
// "arch": [
// "x64",
// "ia32"
// ]
// },
{
"target": "zip",
"arch": [
"x64",
"ia32"
]
},
{
"target": "portable",
"arch": [
"x64",
"ia32"
] ]
} }
], ],
"artifactName": "${productName}-Windows-${arch}-${version}-Setup.${ext}" // "artifactName": "${productName}-win-${arch}-${version}.${ext}"
}, },
"nsis": { "nsis": {
// "artifactName": "${productName}-win-${arch}-${version}-Setup.${ext}",
"oneClick": false, "oneClick": false,
"perMachine": false, "perMachine": false,
"allowToChangeInstallationDirectory": true, "allowToChangeInstallationDirectory": true,
"deleteAppDataOnUninstall": false "deleteAppDataOnUninstall": false
}, },
"portable": {
// "artifactName": "${productName}-win-${arch}-${version}.${ext}"
},
"linux": { "linux": {
"target": [ "target": [
"AppImage" {
], "target": "AppImage",
"artifactName": "${productName}-Linux-${arch}-${version}.${ext}" "arch": [
"x64",
"arm64",
// "armv7l"
]
}, },
{
"target": "deb",
"arch": [
"x64",
"arm64",
// "armv7l"
]
},
{
"target": "rpm",
"arch": [
"x64"
]
},
// {
// "target": "snap",
// "arch": [
// "x64"
// ]
// }
],
"category": "Network",
// "artifactName": "${productName}-Linux-${arch}-${version}.${ext}"
},
"publish": [
{
"provider": "github",
"owner": "luckjiawei",
"repo": "frpc-desktop",
"releaseType": "draft",
},
]
} }

View File

@ -49,6 +49,7 @@ localPort = ${m.localPort}
`; `;
switch (m.type) { switch (m.type) {
case "tcp": case "tcp":
case "udp":
toml += `remotePort = ${m.remotePort}`; toml += `remotePort = ${m.remotePort}`;
break; break;
case "http": case "http":
@ -72,6 +73,12 @@ ${config.authMethod === 'multiuser' ? `
user = "${config.user}" user = "${config.user}"
metadatas.token = "${config.metaToken}" metadatas.token = "${config.metaToken}"
` : ""} ` : ""}
${config.transportHeartbeatInterval ? `
transport.heartbeatInterval = ${config.transportHeartbeatInterval}
` : ""}
${config.transportHeartbeatTimeout ? `
transport.heartbeatTimeout = ${config.transportHeartbeatTimeout}
` : ""}
log.to = "frpc.log" log.to = "frpc.log"
@ -111,6 +118,7 @@ local_port = ${m.localPort}
`; `;
switch (m.type) { switch (m.type) {
case "tcp": case "tcp":
case "udp":
ini += `remote_port = ${m.remotePort}`; ini += `remote_port = ${m.remotePort}`;
break; break;
case "http": case "http":
@ -135,6 +143,14 @@ ${config.authMethod === 'multiuser' ? `
user = ${config.user} user = ${config.user}
meta_token = ${config.metaToken} meta_token = ${config.metaToken}
` : ""} ` : ""}
${config.transportHeartbeatInterval ? `
heartbeat_interval = ${config.transportHeartbeatInterval}
` : ""}
${config.transportHeartbeatTimeout ? `
heartbeat_timeout = ${config.transportHeartbeatTimeout}
` : ""}
log_file = "frpc.log" log_file = "frpc.log"
log_level = ${config.logLevel} log_level = ${config.logLevel}
log_max_days = ${config.logMaxDays} log_max_days = ${config.logMaxDays}

View File

@ -5,7 +5,6 @@ const fs = require("fs");
const path = require("path"); const path = require("path");
const zlib = require("zlib"); const zlib = require("zlib");
const {download} = require("electron-dl"); const {download} = require("electron-dl");
const unzipper = require('unzipper');
const AdmZip = require('adm-zip'); const AdmZip = require('adm-zip');
const log = require('electron-log'); const log = require('electron-log');
@ -15,13 +14,13 @@ const versionRelation = {
"win32_ia32": ["window", "386"], "win32_ia32": ["window", "386"],
"darwin_arm64": ["darwin", "arm64"], "darwin_arm64": ["darwin", "arm64"],
"darwin_x64": ["darwin", "amd64"], "darwin_x64": ["darwin", "amd64"],
// "darwin_arm64": ["window", "amd64"],
"darwin_amd64": ["darwin", "amd64"], "darwin_amd64": ["darwin", "amd64"],
"linux_x64": ["linux", "amd64"],
"linux_arm64": ["linux", "arm64"],
} }
const platform = process.platform; const platform = process.platform;
const arch = process.arch; const arch = process.arch;
let currArch = `${platform}_${arch}` let currArch = `${platform}_${arch}`
// currArch = `darwin_x64`
const frpArch = versionRelation[currArch] const frpArch = versionRelation[currArch]
const unTarGZ = (tarGzPath: string, targetPath: string) => { const unTarGZ = (tarGzPath: string, targetPath: string) => {

84
electron/api/update.ts Normal file
View File

@ -0,0 +1,84 @@
import {app, dialog, autoUpdater, BrowserWindow} from "electron";
const log = require('electron-log');
export const initUpdaterApi = (win: BrowserWindow) => {
//更新测试打开
Object.defineProperty(app, 'isPackaged', {
get() {
return true;
}
});
const server = 'https://hazel-git-master-uiluck.vercel.app'
let packageName = null
const platform = process.platform;
const arch = process.arch;
switch (platform) {
case "darwin":
if (arch == "arm64") {
packageName = "darwin_arm64";
} else {
packageName = "darwin";
}
break;
case "win32":
packageName = "exe";
break;
case "linux":
packageName = "AppImage";
if (arch == "arm64") {
packageName = "AppImage_arm64";
} else {
packageName = "AppImage";
}
break;
}
const url = `${server}/update/${packageName}/${app.getVersion()}`
log.info(`开启自动更新 ${url}`);
autoUpdater.setFeedURL({url: url})
autoUpdater.on('checking-for-update', () => {
log.info("正在检查更新")
})
autoUpdater.on('update-available', (event, info) => {
log.info(`发现新版本`)
})
autoUpdater.on('update-not-available', () => {
log.info('没有可用的更新')
})
autoUpdater.on('error', (err) => {
log.error(`更新错误:${err.message}`)
})
autoUpdater.on('update-downloaded', () => {
console.log('update-downloaded')
dialog.showMessageBox({
type: 'info',
title: '应用更新',
message: '发现新版本,是否更新?',
buttons: ['是', '否']
}).then((buttonIndex) => {
if (buttonIndex.response == 0) { //选择是,则退出程序,安装新版本
autoUpdater.quitAndInstall()
app.quit()
}
})
})
// setInterval(() => {
// log.initialize("定时检查更新")
// // autoUpdater.checkForUpdates();
// }, 60000)
autoUpdater.checkForUpdates();
log.info("手动检查更新一次")
}

View File

@ -7,6 +7,7 @@ import {initProxyApi} from "../api/proxy";
import {initFrpcApi, startFrpWorkerProcess, 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 {initUpdaterApi} from "../api/update";
import {getConfig} from "../storage/config"; import {getConfig} from "../storage/config";
import log from "electron-log"; import log from "electron-log";
// The built directory structure // The built directory structure
@ -117,7 +118,7 @@ async function createWindow() {
} }
export const createTray = () => { export const createTray = () => {
log.info(`当前环境 platform${process.platform} arch${process.arch} appData${app.getPath("userData")}`) log.info(`当前环境 platform${process.platform} arch${process.arch} appData${app.getPath("userData")} version:${app.getVersion()}`)
let menu: Array<(MenuItemConstructorOptions) | (MenuItem)> = [ let menu: Array<(MenuItemConstructorOptions) | (MenuItem)> = [
{ {
label: '显示主窗口', click: function () { label: '显示主窗口', click: function () {
@ -162,6 +163,14 @@ export const createTray = () => {
app.whenReady().then(() => { app.whenReady().then(() => {
createWindow().then(r => { createWindow().then(r => {
createTray() createTray()
// 初始化各个API
initGitHubApi();
initConfigApi();
initProxyApi();
initFrpcApi();
initLoggerApi();
initFileApi();
// initUpdaterApi(win);
}) })
}); });
@ -212,15 +221,3 @@ ipcMain.handle("open-win", (_, arg) => {
childWindow.loadFile(indexHtml, {hash: arg}); childWindow.loadFile(indexHtml, {hash: arg});
} }
}); });
ipcMain.on('open-url', (event, url) => {
shell.openExternal(url).then(r => {
});
});
initGitHubApi();
initConfigApi();
initProxyApi();
initFrpcApi();
initLoggerApi();
initFileApi();

View File

@ -44,7 +44,7 @@ function useLoading() {
animation-fill-mode: both; animation-fill-mode: both;
width: 50px; width: 50px;
height: 50px; height: 50px;
background: #fff; background: #5F3BB0;
animation: square-spin 3s 0s cubic-bezier(0.09, 0.57, 0.49, 0.9) infinite; animation: square-spin 3s 0s cubic-bezier(0.09, 0.57, 0.49, 0.9) infinite;
} }
.app-loading-wrap { .app-loading-wrap {
@ -56,7 +56,7 @@ function useLoading() {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: #282c34; background: #ffffff;
z-index: 9; z-index: 9;
} }
` `

View File

@ -28,6 +28,8 @@ export type Config = {
systemStartupConnect: boolean; systemStartupConnect: boolean;
user: string; user: string;
metaToken: string; metaToken: string;
transportHeartbeatInterval: number;
transportHeartbeatTimeout: number;
}; };
/** /**

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

View File

@ -1,19 +1,21 @@
{ {
"name": "Frpc-Desktop", "name": "Frpc-Desktop",
"version": "1.0.5", "version": "1.0.6",
"main": "dist-electron/main/index.js", "main": "dist-electron/main/index.js",
"description": "一个frpc桌面客户端", "description": "一个frpc桌面客户端",
"repository": "https://github.com/luckjiawei/frpc-desktop", "repository": "github:luckjiawei/frpc-desktop",
"author": "刘嘉伟 <8473136@qq.com>", "author": "刘嘉伟 <8473136@qq.com>",
"license": "MIT", "license": "MIT",
"private": true, "private": true,
"keywords": [ "keywords": [
"electron", "frp",
"rollup", "frpc",
"vite", "proxy",
"vue3", "electron-app"
"vue"
], ],
"engines": {
"node": ">= 18"
},
"debug": { "debug": {
"env": { "env": {
"VITE_DEV_SERVER_URL": "http://127.0.0.1:3344/" "VITE_DEV_SERVER_URL": "http://127.0.0.1:3344/"
@ -22,11 +24,14 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vue-tsc --noEmit && vite build", "build": "vue-tsc --noEmit && vite build",
"build:electron": "npm run build && electron-builder", "build:electron": "npm run build && electron-builder --mac --win --linux",
"build:electron:mac": "npm run build && electron-builder --mac --x64 --arm64", "build:electron:mac": "npm run build && electron-builder --mac",
"build:electron:win": "npm run build && electron-builder --win --arm64 --x64 --ia32", "build:electron:win": "npm run build && electron-builder --win",
"build:electron:linux": "npm run build && electron-builder --linux --arm64 --x64", "build:electron:linux": "npm run build && electron-builder --linux",
"build:electron:all": "npm run build:electron:win && npm run build:electron:mac && npm run build:electron:linux", "release": "npm run build && electron-builder --mac --win --linux -p always",
"release:mac": "npm run build && electron-builder --mac -p always",
"release:win": "npm run build && electron-builder --win -p always",
"release:linux": "npm run build && electron-builder --linux -p always",
"preview": "vite preview", "preview": "vite preview",
"electron:generate-icons": "electron-icon-builder --input=./public/logo.png --output=build --flatten" "electron:generate-icons": "electron-icon-builder --input=./public/logo.png --output=build --flatten"
}, },
@ -38,16 +43,18 @@
"@vueuse/core": "^9.13.0", "@vueuse/core": "^9.13.0",
"autoprefixer": "^10.4.15", "autoprefixer": "^10.4.15",
"cssnano": "^6.0.1", "cssnano": "^6.0.1",
"electron": "^26.0.0", "electron": "^26.6.10",
"electron-builder": "^24.6.3", "electron-builder": "^24.13.3",
"electron-builder-squirrel-windows": "^24.13.3",
"element-plus": "^2.4.2", "element-plus": "^2.4.2",
"eslint": "^7.28.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"moment": "^2.29.4", "moment": "^2.29.4",
"nedb": "^1.8.0", "nedb": "^1.8.0",
"node-cmd": "^5.0.0", "node-cmd": "^5.0.0",
"postcss": "^8.4.31",
"prettier": "^2.8.8", "prettier": "^2.8.8",
"sass": "^1.66.1", "sass": "^1.66.1",
"sass-loader": "^13.3.2",
"tailwindcss": "^3.3.3", "tailwindcss": "^3.3.3",
"tree-kill": "^1.2.2", "tree-kill": "^1.2.2",
"typescript": "^5.1.6", "typescript": "^5.1.6",
@ -64,7 +71,9 @@
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"electron-dl": "^3.5.1", "electron-dl": "^3.5.1",
"electron-log": "^5.1.7", "electron-log": "^5.1.7",
"isbinaryfile": "4.0.10",
"js-base64": "^3.7.7",
"tar": "^6.2.0", "tar": "^6.2.0",
"unzipper": "^0.10.14" "unused-filename": "^4.0.1"
} }
} }

View File

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

View File

Before

Width:  |  Height:  |  Size: 419 KiB

After

Width:  |  Height:  |  Size: 419 KiB

View File

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

View File

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 133 KiB

View File

Before

Width:  |  Height:  |  Size: 299 KiB

After

Width:  |  Height:  |  Size: 299 KiB

View File

@ -54,5 +54,9 @@ onMounted(() => {
<Icon class="animate__animated" :icon="r?.meta?.icon as string"/> <Icon class="animate__animated" :icon="r?.meta?.icon as string"/>
</li> </li>
</ul> </ul>
<div class="version mb-2 animate__animated" @click="handleOpenGitHubReleases">
v1.0.6
</div>
</div> </div>
</template> </template>

View File

@ -6,6 +6,7 @@ import Breadcrumb from "@/layout/compoenets/Breadcrumb.vue";
import {useDebounceFn} from "@vueuse/core"; import {useDebounceFn} from "@vueuse/core";
import {clone} from "@/utils/clone"; import {clone} from "@/utils/clone";
import {Icon} from "@iconify/vue"; import {Icon} from "@iconify/vue";
import {Base64} from "js-base64";
defineComponent({ defineComponent({
name: "Config" name: "Config"
@ -30,6 +31,8 @@ type Config = {
systemStartupConnect: boolean; systemStartupConnect: boolean;
user: string; user: string;
metaToken: string; metaToken: string;
transportHeartbeatInterval: number;
transportHeartbeatTimeout: number;
}; };
type Version = { type Version = {
@ -37,7 +40,18 @@ type Version = {
name: string; name: string;
} }
const formData = ref<Config>({ type ShareLinkConfig = {
serverAddr: string,
serverPort: number,
authMethod: string,
authToken: string,
transportHeartbeatInterval: number,
transportHeartbeatTimeout: number,
user: string,
metaToken: string,
}
const defaultFormData = ref<Config>({
currentVersion: "", currentVersion: "",
serverAddr: "", serverAddr: "",
serverPort: 7000, serverPort: 7000,
@ -55,9 +69,14 @@ const formData = ref<Config>({
systemSelfStart: false, systemSelfStart: false,
systemStartupConnect: false, systemStartupConnect: false,
user: "", user: "",
metaToken: "" metaToken: "",
transportHeartbeatInterval: 30,
transportHeartbeatTimeout: 90,
}); });
const formData = ref<Config>(defaultFormData.value);
const loading = ref(1); const loading = ref(1);
const rules = reactive<FormRules>({ const rules = reactive<FormRules>({
@ -99,12 +118,27 @@ const rules = reactive<FormRules>({
], ],
systemSelfStart: [ systemSelfStart: [
{required: true, message: "请选择是否开机自启", trigger: "change"}, {required: true, message: "请选择是否开机自启", trigger: "change"},
] ],
transportHeartbeatInterval: [
{required: true, message: "心跳间隔时间不能为空", trigger: "change"},
],
transportHeartbeatTimeout: [
{required: true, message: "心跳超时时间不能为空", trigger: "change"},
],
}); });
const versions = ref<Array<Version>>([]); const versions = ref<Array<Version>>([]);
const copyServerConfigBase64 = ref();
const pasteServerConfigBase64 = ref();
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>();
const visibles = reactive({
copyServerConfig: false,
pasteServerConfig: false,
});
const handleSubmit = useDebounceFn(() => { const handleSubmit = useDebounceFn(() => {
if (!formRef.value) return; if (!formRef.value) return;
formRef.value.validate(valid => { formRef.value.validate(valid => {
@ -138,6 +172,13 @@ onMounted(() => {
const {err, data} = args; const {err, data} = args;
if (!err) { if (!err) {
if (data) { if (data) {
console.log('data', data)
if (!data.transportHeartbeatInterval) {
data.transportHeartbeatInterval = defaultFormData.value.transportHeartbeatInterval
}
if (!data.transportHeartbeatTimeout) {
data.transportHeartbeatTimeout = defaultFormData.value.transportHeartbeatTimeout
}
formData.value = data; formData.value = data;
} }
} }
@ -177,6 +218,78 @@ const handleSelectFile = (type: number, ext: string[]) => {
}) })
} }
/**
* 分享配置
*/
const handleCopyServerConfig2Base64 = useDebounceFn(() => {
const shareConfig: ShareLinkConfig = {
serverAddr: formData.value.serverAddr,
serverPort: formData.value.serverPort,
authMethod: formData.value.authMethod,
authToken: formData.value.authToken,
transportHeartbeatInterval: formData.value.transportHeartbeatInterval,
transportHeartbeatTimeout: formData.value.transportHeartbeatTimeout,
user: formData.value.user,
metaToken: formData.value.metaToken,
};
const base64str = Base64.encode(
JSON.stringify(shareConfig)
)
copyServerConfigBase64.value = base64str;
visibles.copyServerConfig = true;
}, 300);
/**
* 导入配置
*/
const handlePasteServerConfig4Base64 = useDebounceFn(() => {
visibles.pasteServerConfig = true;
}, 300);
const handlePasteServerConfigBase64 = useDebounceFn(() => {
const plain = Base64.decode(pasteServerConfigBase64.value)
console.log('plain', plain)
let serverConfig: ShareLinkConfig = null;
try {
serverConfig = JSON.parse(plain)
} catch {
ElMessage({
type: "warning",
message: "链接不正确 请输入正确的链接"
});
return;
}
if (!serverConfig && !serverConfig.serverAddr) {
ElMessage({
type: "warning",
message: "链接不正确 请输入正确的链接"
});
return;
}
if (!serverConfig && !serverConfig.serverPort) {
ElMessage({
type: "warning",
message: "链接不正确 请输入正确的链接"
});
return;
}
formData.value.serverAddr = serverConfig.serverAddr
formData.value.serverPort = serverConfig.serverPort
formData.value.authMethod = serverConfig.authMethod
formData.value.authToken = serverConfig.authToken
formData.value.transportHeartbeatInterval = serverConfig.transportHeartbeatInterval
formData.value.transportHeartbeatTimeout = serverConfig.transportHeartbeatTimeout
formData.value.user = serverConfig.user
formData.value.metaToken = serverConfig.metaToken
handleSubmit();
visibles.pasteServerConfig = false;
}, 300)
onUnmounted(() => { onUnmounted(() => {
ipcRenderer.removeAllListeners("Config.getConfig.hook"); ipcRenderer.removeAllListeners("Config.getConfig.hook");
ipcRenderer.removeAllListeners("Config.saveConfig.hook"); ipcRenderer.removeAllListeners("Config.saveConfig.hook");
@ -236,7 +349,16 @@ onUnmounted(() => {
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
<div class="h2">服务器配置</div> <div class="h2 flex justify-between">
<div>服务器配置</div>
<div class="flex items-center justify-center">
<Icon @click="handleCopyServerConfig2Base64" class="mr-2 cursor-pointer text-xl font-bold"
icon="material-symbols:content-copy"/>
<Icon @click="handlePasteServerConfig4Base64" class="mr-2 cursor-pointer text-xl font-bold"
icon="material-symbols:content-paste-go"/>
</div>
</div>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
<el-form-item label="服务器地址:" prop="serverAddr"> <el-form-item label="服务器地址:" prop="serverAddr">
@ -300,6 +422,7 @@ onUnmounted(() => {
@change="handleAuthMethodChange" @change="handleAuthMethodChange"
clearable clearable
> >
<el-option label="无" value="null"></el-option>
<el-option label="令牌token" value="token"></el-option> <el-option label="令牌token" value="token"></el-option>
<el-option label="多用户" value="multiuser"></el-option> <el-option label="多用户" value="multiuser"></el-option>
</el-select> </el-select>
@ -365,7 +488,7 @@ onUnmounted(() => {
trigger="hover" trigger="hover"
> >
<template #default> <template #default>
对应参数<span class="font-black text-[#5A3DAA]">meta_token</span> 对应参数<span class="font-black text-[#5A3DAA]">metadatas.token</span>
</template> </template>
<template #reference> <template #reference>
<Icon class="text-base" color="#5A3DAA" icon="material-symbols:info"/> <Icon class="text-base" color="#5A3DAA" icon="material-symbols:info"/>
@ -381,6 +504,70 @@ onUnmounted(() => {
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12">
<el-form-item label="心跳间隔:" prop="transportHeartbeatInterval">
<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.heartbeatInterval</span>
</template>
<template #reference>
<Icon class="text-base" color="#5A3DAA" icon="material-symbols:info"/>
</template>
</el-popover>
</div>
心跳间隔
</template>
<el-input-number class="w-full" v-model="formData.transportHeartbeatInterval" :min="1" :max="10"
controls-position="right"/>
<!-- <el-input-->
<!-- placeholder="请输入心跳间隔"-->
<!-- type="number"-->
<!-- :min="0"-->
<!-- v-model="formData.heartbeatInterval"-->
<!-- >-->
<!-- <template #append></template>-->
<!-- </el-input>-->
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="心跳超时:" prop="transportHeartbeatTimeout">
<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.heartbeatTimeout</span>
</template>
<template #reference>
<Icon class="text-base" color="#5A3DAA" icon="material-symbols:info"/>
</template>
</el-popover>
</div>
心跳超时
</template>
<el-input-number class="w-full" v-model="formData.transportHeartbeatTimeout" :min="1" :max="10"
controls-position="right"/>
<!-- <el-input-->
<!-- placeholder="请输入心跳超时时间"-->
<!-- :min="0"-->
<!-- type="number"-->
<!-- v-model="formData.heartbeatTimeout"-->
<!-- >-->
<!-- <template #append></template>-->
<!-- </el-input>-->
</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>
@ -539,6 +726,7 @@ onUnmounted(() => {
</el-form-item> </el-form-item>
</el-col> </el-col>
</template> </template>
<el-col :span="24"> <el-col :span="24">
<div class="h2">日志配置</div> <div class="h2">日志配置</div>
</el-col> </el-col>
@ -621,6 +809,25 @@ onUnmounted(() => {
</el-form> </el-form>
</div> </div>
</div> </div>
<el-dialog v-model="visibles.copyServerConfig" title="分享服务器" width="500">
<el-alert class="mb-4" title="生成内容包含服务器密钥等内容 请妥善保管 且链接仅在Frpc-Desktop中可用" type="warning"
:closable="false"/>
<el-input class="h-30" v-model="copyServerConfigBase64" type="textarea" :rows="8"></el-input>
</el-dialog>
<el-dialog v-model="visibles.pasteServerConfig" title="导入服务器" width="500">
<el-input class="h-30"
v-model="pasteServerConfigBase64"
type="textarea" placeholder="在此输入分享链接"
:rows="8"></el-input>
<template #footer>
<div class="dialog-footer">
<el-button plain type="primary" @click="handlePasteServerConfigBase64">导入</el-button>
</div>
</template>
</el-dialog>
</div> </div>
</template> </template>
@ -641,4 +848,5 @@ onUnmounted(() => {
.button-input { .button-input {
width: calc(100% - 68px); width: calc(100% - 68px);
} }
</style> </style>

View File

@ -1,10 +1,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import { defineComponent, onMounted, onUnmounted, reactive, ref } from "vue"; import {defineComponent, onMounted, onUnmounted, reactive, ref} from "vue";
import { Icon } from "@iconify/vue"; import {Icon} from "@iconify/vue";
import Breadcrumb from "@/layout/compoenets/Breadcrumb.vue"; import Breadcrumb from "@/layout/compoenets/Breadcrumb.vue";
import { ElMessage, FormInstance, FormRules } from "element-plus"; import {ElMessage, FormInstance, FormRules} from "element-plus";
import { ipcRenderer } from "electron"; import {ipcRenderer} from "electron";
import { clone } from "@/utils/clone"; import {clone} from "@/utils/clone";
defineComponent({ defineComponent({
name: "Proxy" name: "Proxy"
@ -58,24 +58,24 @@ const editForm = ref<Proxy>({
*/ */
const editFormRules = reactive<FormRules>({ const editFormRules = reactive<FormRules>({
name: [ name: [
{ required: true, message: "请输入名称", trigger: "blur" }, {required: true, message: "请输入名称", trigger: "blur"},
// { // {
// pattern: /^[a-zA-Z]+$/, // pattern: /^[a-zA-Z]+$/,
// message: "", // message: "",
// trigger: "blur" // trigger: "blur"
// } // }
], ],
type: [{ required: true, message: "请选择类型", trigger: "blur" }], type: [{required: true, message: "请选择类型", trigger: "blur"}],
localIp: [ localIp: [
{ required: true, message: "请输入内网地址", trigger: "blur" }, {required: true, message: "请输入内网地址", trigger: "blur"},
{ {
pattern: /^[\w-]+(\.[\w-]+)+$/, pattern: /^[\w-]+(\.[\w-]+)+$/,
message: "请输入正确的内网地址", message: "请输入正确的内网地址",
trigger: "blur" trigger: "blur"
} }
], ],
localPort: [{ required: true, message: "请输入本地端口", trigger: "blur" }], localPort: [{required: true, message: "请输入本地端口", trigger: "blur"}],
remotePort: [{ required: true, message: "请输入远程端口", trigger: "blur" }] remotePort: [{required: true, message: "请输入远程端口", trigger: "blur"}]
}); });
/** /**
@ -152,7 +152,7 @@ const handleResetForm = () => {
const handleInitHook = () => { const handleInitHook = () => {
const InsertOrUpdateHook = (message: string, args: any) => { const InsertOrUpdateHook = (message: string, args: any) => {
loading.value.form--; loading.value.form--;
const { err } = args; const {err} = args;
if (!err) { if (!err) {
ElMessage({ ElMessage({
type: "success", type: "success",
@ -185,13 +185,13 @@ const handleInitHook = () => {
// }); // });
ipcRenderer.on("Proxy.getProxys.hook", (event, args) => { ipcRenderer.on("Proxy.getProxys.hook", (event, args) => {
loading.value.list--; loading.value.list--;
const { err, data } = args; const {err, data} = args;
if (!err) { if (!err) {
proxys.value = data; proxys.value = data;
} }
}); });
ipcRenderer.on("Proxy.deleteProxyById.hook", (event, args) => { ipcRenderer.on("Proxy.deleteProxyById.hook", (event, args) => {
const { err, data } = args; const {err, data} = args;
if (!err) { if (!err) {
handleLoadProxys(); handleLoadProxys();
ElMessage({ ElMessage({
@ -236,7 +236,7 @@ onUnmounted(() => {
class="cursor-pointer h-[36px] w-[36px] bg-[#5f3bb0] rounded text-white flex justify-center items-center" class="cursor-pointer h-[36px] w-[36px] bg-[#5f3bb0] rounded text-white flex justify-center items-center"
@click="handleOpenInsert" @click="handleOpenInsert"
> >
<Icon icon="material-symbols:add" /> <Icon icon="material-symbols:add"/>
</div> </div>
</breadcrumb> </breadcrumb>
<div class="app-container-breadcrumb" v-loading="loading.list > 0"> <div class="app-container-breadcrumb" v-loading="loading.list > 0">
@ -263,13 +263,13 @@ onUnmounted(() => {
</div> </div>
<div class="h-12 relative"> <div class="h-12 relative">
<div class="text-sm font-bold">{{ proxy.name }}</div> <div class="text-sm font-bold">{{ proxy.name }}</div>
<!-- <el-tag--> <!-- <el-tag-->
<!-- size="small"--> <!-- size="small"-->
<!-- class="absolute bottom-0"--> <!-- class="absolute bottom-0"-->
<!-- type="success"--> <!-- type="success"-->
<!-- effect="plain"--> <!-- effect="plain"-->
<!-- >正常--> <!-- >正常-->
<!-- </el-tag>--> <!-- </el-tag>-->
</div> </div>
</div> </div>
<div> <div>
@ -277,7 +277,7 @@ onUnmounted(() => {
<a href="javascript:void(0)" <a href="javascript:void(0)"
class="text-xl text-[#ADADAD] hover:text-[#5A3DAA]" class="text-xl text-[#ADADAD] hover:text-[#5A3DAA]"
> >
<Icon icon="material-symbols:more-vert" /> <Icon icon="material-symbols:more-vert"/>
</a> </a>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
@ -323,7 +323,7 @@ onUnmounted(() => {
v-else v-else
class="w-full h-full bg-white rounded p-2 overflow-hidden drop-shadow-xl flex justify-center items-center" class="w-full h-full bg-white rounded p-2 overflow-hidden drop-shadow-xl flex justify-center items-center"
> >
<el-empty description="暂无代理" /> <el-empty description="暂无代理"/>
</div> </div>
</div> </div>
@ -344,20 +344,22 @@ onUnmounted(() => {
<el-col :span="24"> <el-col :span="24">
<el-form-item label="代理类型:" prop="proxyType"> <el-form-item label="代理类型:" prop="proxyType">
<el-radio-group v-model="editForm.type"> <el-radio-group v-model="editForm.type">
<el-radio label="http" model-value="http" /> <el-radio label="http" model-value="http"/>
<el-radio label="https" model-value="https" /> <el-radio label="https" model-value="https"/>
<el-radio label="tcp" model-value="tcp" /> <el-radio label="tcp" model-value="tcp"/>
<el-radio label="udp" model-value="udp"/>
<!-- <el-radio label="stcp" model-value="stcp" />-->
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
<el-form-item label="代理名称:" prop="proxyName"> <el-form-item label="代理名称:" prop="proxyName">
<el-input v-model="editForm.name" placeholder="代理名称" /> <el-input v-model="editForm.name" placeholder="代理名称"/>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="16"> <el-col :span="16">
<el-form-item label="内网地址:" prop="localIp"> <el-form-item label="内网地址:" prop="localIp">
<el-input v-model="editForm.localIp" placeholder="127.0.0.1" /> <el-input v-model="editForm.localIp" placeholder="127.0.0.1"/>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
@ -372,7 +374,7 @@ onUnmounted(() => {
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<template v-if="editForm.type === 'tcp'"> <template v-if="editForm.type === 'tcp' || editForm.type === 'udp'">
<el-col :span="8"> <el-col :span="8">
<el-form-item label="外网端口:" prop="remotePort"> <el-form-item label="外网端口:" prop="remotePort">
<el-input-number <el-input-number
@ -422,7 +424,7 @@ onUnmounted(() => {
plain plain
@click="handleAddDomain" @click="handleAddDomain"
> >
<Icon icon="material-symbols:add" /> <Icon icon="material-symbols:add"/>
</el-button> </el-button>
<el-button <el-button
type="danger" type="danger"
@ -430,7 +432,7 @@ onUnmounted(() => {
@click="handleDeleteDomain(di)" @click="handleDeleteDomain(di)"
:disabled="editForm.customDomains.length === 1" :disabled="editForm.customDomains.length === 1"
> >
<Icon icon="material-symbols:delete-rounded" /> <Icon icon="material-symbols:delete-rounded"/>
</el-button> </el-button>
<!-- <div class="domain-input-button !bg-[#d3585b]">--> <!-- <div class="domain-input-button !bg-[#d3585b]">-->
<!-- <Icon icon="material-symbols:delete-rounded" />--> <!-- <Icon icon="material-symbols:delete-rounded" />-->
@ -467,6 +469,10 @@ onUnmounted(() => {
background: #5f3bb0; background: #5f3bb0;
} }
.udp {
background: #5ec7fe;
}
.domain-input { .domain-input {
width: calc(100% - 115px); width: calc(100% - 115px);
} }

View File

@ -22,7 +22,7 @@
] ]
}, },
"types": [ "types": [
"node", // "node",
"vite/client", "vite/client",
"element-plus/global" "element-plus/global"
], ],