Merge branch 'develop'

This commit is contained in:
刘嘉伟 2023-12-01 16:58:11 +08:00
commit 1f531ee330
10 changed files with 640 additions and 343 deletions

View File

@ -21,6 +21,10 @@
</p> </p>
</div> </div>
## 里程碑
- 2023-12-01: 发布v1.0.1版本
- 2023-11-28: 发布v1.0版本
## 演示 ## 演示
![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/demo/conn.png?raw=true)
@ -31,11 +35,6 @@
![log](https://github.com/luckjiawei/frpc-desktop/blob/main/demo/log.png?raw=true) ![log](https://github.com/luckjiawei/frpc-desktop/blob/main/demo/log.png?raw=true)
[//]: # (## 里程碑)
[//]: # (- 2023-11-28: 发布v1.0版本)
## License ## License
[MIT](LICENSE) [MIT](LICENSE)

13
electron/api/file.ts Normal file
View File

@ -0,0 +1,13 @@
import {dialog, ipcMain} from "electron";
export const initFileApi = () => {
ipcMain.handle("file.selectFile", async (event, args) => {
const result = dialog.showOpenDialogSync({
properties: ['openFile'],
filters: [
{name: 'Text Files', extensions: args},
]
})
return result;
});
}

View File

@ -1,25 +1,18 @@
import { app, ipcMain } 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} from "../storage/proxy";
import { getVersionById } from "../storage/version"; import {getVersionById} from "../storage/version";
import treeKill from "tree-kill";
const fs = require("fs"); const fs = require("fs");
const path = require("path"); const path = require("path");
const {exec, spawn} = require("child_process");
const { exec, spawn } = require("child_process");
export let frpcProcess = null; export let frpcProcess = null;
const runningCmd = { const runningCmd = {
commandPath: null, commandPath: null,
configPath: null configPath: null
}; };
let frpcStatusListener = null;
// const getFrpc = (config: Config) => {
// getVersionById(config.currentVersion, (err, document) => {
// if (!err) {
// }
// });
// };
/** /**
* *
@ -74,20 +67,30 @@ serverPort = ${config.serverPort}
auth.method = "${config.authMethod}" auth.method = "${config.authMethod}"
auth.token = "${config.authToken}" auth.token = "${config.authToken}"
log.to = "frpc.log" log.to = "frpc.log"
log.level = "debug" log.level = "${config.logLevel}"
log.maxDays = 3 log.maxDays = ${config.logMaxDays}
webServer.addr = "127.0.0.1" webServer.addr = "127.0.0.1"
webServer.port = 57400 webServer.port = 57400
transport.tls.enable = ${config.tlsConfigEnable}
${config.tlsConfigEnable ? `
transport.tls.certFile = "${config.tlsConfigCertFile}"
transport.tls.keyFile = "${config.tlsConfigKeyFile}"
transport.tls.trustedCaFile = "${config.tlsConfigTrustedCaFile}"
transport.tls.serverName = "${config.tlsConfigServerName}"
` : ""}
${proxyToml}
${proxyToml.join("")}
`; `;
// const configPath = path.join("frp.toml"); // const configPath = path.join("frp.toml");
const filename = "frp.toml"; const filename = "frp.toml";
const configPath = path.join(app.getPath("userData"), filename)
console.debug("生成配置成功", configPath)
fs.writeFile( fs.writeFile(
path.join(app.getPath("userData"), filename), // 配置文件目录 configPath, // 配置文件目录
toml, // 配置文件内容 toml, // 配置文件内容
{ flag: "w" }, {flag: "w"},
err => { err => {
if (!err) { if (!err) {
callback(filename); callback(filename);
@ -106,6 +109,7 @@ ${proxyToml}
*/ */
const startFrpcProcess = (commandPath: string, configPath: string) => { const startFrpcProcess = (commandPath: string, configPath: string) => {
const command = `${commandPath} -c ${configPath}`; const command = `${commandPath} -c ${configPath}`;
console.info("启动", command)
frpcProcess = spawn(command, { frpcProcess = spawn(command, {
cwd: app.getPath("userData"), cwd: app.getPath("userData"),
shell: true shell: true
@ -113,14 +117,28 @@ const startFrpcProcess = (commandPath: string, configPath: string) => {
runningCmd.commandPath = commandPath; runningCmd.commandPath = commandPath;
runningCmd.configPath = configPath; runningCmd.configPath = configPath;
frpcProcess.stdout.on("data", data => { frpcProcess.stdout.on("data", data => {
console.log(`命令输出: ${data}`); console.debug(`命令输出: ${data}`);
}); });
frpcProcess.stdout.on("error", data => { frpcProcess.stdout.on("error", data => {
console.log(`执行错误: ${data}`); console.log("启动错误", data)
frpcProcess.kill("SIGINT"); stopFrpcProcess()
}); });
frpcStatusListener = setInterval(() => {
const status = frpcProcessStatus()
if (!status) {
console.log("连接已断开")
new Notification({
title: "Frpc Desktop",
body: "连接已断开,请前往日志查看原因"
}).show()
clearInterval(frpcStatusListener)
}
}, 3000)
}; };
/**
* frpc配置
*/
export const reloadFrpcProcess = () => { export const reloadFrpcProcess = () => {
if (frpcProcess && !frpcProcess.killed) { if (frpcProcess && !frpcProcess.killed) {
getConfig((err1, config) => { getConfig((err1, config) => {
@ -128,7 +146,7 @@ export const reloadFrpcProcess = () => {
if (config) { if (config) {
generateConfig(config, configPath => { generateConfig(config, configPath => {
const command = `${runningCmd.commandPath} reload -c ${configPath}`; const command = `${runningCmd.commandPath} reload -c ${configPath}`;
console.log("重启", command); console.info("重启", command);
exec(command, { exec(command, {
cwd: app.getPath("userData"), cwd: app.getPath("userData"),
shell: true shell: true
@ -140,13 +158,45 @@ export const reloadFrpcProcess = () => {
} }
}; };
export const initFrpcApi = () => { /**
ipcMain.handle("frpc.running", async (event, args) => { * frpc子进程
*/
export const stopFrpcProcess = (callback?:() => void) => {
if (frpcProcess) {
treeKill(frpcProcess.pid, (error: Error) => {
if (error) {
console.log("关闭失败", frpcProcess.pid, error)
} else {
console.log('关闭成功')
frpcProcess = null
clearInterval(frpcStatusListener)
}
callback()
})
}
}
/**
* frpc子进程状态
*/
export const frpcProcessStatus = () => {
if (!frpcProcess) { if (!frpcProcess) {
return false; return false;
} else {
return !frpcProcess.killed;
} }
try {
// 发送信号给进程,如果进程存在,会正常返回
process.kill(frpcProcess.pid, 0);
return true;
} catch (error) {
// 进程不存在,抛出异常
return false;
}
}
export const initFrpcApi = () => {
ipcMain.handle("frpc.running", async (event, args) => {
return frpcProcessStatus()
}); });
ipcMain.on("frpc.start", async (event, args) => { ipcMain.on("frpc.start", async (event, args) => {
@ -163,7 +213,7 @@ export const initFrpcApi = () => {
path.join(frpcVersionPath, "frpc.exe"), path.join(frpcVersionPath, "frpc.exe"),
configPath configPath
); );
}else { } else {
startFrpcProcess( startFrpcProcess(
path.join(frpcVersionPath, "frpc"), path.join(frpcVersionPath, "frpc"),
configPath configPath
@ -184,8 +234,7 @@ export const initFrpcApi = () => {
ipcMain.on("frpc.stop", () => { ipcMain.on("frpc.stop", () => {
if (frpcProcess && !frpcProcess.killed) { if (frpcProcess && !frpcProcess.killed) {
console.log("关闭"); stopFrpcProcess()
frpcProcess.kill();
} }
}); });
}; };

View File

@ -1,4 +1,4 @@
import {app, BrowserWindow, ipcMain, net} from "electron"; import {app, BrowserWindow, ipcMain, net, shell} from "electron";
import {insertVersion} from "../storage/version"; import {insertVersion} from "../storage/version";
const fs = require("fs"); const fs = require("fs");
@ -162,4 +162,11 @@ export const initGitHubApi = () => {
} }
}); });
}); });
/**
* GitHub
*/
ipcMain.on("github.open", () => {
shell.openExternal("https://github.com/luckjiawei/frpc-desktop");
})
}; };

View File

@ -1,11 +1,12 @@
import { app, BrowserWindow, ipcMain, shell } from "electron"; import {app, BrowserWindow, ipcMain, Menu, MenuItem, MenuItemConstructorOptions, shell, Tray} from "electron";
import { release } from "node:os"; import {release} from "node:os";
import { join } from "node:path"; 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 } from "../api/frpc"; import {initFrpcApi, stopFrpcProcess} from "../api/frpc";
import { initLoggerApi } from "../api/logger"; import {initLoggerApi} from "../api/logger";
import {initFileApi} from "../api/file";
// The built directory structure // The built directory structure
// //
// ├─┬ dist-electron // ├─┬ dist-electron
@ -39,15 +40,17 @@ if (!app.requestSingleInstanceLock()) {
// process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true' // process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'
let win: BrowserWindow | null = null; let win: BrowserWindow | null = null;
let tray = null;
// Here, you can also use other preload // Here, you can also use other preload
const preload = join(__dirname, "../preload/index.js"); const preload = join(__dirname, "../preload/index.js");
const url = process.env.VITE_DEV_SERVER_URL; const url = process.env.VITE_DEV_SERVER_URL;
const indexHtml = join(process.env.DIST, "index.html"); const indexHtml = join(process.env.DIST, "index.html");
let isQuiting;
async function createWindow() { async function createWindow() {
win = new BrowserWindow({ win = new BrowserWindow({
title: "Main window", title: "Frpc Desktop",
icon: join(process.env.VITE_PUBLIC, "favicon.ico"), icon: join(process.env.VITE_PUBLIC, "logo/16x16.png"),
webPreferences: { webPreferences: {
preload, preload,
// Warning: Enable nodeIntegration and disable contextIsolation is not secure in production // Warning: Enable nodeIntegration and disable contextIsolation is not secure in production
@ -57,7 +60,6 @@ async function createWindow() {
contextIsolation: false contextIsolation: false
} }
}); });
if (process.env.VITE_DEV_SERVER_URL) { if (process.env.VITE_DEV_SERVER_URL) {
// electron-vite-vue#298 // electron-vite-vue#298
win.loadURL(url); win.loadURL(url);
@ -73,26 +75,81 @@ async function createWindow() {
}); });
// Make all links open with the browser, not with the application // Make all links open with the browser, not with the application
win.webContents.setWindowOpenHandler(({ url }) => { win.webContents.setWindowOpenHandler(({url}) => {
if (url.startsWith("https:")) shell.openExternal(url); if (url.startsWith("https:")) shell.openExternal(url);
return { action: "deny" }; return {action: "deny"};
}); });
// 隐藏菜单栏 // 隐藏菜单栏
const { Menu } = require("electron"); const {Menu} = require("electron");
Menu.setApplicationMenu(null); Menu.setApplicationMenu(null);
// hide menu for Mac // hide menu for Mac
if (process.platform !== "darwin") { // if (process.platform !== "darwin") {
// app.dock.hide();
// }
win.on('minimize', function (event) {
event.preventDefault();
win.hide();
});
win.on('close', function (event) {
if (!isQuiting) {
event.preventDefault();
win.hide();
if (process.platform === "darwin") {
app.dock.hide(); app.dock.hide();
} }
// win.webContents.on('will-navigate', (event, url) => { }) #344 }
return false;
});
} }
app.whenReady().then(createWindow); export const createTray = () => {
let menu: Array<(MenuItemConstructorOptions) | (MenuItem)> = [
{
label: '显示主窗口', click: function () {
win.show();
if (process.platform === "darwin") {
app.dock.show();
}
}
},
{
label: '退出',
click: () => {
isQuiting = true;
stopFrpcProcess(() => {
app.quit();
})
}
}
];
tray = new Tray(node_path.join(process.env.VITE_PUBLIC, "logo/16x16.png"))
tray.setToolTip('Frpc Desktop')
const contextMenu = Menu.buildFromTemplate(menu)
tray.setContextMenu(contextMenu)
// 托盘双击打开
tray.on('double-click', () => {
win.show();
})
}
app.whenReady().then(() => {
createWindow().then(r => {
createTray()
})
});
app.on("window-all-closed", () => { app.on("window-all-closed", () => {
win = null; win = null;
if (process.platform !== "darwin") app.quit(); if (process.platform !== "darwin") {
stopFrpcProcess(() => {
app.quit();
})
}
}); });
app.on("second-instance", () => { app.on("second-instance", () => {
@ -112,6 +169,10 @@ app.on("activate", () => {
} }
}); });
app.on('before-quit', () => {
isQuiting = true;
})
// New window example arg: new windows url // New window example arg: new windows url
ipcMain.handle("open-win", (_, arg) => { ipcMain.handle("open-win", (_, arg) => {
const childWindow = new BrowserWindow({ const childWindow = new BrowserWindow({
@ -125,12 +186,13 @@ ipcMain.handle("open-win", (_, arg) => {
if (process.env.VITE_DEV_SERVER_URL) { if (process.env.VITE_DEV_SERVER_URL) {
childWindow.loadURL(`${url}#${arg}`); childWindow.loadURL(`${url}#${arg}`);
} else { } else {
childWindow.loadFile(indexHtml, { hash: arg }); childWindow.loadFile(indexHtml, {hash: arg});
} }
}); });
ipcMain.on('open-url', (event, url) => { ipcMain.on('open-url', (event, url) => {
shell.openExternal(url).then(r => {}); shell.openExternal(url).then(r => {
});
}); });
initGitHubApi(); initGitHubApi();
@ -138,3 +200,4 @@ initConfigApi();
initProxyApi(); initProxyApi();
initFrpcApi(); initFrpcApi();
initLoggerApi(); initLoggerApi();
initFileApi();

View File

@ -13,6 +13,13 @@ export type Config = {
serverPort: number; serverPort: number;
authMethod: string; authMethod: string;
authToken: string; authToken: string;
logLevel: string;
logMaxDays: number;
tlsConfigEnable: boolean;
tlsConfigCertFile: string;
tlsConfigKeyFile: string;
tlsConfigTrustedCaFile: string;
tlsConfigServerName: string;
}; };
/** /**

View File

@ -1,6 +1,6 @@
{ {
"name": "Frpc-Desktop", "name": "Frpc-Desktop",
"version": "1.0.0", "version": "1.0.1",
"main": "dist-electron/main/index.js", "main": "dist-electron/main/index.js",
"description": "一个frpc桌面客户端", "description": "一个frpc桌面客户端",
"author": "刘嘉伟 <8473136@qq.com>", "author": "刘嘉伟 <8473136@qq.com>",

View File

@ -3,6 +3,7 @@ import { computed, defineComponent, onMounted, ref } from "vue";
import { Icon } from "@iconify/vue"; import { Icon } from "@iconify/vue";
import router from "@/router"; import router from "@/router";
import { RouteRecordRaw } from "vue-router"; import { RouteRecordRaw } from "vue-router";
import {ipcRenderer} from "electron";
defineComponent({ defineComponent({
name: "AppMain" name: "AppMain"
@ -27,6 +28,10 @@ const handleMenuChange = (route: RouteRecordRaw) => {
}); });
}; };
const handleOpenGitHub = () => {
ipcRenderer.send("github.open")
}
onMounted(() => { onMounted(() => {
routes.value = router.options.routes[0].children?.filter( routes.value = router.options.routes[0].children?.filter(
f => !f.meta?.hidden f => !f.meta?.hidden
@ -49,6 +54,12 @@ onMounted(() => {
> >
<Icon :icon="r?.meta?.icon as string" /> <Icon :icon="r?.meta?.icon as string" />
</li> </li>
<li
class="menu"
@click="handleOpenGitHub"
>
<Icon icon="mdi:github" />
</li>
</ul> </ul>
</div> </div>
</template> </template>

View File

@ -1,11 +1,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import { defineComponent, onMounted, onUnmounted, ref, reactive } from "vue"; import {defineComponent, onMounted, onUnmounted, reactive, ref} from "vue";
import { ipcRenderer } from "electron"; import {ipcRenderer} from "electron";
import { ElMessage, FormInstance, FormRules } from "element-plus"; import {ElMessage, FormInstance, FormRules} from "element-plus";
import Breadcrumb from "@/layout/compoenets/Breadcrumb.vue"; 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";
defineComponent({ defineComponent({
name: "Config" name: "Config"
@ -17,6 +17,14 @@ type Config = {
serverPort: number; serverPort: number;
authMethod: string; authMethod: string;
authToken: string; authToken: string;
logLevel: string;
logMaxDays: number;
tlsConfigEnable: boolean;
tlsConfigCertFile: string;
tlsConfigKeyFile: string;
tlsConfigTrustedCaFile: string;
tlsConfigServerName: string;
}; };
type Version = { type Version = {
@ -29,15 +37,22 @@ const formData = ref<Config>({
serverAddr: "", serverAddr: "",
serverPort: 7000, serverPort: 7000,
authMethod: "", authMethod: "",
authToken: "" authToken: "",
logLevel: "info",
logMaxDays: 3,
tlsConfigEnable: false,
tlsConfigCertFile: "",
tlsConfigKeyFile: "",
tlsConfigTrustedCaFile: "",
tlsConfigServerName: "",
}); });
const loading = ref(1); const loading = ref(1);
const rules = reactive<FormRules>({ const rules = reactive<FormRules>({
currentVersion: [{ required: true, message: "请选择版本", trigger: "blur" }], currentVersion: [{required: true, message: "请选择版本", trigger: "blur"}],
serverAddr: [ serverAddr: [
{ required: true, message: "请输入服务端地址", trigger: "blur" }, {required: true, message: "请输入服务端地址", trigger: "blur"},
{ {
pattern: /^[\w-]+(\.[\w-]+)+$/, pattern: /^[\w-]+(\.[\w-]+)+$/,
message: "请输入正确的服务端地址", message: "请输入正确的服务端地址",
@ -45,10 +60,17 @@ const rules = reactive<FormRules>({
} }
], ],
serverPort: [ serverPort: [
{ required: true, message: "请输入服务器端口", trigger: "blur" } {required: true, message: "请输入服务器端口", trigger: "blur"}
], ],
// authMethod: [{ required: true, message: "", trigger: "blur" }], // authMethod: [{ required: true, message: "", trigger: "blur" }],
authToken: [{ required: true, message: "请输入token值 ", trigger: "blur" }] authToken: [{required: true, message: "请输入token值 ", trigger: "blur"}],
logLevel: [{required: true, message: "请选择日志级别 ", trigger: "blur"}],
logMaxDays: [{required: true, message: "请输入日志保留天数 ", trigger: "blur"}],
tlsConfigEnable: [{required: true, message: "请选择TLS状态", trigger: "change"}],
tlsConfigCertFile: [{required: true, message: "请选择TLS证书文件", trigger: "change"}],
tlsConfigKeyFile: [{required: true, message: "请选择TLS密钥文件", trigger: "change"}],
tlsConfigTrustedCaFile: [{required: true, message: "请选择CA证书文件", trigger: "change"}],
tlsConfigServerName: [{required: true, message: "请输入TLS Server名称", trigger: "blur"}]
}); });
const versions = ref<Array<Version>>([]); const versions = ref<Array<Version>>([]);
@ -73,7 +95,7 @@ onMounted(() => {
ipcRenderer.send("config.getConfig"); ipcRenderer.send("config.getConfig");
handleLoadVersions(); handleLoadVersions();
ipcRenderer.on("Config.getConfig.hook", (event, args) => { ipcRenderer.on("Config.getConfig.hook", (event, args) => {
const { err, data } = args; const {err, data} = args;
if (!err) { if (!err) {
if (data) { if (data) {
formData.value = data; formData.value = data;
@ -90,13 +112,31 @@ onMounted(() => {
loading.value--; loading.value--;
}); });
ipcRenderer.on("Config.versions.hook", (event, args) => { ipcRenderer.on("Config.versions.hook", (event, args) => {
const { err, data } = args; const {err, data} = args;
if (!err) { if (!err) {
versions.value = data; versions.value = data;
} }
}); });
}); });
const handleSelectFile = (type: number, ext: string[]) => {
ipcRenderer.invoke("file.selectFile", ext).then(r => {
switch (type) {
case 1:
formData.value.tlsConfigCertFile = r[0]
break;
case 2:
formData.value.tlsConfigKeyFile = r[0]
break;
case 3:
formData.value.tlsConfigTrustedCaFile = r[0]
break;
}
console.log(r)
})
}
onUnmounted(() => { onUnmounted(() => {
ipcRenderer.removeAllListeners("Config.getConfig.hook"); ipcRenderer.removeAllListeners("Config.getConfig.hook");
ipcRenderer.removeAllListeners("Config.saveConfig.hook"); ipcRenderer.removeAllListeners("Config.saveConfig.hook");
@ -104,18 +144,18 @@ onUnmounted(() => {
}); });
</script> </script>
<template> <template>
<div> <div class="main">
<breadcrumb /> <breadcrumb/>
<div class="app-container-breadcrumb pr-2" v-loading="loading > 0">
<div <div
class="w-full bg-white p-4 rounded drop-shadow-lg" class="w-full bg-white p-4 rounded drop-shadow-lg"
v-loading="loading > 0"
> >
<el-form <el-form
:model="formData" :model="formData"
:rules="rules" :rules="rules"
label-position="right" label-position="right"
ref="formRef" ref="formRef"
label-width="120" label-width="140"
> >
<el-row :gutter="10"> <el-row :gutter="10">
<el-col :span="24"> <el-col :span="24">
@ -133,40 +173,52 @@ onUnmounted(() => {
></el-option> ></el-option>
</el-select> </el-select>
<div class="w-full flex justify-end"> <div class="w-full flex justify-end">
<el-button type="text" @click="handleLoadVersions"> <el-link type="primary" @click="handleLoadVersions">
<Icon class="mr-1" icon="material-symbols:refresh-rounded" /> <Icon class="mr-1" icon="material-symbols:refresh-rounded"/>
手动刷新 手动刷新
</el-button> </el-link>
<el-button <!-- <el-button type="text" @click="handleLoadVersions">-->
type="text" <!-- <Icon class="mr-1" icon="material-symbols:refresh-rounded"/>-->
@click="$router.replace({ name: 'Download' })" <!-- 手动刷新-->
> <!-- </el-button>-->
<Icon class="mr-1" icon="material-symbols:download-2" /> <el-link class="ml-2" type="primary" @click="$router.replace({ name: 'Download' })">
<Icon class="mr-1" icon="material-symbols:download-2"/>
点击这里去下载 点击这里去下载
</el-button> </el-link>
<!-- <el-button-->
<!-- type="text"-->
<!-- @click="$router.replace({ name: 'Download' })"-->
<!-- >-->
<!-- <Icon class="mr-1" icon="material-symbols:download-2"/>-->
<!-- 点击这里去下载-->
<!-- </el-button>-->
</div> </div>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
<el-form-item label="服务端地址:" prop="serverAddr"> <div class="h2">服务器配置</div>
</el-col>
<el-col :span="24">
<el-form-item label="服务器地址:" prop="serverAddr">
<el-input <el-input
v-model="formData.serverAddr" v-model="formData.serverAddr"
placeholder="127.0.0.1" placeholder="127.0.0.1"
></el-input> ></el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="12">
<el-form-item label="服务端口:" prop="serverPort"> <el-form-item label="服务端口:" prop="serverPort">
<el-input-number <el-input-number
placeholder="7000" placeholder="7000"
v-model="formData.serverPort" v-model="formData.serverPort"
:min="0" :min="0"
:max="65535" :max="65535"
controls-position="right" controls-position="right"
class="!w-full"
></el-input-number> ></el-input-number>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="12">
<el-form-item label="验证方式:" prop="authMethod"> <el-form-item label="验证方式:" prop="authMethod">
<el-select <el-select
v-model="formData.authMethod" v-model="formData.authMethod"
@ -186,10 +238,82 @@ onUnmounted(() => {
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24">
<div class="h2">TSL Config</div>
</el-col>
<el-col :span="24">
<el-form-item label="是否启用TSL" prop="tlsConfigEnable">
<el-switch active-text=""
inline-prompt
inactive-text="关"
v-model="formData.tlsConfigEnable"/>
</el-form-item>
</el-col>
<template v-if="formData.tlsConfigEnable">
<el-col :span="24">
<el-form-item label="TLS证书文件" prop="tlsConfigCertFile">
<el-input
class="button-input"
v-model="formData.tlsConfigCertFile"
placeholder="请选择TLS证书文件"
readonly
/>
<el-button class="ml-2" type="primary" @click="handleSelectFile(1, ['crt'])">选择</el-button>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="TLS密钥文件" prop="tlsConfigKeyFile">
<el-input
class="button-input"
v-model="formData.tlsConfigKeyFile"
placeholder="请选择TLS密钥文件"
readonly
/>
<el-button class="ml-2" type="primary" @click="handleSelectFile(2, ['key'])">选择</el-button>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="CA证书文件" prop="tlsConfigTrustedCaFile">
<el-input
class="button-input"
v-model="formData.tlsConfigTrustedCaFile"
placeholder="请选择CA证书文件"
readonly
/>
<el-button class="ml-2" type="primary" @click="handleSelectFile(3, ['crt'])">选择</el-button>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="TLS Server名称" prop="tlsConfigServerName">
<el-input
v-model="formData.tlsConfigServerName"
placeholder="请输入TLS Server名称"
/>
</el-form-item>
</el-col>
</template>
<el-col :span="24">
<div class="h2">日志配置</div>
</el-col>
<el-col :span="12">
<el-form-item class="!w-full" label="日志级别:" prop="logLevel">
<el-select v-model="formData.logLevel">
<el-option label="info" value="info"/>
<el-option label="debug" value="debug"/>
<el-option label="waring" value="waring"/>
<el-option label="error" value="error"/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="日志保留天数:" prop="logMaxDays">
<el-input-number class="!w-full" controls-position="right" v-model="formData.logMaxDays"/>
</el-form-item>
</el-col>
<el-col :span="24"> <el-col :span="24">
<el-form-item> <el-form-item>
<el-button plain type="primary" @click="handleSubmit"> <el-button plain type="primary" @click="handleSubmit">
<Icon class="mr-1" icon="material-symbols:save" /> <Icon class="mr-1" icon="material-symbols:save"/>
</el-button> </el-button>
</el-form-item> </el-form-item>
@ -198,6 +322,24 @@ onUnmounted(() => {
</el-form> </el-form>
</div> </div>
</div> </div>
</div>
</template> </template>
<style lang="scss" scoped></style> <style lang="scss" scoped>
.h2 {
color: #5A3DAA;
font-size: 16px;
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
font-weight: 700;
padding: 6px 10px 6px 15px;
border-left: 5px solid #5A3DAA;
border-radius: 4px;
background-color: #EEEBF6;
margin-bottom: 18px;
}
.button-input {
width: calc(100% - 68px);
}
</style>

View File

@ -1,9 +1,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import { defineComponent, onMounted, onUnmounted, ref } from "vue"; import {defineComponent, onMounted, onUnmounted, ref} from "vue";
import Breadcrumb from "@/layout/compoenets/Breadcrumb.vue"; import Breadcrumb from "@/layout/compoenets/Breadcrumb.vue";
import { ipcRenderer } from "electron"; import {ipcRenderer} from "electron";
import { Icon } from "@iconify/vue"; import {Icon} from "@iconify/vue";
import { ElMessageBox } from "element-plus"; import {ElMessageBox} from "element-plus";
import router from "@/router"; import router from "@/router";
defineComponent({ defineComponent({
@ -32,6 +32,7 @@ onMounted(() => {
setInterval(() => { setInterval(() => {
ipcRenderer.invoke("frpc.running").then(data => { ipcRenderer.invoke("frpc.running").then(data => {
running.value = data; running.value = data;
console.log('进程状态', data)
}); });
}, 300); }, 300);
@ -57,7 +58,7 @@ onUnmounted(() => {
<template> <template>
<div class="main"> <div class="main">
<breadcrumb /> <breadcrumb/>
<div class="app-container-breadcrumb"> <div class="app-container-breadcrumb">
<div <div
class="w-full h-full bg-white p-4 rounded drop-shadow-lg overflow-y-auto flex justify-center items-center" class="w-full h-full bg-white p-4 rounded drop-shadow-lg overflow-y-auto flex justify-center items-center"
@ -87,23 +88,28 @@ onUnmounted(() => {
<div <div
class="bg-white z-10 w-full h-full bg-white absolute rounded-full flex justify-center items-center" class="bg-white z-10 w-full h-full bg-white absolute rounded-full flex justify-center items-center"
> >
<Icon icon="material-symbols:rocket-launch-rounded" /> <Icon icon="material-symbols:rocket-launch-rounded"/>
</div> </div>
</div> </div>
<div class="flex justify-center items-center"> <div class="flex justify-center items-center">
<div class="pl-8 h-28 w-52 flex flex-col justify-between"> <div class="pl-8 h-28 w-52 flex flex-col justify-between">
<transition name="fade"> <transition name="fade">
<div class="font-bold text-2xl text-center"> <div class="font-bold text-2xl text-center">
<Icon v-if="running" class="text-[#7EC050] inline-block relative top-1" icon="material-symbols:check-circle-rounded" />
<Icon v-else class="text-[#E47470] inline-block relative top-1" icon="material-symbols:error" />
Frpc {{ running ? "已启动" : "已断开" }} Frpc {{ running ? "已启动" : "已断开" }}
</div> </div>
</transition> </transition>
<el-button <!-- <el-button-->
class="block" <!-- class="block"-->
type="text" <!-- type="text"-->
v-if="running" <!-- v-if="running"-->
@click="$router.replace({ name: 'Logger' })" <!-- @click="$router.replace({ name: 'Logger' })"-->
>查看日志 <!-- >查看日志-->
</el-button> <!-- </el-button>-->
<div class="w-full justify-center text-center">
<el-link v-if="running" type="primary" @click="$router.replace({ name: 'Logger' })">查看日志</el-link>
</div>
<div <div
class="w-full h-8 bg-[#563EA4] rounded flex justify-center items-center text-white font-bold cursor-pointer" class="w-full h-8 bg-[#563EA4] rounded flex justify-center items-center text-white font-bold cursor-pointer"
@click="handleButtonClick" @click="handleButtonClick"