Merge branch 'develop' into feat/xsl20240807-add-aboutPage
26
README.md
@ -16,7 +16,7 @@
|
||||
<h3 align="center">Frpc-Desktop</h3>
|
||||
|
||||
<p align="center">
|
||||
🎉 一个 Frp 跨平台桌面客户端 支持多个frp版本
|
||||
🎉 Frp 跨平台桌面客户端 支持所有frp版本
|
||||
<br />
|
||||
</p>
|
||||
</div>
|
||||
@ -24,12 +24,20 @@
|
||||
## TODO
|
||||
- [x] 开机自启动
|
||||
- [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.4版本 适配支持多用户插件
|
||||
- 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">
|
||||
|
||||
|
||||
## 演示
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
## License
|
||||
|
||||
|
@ -14,33 +14,131 @@
|
||||
"dist",
|
||||
"dist-electron"
|
||||
],
|
||||
"dmg": {
|
||||
"window": {
|
||||
"width": 540,
|
||||
"height": 380
|
||||
},
|
||||
"contents": [
|
||||
{
|
||||
"x": 410,
|
||||
"y": 230,
|
||||
"type": "link",
|
||||
"path": "/Applications"
|
||||
},
|
||||
{
|
||||
"x": 130,
|
||||
"y": 230,
|
||||
"type": "file"
|
||||
}
|
||||
]
|
||||
},
|
||||
"mac": {
|
||||
"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": {
|
||||
"target": [
|
||||
{
|
||||
"target": "nsis",
|
||||
"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": {
|
||||
// "artifactName": "${productName}-win-${arch}-${version}-Setup.${ext}",
|
||||
"oneClick": false,
|
||||
"perMachine": false,
|
||||
"allowToChangeInstallationDirectory": true,
|
||||
"deleteAppDataOnUninstall": false
|
||||
},
|
||||
"portable": {
|
||||
// "artifactName": "${productName}-win-${arch}-${version}.${ext}"
|
||||
},
|
||||
"linux": {
|
||||
"target": [
|
||||
"AppImage"
|
||||
{
|
||||
"target": "AppImage",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64",
|
||||
// "armv7l"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "deb",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64",
|
||||
// "armv7l"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "rpm",
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
},
|
||||
// {
|
||||
// "target": "snap",
|
||||
// "arch": [
|
||||
// "x64"
|
||||
// ]
|
||||
// }
|
||||
|
||||
],
|
||||
"artifactName": "${productName}-Linux-${arch}-${version}.${ext}"
|
||||
"category": "Network",
|
||||
// "artifactName": "${productName}-Linux-${arch}-${version}.${ext}"
|
||||
},
|
||||
"publish": [
|
||||
{
|
||||
"provider": "github",
|
||||
"owner": "luckjiawei",
|
||||
"repo": "frpc-desktop",
|
||||
"releaseType": "draft",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
@ -49,6 +49,7 @@ localPort = ${m.localPort}
|
||||
`;
|
||||
switch (m.type) {
|
||||
case "tcp":
|
||||
case "udp":
|
||||
toml += `remotePort = ${m.remotePort}`;
|
||||
break;
|
||||
case "http":
|
||||
@ -72,6 +73,12 @@ ${config.authMethod === 'multiuser' ? `
|
||||
user = "${config.user}"
|
||||
metadatas.token = "${config.metaToken}"
|
||||
` : ""}
|
||||
${config.transportHeartbeatInterval ? `
|
||||
transport.heartbeatInterval = ${config.transportHeartbeatInterval}
|
||||
` : ""}
|
||||
${config.transportHeartbeatTimeout ? `
|
||||
transport.heartbeatTimeout = ${config.transportHeartbeatTimeout}
|
||||
` : ""}
|
||||
|
||||
|
||||
log.to = "frpc.log"
|
||||
@ -111,6 +118,7 @@ local_port = ${m.localPort}
|
||||
`;
|
||||
switch (m.type) {
|
||||
case "tcp":
|
||||
case "udp":
|
||||
ini += `remote_port = ${m.remotePort}`;
|
||||
break;
|
||||
case "http":
|
||||
@ -135,6 +143,14 @@ ${config.authMethod === 'multiuser' ? `
|
||||
user = ${config.user}
|
||||
meta_token = ${config.metaToken}
|
||||
` : ""}
|
||||
|
||||
${config.transportHeartbeatInterval ? `
|
||||
heartbeat_interval = ${config.transportHeartbeatInterval}
|
||||
` : ""}
|
||||
${config.transportHeartbeatTimeout ? `
|
||||
heartbeat_timeout = ${config.transportHeartbeatTimeout}
|
||||
` : ""}
|
||||
|
||||
log_file = "frpc.log"
|
||||
log_level = ${config.logLevel}
|
||||
log_max_days = ${config.logMaxDays}
|
||||
|
@ -5,7 +5,6 @@ const fs = require("fs");
|
||||
const path = require("path");
|
||||
const zlib = require("zlib");
|
||||
const {download} = require("electron-dl");
|
||||
const unzipper = require('unzipper');
|
||||
const AdmZip = require('adm-zip');
|
||||
const log = require('electron-log');
|
||||
|
||||
@ -15,13 +14,13 @@ const versionRelation = {
|
||||
"win32_ia32": ["window", "386"],
|
||||
"darwin_arm64": ["darwin", "arm64"],
|
||||
"darwin_x64": ["darwin", "amd64"],
|
||||
// "darwin_arm64": ["window", "amd64"],
|
||||
"darwin_amd64": ["darwin", "amd64"],
|
||||
"linux_x64": ["linux", "amd64"],
|
||||
"linux_arm64": ["linux", "arm64"],
|
||||
}
|
||||
const platform = process.platform;
|
||||
const arch = process.arch;
|
||||
let currArch = `${platform}_${arch}`
|
||||
// currArch = `darwin_x64`
|
||||
const frpArch = versionRelation[currArch]
|
||||
|
||||
const unTarGZ = (tarGzPath: string, targetPath: string) => {
|
||||
|
84
electron/api/update.ts
Normal 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("手动检查更新一次")
|
||||
|
||||
|
||||
}
|
@ -7,6 +7,7 @@ import {initProxyApi} from "../api/proxy";
|
||||
import {initFrpcApi, startFrpWorkerProcess, stopFrpcProcess} from "../api/frpc";
|
||||
import {initLoggerApi} from "../api/logger";
|
||||
import {initFileApi} from "../api/file";
|
||||
import {initUpdaterApi} from "../api/update";
|
||||
import {getConfig} from "../storage/config";
|
||||
import log from "electron-log";
|
||||
// The built directory structure
|
||||
@ -117,7 +118,7 @@ async function createWindow() {
|
||||
}
|
||||
|
||||
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)> = [
|
||||
{
|
||||
label: '显示主窗口', click: function () {
|
||||
@ -162,6 +163,14 @@ export const createTray = () => {
|
||||
app.whenReady().then(() => {
|
||||
createWindow().then(r => {
|
||||
createTray()
|
||||
// 初始化各个API
|
||||
initGitHubApi();
|
||||
initConfigApi();
|
||||
initProxyApi();
|
||||
initFrpcApi();
|
||||
initLoggerApi();
|
||||
initFileApi();
|
||||
// initUpdaterApi(win);
|
||||
})
|
||||
});
|
||||
|
||||
@ -212,15 +221,3 @@ ipcMain.handle("open-win", (_, arg) => {
|
||||
childWindow.loadFile(indexHtml, {hash: arg});
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('open-url', (event, url) => {
|
||||
shell.openExternal(url).then(r => {
|
||||
});
|
||||
});
|
||||
|
||||
initGitHubApi();
|
||||
initConfigApi();
|
||||
initProxyApi();
|
||||
initFrpcApi();
|
||||
initLoggerApi();
|
||||
initFileApi();
|
||||
|
@ -44,7 +44,7 @@ function useLoading() {
|
||||
animation-fill-mode: both;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background: #fff;
|
||||
background: #5F3BB0;
|
||||
animation: square-spin 3s 0s cubic-bezier(0.09, 0.57, 0.49, 0.9) infinite;
|
||||
}
|
||||
.app-loading-wrap {
|
||||
@ -56,7 +56,7 @@ function useLoading() {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #282c34;
|
||||
background: #ffffff;
|
||||
z-index: 9;
|
||||
}
|
||||
`
|
||||
|
@ -28,6 +28,8 @@ export type Config = {
|
||||
systemStartupConnect: boolean;
|
||||
user: string;
|
||||
metaToken: string;
|
||||
transportHeartbeatInterval: number;
|
||||
transportHeartbeatTimeout: number;
|
||||
};
|
||||
|
||||
/**
|
||||
|
BIN
logo(new).pxd
BIN
logo(old).png
Before Width: | Height: | Size: 37 KiB |
41
package.json
@ -1,19 +1,21 @@
|
||||
{
|
||||
"name": "Frpc-Desktop",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.6",
|
||||
"main": "dist-electron/main/index.js",
|
||||
"description": "一个frpc桌面客户端",
|
||||
"repository": "https://github.com/luckjiawei/frpc-desktop",
|
||||
"repository": "github:luckjiawei/frpc-desktop",
|
||||
"author": "刘嘉伟 <8473136@qq.com>",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"keywords": [
|
||||
"electron",
|
||||
"rollup",
|
||||
"vite",
|
||||
"vue3",
|
||||
"vue"
|
||||
"frp",
|
||||
"frpc",
|
||||
"proxy",
|
||||
"electron-app"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"debug": {
|
||||
"env": {
|
||||
"VITE_DEV_SERVER_URL": "http://127.0.0.1:3344/"
|
||||
@ -22,11 +24,14 @@
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc --noEmit && vite build",
|
||||
"build:electron": "npm run build && electron-builder",
|
||||
"build:electron:mac": "npm run build && electron-builder --mac --x64 --arm64",
|
||||
"build:electron:win": "npm run build && electron-builder --win --arm64 --x64 --ia32",
|
||||
"build:electron:linux": "npm run build && electron-builder --linux --arm64 --x64",
|
||||
"build:electron:all": "npm run build:electron:win && npm run build:electron:mac && npm run build:electron:linux",
|
||||
"build:electron": "npm run build && electron-builder --mac --win --linux",
|
||||
"build:electron:mac": "npm run build && electron-builder --mac",
|
||||
"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",
|
||||
"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",
|
||||
"electron:generate-icons": "electron-icon-builder --input=./public/logo.png --output=build --flatten"
|
||||
},
|
||||
@ -38,16 +43,18 @@
|
||||
"@vueuse/core": "^9.13.0",
|
||||
"autoprefixer": "^10.4.15",
|
||||
"cssnano": "^6.0.1",
|
||||
"electron": "^26.0.0",
|
||||
"electron-builder": "^24.6.3",
|
||||
"electron": "^26.6.10",
|
||||
"electron-builder": "^24.13.3",
|
||||
"electron-builder-squirrel-windows": "^24.13.3",
|
||||
"element-plus": "^2.4.2",
|
||||
"eslint": "^7.28.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"moment": "^2.29.4",
|
||||
"nedb": "^1.8.0",
|
||||
"node-cmd": "^5.0.0",
|
||||
"postcss": "^8.4.31",
|
||||
"prettier": "^2.8.8",
|
||||
"sass": "^1.66.1",
|
||||
"sass-loader": "^13.3.2",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"tree-kill": "^1.2.2",
|
||||
"typescript": "^5.1.6",
|
||||
@ -64,7 +71,9 @@
|
||||
"animate.css": "^4.1.1",
|
||||
"electron-dl": "^3.5.1",
|
||||
"electron-log": "^5.1.7",
|
||||
"isbinaryfile": "4.0.10",
|
||||
"js-base64": "^3.7.7",
|
||||
"tar": "^6.2.0",
|
||||
"unzipper": "^0.10.14"
|
||||
"unused-filename": "^4.0.1"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
Before Width: | Height: | Size: 419 KiB After Width: | Height: | Size: 419 KiB |
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 133 KiB |
Before Width: | Height: | Size: 299 KiB After Width: | Height: | Size: 299 KiB |
@ -54,5 +54,9 @@ onMounted(() => {
|
||||
<Icon class="animate__animated" :icon="r?.meta?.icon as string"/>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="version mb-2 animate__animated" @click="handleOpenGitHubReleases">
|
||||
v1.0.6
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
@ -6,6 +6,7 @@ import Breadcrumb from "@/layout/compoenets/Breadcrumb.vue";
|
||||
import {useDebounceFn} from "@vueuse/core";
|
||||
import {clone} from "@/utils/clone";
|
||||
import {Icon} from "@iconify/vue";
|
||||
import {Base64} from "js-base64";
|
||||
|
||||
defineComponent({
|
||||
name: "Config"
|
||||
@ -30,6 +31,8 @@ type Config = {
|
||||
systemStartupConnect: boolean;
|
||||
user: string;
|
||||
metaToken: string;
|
||||
transportHeartbeatInterval: number;
|
||||
transportHeartbeatTimeout: number;
|
||||
};
|
||||
|
||||
type Version = {
|
||||
@ -37,7 +40,18 @@ type Version = {
|
||||
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: "",
|
||||
serverAddr: "",
|
||||
serverPort: 7000,
|
||||
@ -55,9 +69,14 @@ const formData = ref<Config>({
|
||||
systemSelfStart: false,
|
||||
systemStartupConnect: false,
|
||||
user: "",
|
||||
metaToken: ""
|
||||
metaToken: "",
|
||||
transportHeartbeatInterval: 30,
|
||||
transportHeartbeatTimeout: 90,
|
||||
});
|
||||
|
||||
|
||||
const formData = ref<Config>(defaultFormData.value);
|
||||
|
||||
const loading = ref(1);
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
@ -99,12 +118,27 @@ const rules = reactive<FormRules>({
|
||||
],
|
||||
systemSelfStart: [
|
||||
{required: true, message: "请选择是否开机自启", trigger: "change"},
|
||||
]
|
||||
],
|
||||
transportHeartbeatInterval: [
|
||||
{required: true, message: "心跳间隔时间不能为空", trigger: "change"},
|
||||
],
|
||||
transportHeartbeatTimeout: [
|
||||
{required: true, message: "心跳超时时间不能为空", trigger: "change"},
|
||||
],
|
||||
|
||||
});
|
||||
|
||||
const versions = ref<Array<Version>>([]);
|
||||
const copyServerConfigBase64 = ref();
|
||||
const pasteServerConfigBase64 = ref();
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
const visibles = reactive({
|
||||
copyServerConfig: false,
|
||||
pasteServerConfig: false,
|
||||
});
|
||||
|
||||
const handleSubmit = useDebounceFn(() => {
|
||||
if (!formRef.value) return;
|
||||
formRef.value.validate(valid => {
|
||||
@ -138,6 +172,13 @@ onMounted(() => {
|
||||
const {err, data} = args;
|
||||
if (!err) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -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(() => {
|
||||
ipcRenderer.removeAllListeners("Config.getConfig.hook");
|
||||
ipcRenderer.removeAllListeners("Config.saveConfig.hook");
|
||||
@ -236,7 +349,16 @@ onUnmounted(() => {
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<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 :span="24">
|
||||
<el-form-item label="服务器地址:" prop="serverAddr">
|
||||
@ -300,6 +422,7 @@ onUnmounted(() => {
|
||||
@change="handleAuthMethodChange"
|
||||
clearable
|
||||
>
|
||||
<el-option label="无" value="null"></el-option>
|
||||
<el-option label="令牌(token)" value="token"></el-option>
|
||||
<el-option label="多用户" value="multiuser"></el-option>
|
||||
</el-select>
|
||||
@ -365,7 +488,7 @@ onUnmounted(() => {
|
||||
trigger="hover"
|
||||
>
|
||||
<template #default>
|
||||
对应参数:<span class="font-black text-[#5A3DAA]">meta_token</span>
|
||||
对应参数:<span class="font-black text-[#5A3DAA]">metadatas.token</span>
|
||||
</template>
|
||||
<template #reference>
|
||||
<Icon class="text-base" color="#5A3DAA" icon="material-symbols:info"/>
|
||||
@ -381,6 +504,70 @@ onUnmounted(() => {
|
||||
/>
|
||||
</el-form-item>
|
||||
</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">
|
||||
<div class="h2">TSL Config</div>
|
||||
</el-col>
|
||||
@ -539,6 +726,7 @@ onUnmounted(() => {
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
|
||||
<el-col :span="24">
|
||||
<div class="h2">日志配置</div>
|
||||
</el-col>
|
||||
@ -621,6 +809,25 @@ onUnmounted(() => {
|
||||
</el-form>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
@ -641,4 +848,5 @@ onUnmounted(() => {
|
||||
.button-input {
|
||||
width: calc(100% - 68px);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
@ -1,10 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
import { defineComponent, onMounted, onUnmounted, reactive, ref } from "vue";
|
||||
import { Icon } from "@iconify/vue";
|
||||
import {defineComponent, onMounted, onUnmounted, reactive, ref} from "vue";
|
||||
import {Icon} from "@iconify/vue";
|
||||
import Breadcrumb from "@/layout/compoenets/Breadcrumb.vue";
|
||||
import { ElMessage, FormInstance, FormRules } from "element-plus";
|
||||
import { ipcRenderer } from "electron";
|
||||
import { clone } from "@/utils/clone";
|
||||
import {ElMessage, FormInstance, FormRules} from "element-plus";
|
||||
import {ipcRenderer} from "electron";
|
||||
import {clone} from "@/utils/clone";
|
||||
|
||||
defineComponent({
|
||||
name: "Proxy"
|
||||
@ -58,24 +58,24 @@ const editForm = ref<Proxy>({
|
||||
*/
|
||||
const editFormRules = reactive<FormRules>({
|
||||
name: [
|
||||
{ required: true, message: "请输入名称", trigger: "blur" },
|
||||
{required: true, message: "请输入名称", trigger: "blur"},
|
||||
// {
|
||||
// pattern: /^[a-zA-Z]+$/,
|
||||
// message: "名称只能是英文",
|
||||
// trigger: "blur"
|
||||
// }
|
||||
],
|
||||
type: [{ required: true, message: "请选择类型", trigger: "blur" }],
|
||||
type: [{required: true, message: "请选择类型", trigger: "blur"}],
|
||||
localIp: [
|
||||
{ required: true, message: "请输入内网地址", trigger: "blur" },
|
||||
{required: true, message: "请输入内网地址", trigger: "blur"},
|
||||
{
|
||||
pattern: /^[\w-]+(\.[\w-]+)+$/,
|
||||
message: "请输入正确的内网地址",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
localPort: [{ required: true, message: "请输入本地端口", trigger: "blur" }],
|
||||
remotePort: [{ required: true, message: "请输入远程端口", trigger: "blur" }]
|
||||
localPort: [{required: true, message: "请输入本地端口", trigger: "blur"}],
|
||||
remotePort: [{required: true, message: "请输入远程端口", trigger: "blur"}]
|
||||
});
|
||||
|
||||
/**
|
||||
@ -152,7 +152,7 @@ const handleResetForm = () => {
|
||||
const handleInitHook = () => {
|
||||
const InsertOrUpdateHook = (message: string, args: any) => {
|
||||
loading.value.form--;
|
||||
const { err } = args;
|
||||
const {err} = args;
|
||||
if (!err) {
|
||||
ElMessage({
|
||||
type: "success",
|
||||
@ -185,13 +185,13 @@ const handleInitHook = () => {
|
||||
// });
|
||||
ipcRenderer.on("Proxy.getProxys.hook", (event, args) => {
|
||||
loading.value.list--;
|
||||
const { err, data } = args;
|
||||
const {err, data} = args;
|
||||
if (!err) {
|
||||
proxys.value = data;
|
||||
}
|
||||
});
|
||||
ipcRenderer.on("Proxy.deleteProxyById.hook", (event, args) => {
|
||||
const { err, data } = args;
|
||||
const {err, data} = args;
|
||||
if (!err) {
|
||||
handleLoadProxys();
|
||||
ElMessage({
|
||||
@ -233,65 +233,65 @@ onUnmounted(() => {
|
||||
<div class="main">
|
||||
<breadcrumb>
|
||||
<div
|
||||
class="cursor-pointer h-[36px] w-[36px] bg-[#5f3bb0] rounded text-white flex justify-center items-center"
|
||||
@click="handleOpenInsert"
|
||||
class="cursor-pointer h-[36px] w-[36px] bg-[#5f3bb0] rounded text-white flex justify-center items-center"
|
||||
@click="handleOpenInsert"
|
||||
>
|
||||
<Icon icon="material-symbols:add" />
|
||||
<Icon icon="material-symbols:add"/>
|
||||
</div>
|
||||
</breadcrumb>
|
||||
<div class="app-container-breadcrumb" v-loading="loading.list > 0">
|
||||
<template v-if="proxys && proxys.length > 0">
|
||||
<el-row :gutter="20">
|
||||
<el-col
|
||||
v-for="proxy in proxys"
|
||||
:key="proxy._id"
|
||||
:lg="6"
|
||||
:md="8"
|
||||
:sm="12"
|
||||
:xl="6"
|
||||
:xs="24"
|
||||
class="mb-[20px]"
|
||||
v-for="proxy in proxys"
|
||||
:key="proxy._id"
|
||||
:lg="6"
|
||||
:md="8"
|
||||
:sm="12"
|
||||
:xl="6"
|
||||
:xs="24"
|
||||
class="mb-[20px]"
|
||||
>
|
||||
<div class="bg-white w-full rounded drop-shadow-xl p-4">
|
||||
<div class="w-full flex justify-between">
|
||||
<div class="flex">
|
||||
<div
|
||||
class="w-12 h-12 rounded mr-4 flex justify-center items-center"
|
||||
:class="proxy.type"
|
||||
class="w-12 h-12 rounded mr-4 flex justify-center items-center"
|
||||
:class="proxy.type"
|
||||
>
|
||||
<span class="text-white text-sm">{{ proxy.type }}</span>
|
||||
</div>
|
||||
<div class="h-12 relative">
|
||||
<div class="text-sm font-bold">{{ proxy.name }}</div>
|
||||
<!-- <el-tag-->
|
||||
<!-- size="small"-->
|
||||
<!-- class="absolute bottom-0"-->
|
||||
<!-- type="success"-->
|
||||
<!-- effect="plain"-->
|
||||
<!-- >正常-->
|
||||
<!-- </el-tag>-->
|
||||
<!-- <el-tag-->
|
||||
<!-- size="small"-->
|
||||
<!-- class="absolute bottom-0"-->
|
||||
<!-- type="success"-->
|
||||
<!-- effect="plain"-->
|
||||
<!-- >正常-->
|
||||
<!-- </el-tag>-->
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<el-dropdown size="small">
|
||||
<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>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="handleOpenUpdate(proxy)">
|
||||
<Icon
|
||||
icon="material-symbols:edit"
|
||||
class="primary-text text-[14px]"
|
||||
icon="material-symbols:edit"
|
||||
class="primary-text text-[14px]"
|
||||
/>
|
||||
<span class="ml-1">修 改</span>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="handleDeleteProxy(proxy)">
|
||||
<Icon
|
||||
icon="material-symbols:delete-rounded"
|
||||
class="text-red-500 text-[14px]"
|
||||
icon="material-symbols:delete-rounded"
|
||||
class="text-red-500 text-[14px]"
|
||||
/>
|
||||
<span class="ml-1">删 除</span>
|
||||
</el-dropdown-item>
|
||||
@ -320,81 +320,83 @@ onUnmounted(() => {
|
||||
</el-row>
|
||||
</template>
|
||||
<div
|
||||
v-else
|
||||
class="w-full h-full bg-white rounded p-2 overflow-hidden drop-shadow-xl flex justify-center items-center"
|
||||
v-else
|
||||
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>
|
||||
|
||||
<el-dialog
|
||||
v-model="edit.visible"
|
||||
:title="edit.title"
|
||||
class="w-[400px]"
|
||||
top="30px"
|
||||
v-model="edit.visible"
|
||||
:title="edit.title"
|
||||
class="w-[400px]"
|
||||
top="30px"
|
||||
>
|
||||
<el-form
|
||||
v-loading="loading.form"
|
||||
label-position="top"
|
||||
:model="editForm"
|
||||
:rules="editFormRules"
|
||||
ref="editFormRef"
|
||||
v-loading="loading.form"
|
||||
label-position="top"
|
||||
:model="editForm"
|
||||
:rules="editFormRules"
|
||||
ref="editFormRef"
|
||||
>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="代理类型:" prop="proxyType">
|
||||
<el-radio-group v-model="editForm.type">
|
||||
<el-radio label="http" model-value="http" />
|
||||
<el-radio label="https" model-value="https" />
|
||||
<el-radio label="tcp" model-value="tcp" />
|
||||
<el-radio label="http" model-value="http"/>
|
||||
<el-radio label="https" model-value="https"/>
|
||||
<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-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="代理名称:" prop="proxyName">
|
||||
<el-input v-model="editForm.name" placeholder="代理名称" />
|
||||
<el-input v-model="editForm.name" placeholder="代理名称"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="16">
|
||||
<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-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="内网端口:" prop="localPort">
|
||||
<el-input-number
|
||||
placeholder="8080"
|
||||
class="!w-full"
|
||||
:min="0"
|
||||
:max="65535"
|
||||
v-model="editForm.localPort"
|
||||
controls-position="right"
|
||||
placeholder="8080"
|
||||
class="!w-full"
|
||||
:min="0"
|
||||
:max="65535"
|
||||
v-model="editForm.localPort"
|
||||
controls-position="right"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<template v-if="editForm.type === 'tcp'">
|
||||
<template v-if="editForm.type === 'tcp' || editForm.type === 'udp'">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="外网端口:" prop="remotePort">
|
||||
<el-input-number
|
||||
:min="0"
|
||||
:max="65535"
|
||||
placeholder="8080"
|
||||
v-model="editForm.remotePort"
|
||||
controls-position="right"
|
||||
:min="0"
|
||||
:max="65535"
|
||||
placeholder="8080"
|
||||
v-model="editForm.remotePort"
|
||||
controls-position="right"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
<template
|
||||
v-if="editForm.type === 'http' || editForm.type === 'https'"
|
||||
v-if="editForm.type === 'http' || editForm.type === 'https'"
|
||||
>
|
||||
<el-col :span="24">
|
||||
<el-form-item
|
||||
v-for="(d, di) in editForm.customDomains"
|
||||
:key="'domain' + di"
|
||||
:label="di === 0 ? '自定义域名:' : ''"
|
||||
:prop="`customDomains.${di}`"
|
||||
:rules="[
|
||||
v-for="(d, di) in editForm.customDomains"
|
||||
:key="'domain' + di"
|
||||
:label="di === 0 ? '自定义域名:' : ''"
|
||||
:prop="`customDomains.${di}`"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: `自定义域名不能为空`,
|
||||
@ -409,28 +411,28 @@ onUnmounted(() => {
|
||||
]"
|
||||
>
|
||||
<el-input
|
||||
class="domain-input"
|
||||
placeholder="github.com"
|
||||
v-model="editForm.customDomains[di]"
|
||||
class="domain-input"
|
||||
placeholder="github.com"
|
||||
v-model="editForm.customDomains[di]"
|
||||
/>
|
||||
<!-- <div class="domain-input-button !bg-[#67c23a]">-->
|
||||
<!-- <Icon icon="material-symbols:add" />-->
|
||||
<!-- </div>-->
|
||||
<el-button
|
||||
class="ml-[10px]"
|
||||
type="primary"
|
||||
plain
|
||||
@click="handleAddDomain"
|
||||
class="ml-[10px]"
|
||||
type="primary"
|
||||
plain
|
||||
@click="handleAddDomain"
|
||||
>
|
||||
<Icon icon="material-symbols:add" />
|
||||
<Icon icon="material-symbols:add"/>
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
@click="handleDeleteDomain(di)"
|
||||
:disabled="editForm.customDomains.length === 1"
|
||||
type="danger"
|
||||
plain
|
||||
@click="handleDeleteDomain(di)"
|
||||
:disabled="editForm.customDomains.length === 1"
|
||||
>
|
||||
<Icon icon="material-symbols:delete-rounded" />
|
||||
<Icon icon="material-symbols:delete-rounded"/>
|
||||
</el-button>
|
||||
<!-- <div class="domain-input-button !bg-[#d3585b]">-->
|
||||
<!-- <Icon icon="material-symbols:delete-rounded" />-->
|
||||
@ -443,7 +445,7 @@ onUnmounted(() => {
|
||||
<div class="w-full flex justify-end">
|
||||
<el-button @click="edit.visible = false">关 闭</el-button>
|
||||
<el-button plain type="primary" @click="handleSubmit"
|
||||
>保 存
|
||||
>保 存
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
@ -467,6 +469,10 @@ onUnmounted(() => {
|
||||
background: #5f3bb0;
|
||||
}
|
||||
|
||||
.udp {
|
||||
background: #5ec7fe;
|
||||
}
|
||||
|
||||
.domain-input {
|
||||
width: calc(100% - 115px);
|
||||
}
|
||||
|
@ -22,7 +22,7 @@
|
||||
]
|
||||
},
|
||||
"types": [
|
||||
"node",
|
||||
// "node",
|
||||
"vite/client",
|
||||
"element-plus/global"
|
||||
],
|
||||
|