配置导出

This commit is contained in:
刘嘉伟 2024-08-21 22:31:12 +08:00
parent 6e028d377a
commit 9fed5fc844
8 changed files with 213 additions and 86 deletions

View File

@ -1,52 +1,102 @@
import {app, ipcMain} from "electron"; import { app, dialog, ipcMain } from "electron";
import {getConfig, saveConfig} from "../storage/config"; import { getConfig, saveConfig } from "../storage/config";
import {listVersion} from "../storage/version"; import { listVersion } from "../storage/version";
import { generateConfig, genIniConfig, genTomlConfig } from "./frpc";
import { exec } from "child_process";
import { listProxy } from "../storage/proxy";
import path from "path";
import fs from "fs";
const log = require('electron-log'); const log = require("electron-log");
export const initConfigApi = () => { export const initConfigApi = () => {
ipcMain.on("config.saveConfig", async (event, args) => { ipcMain.on("config.saveConfig", async (event, args) => {
saveConfig(args, (err, numberOfUpdated, upsert) => { saveConfig(args, (err, numberOfUpdated, upsert) => {
if (!err) { if (!err) {
const start = args.systemSelfStart || false; const start = args.systemSelfStart || false;
log.info("开启自启状态", start) log.info("开启自启状态", start);
app.setLoginItemSettings({ app.setLoginItemSettings({
openAtLogin: start, //win openAtLogin: start, //win
openAsHidden: start, //macOs openAsHidden: start //macOs
}); });
}
event.reply("Config.saveConfig.hook", {
err: err,
numberOfUpdated: numberOfUpdated,
upsert: upsert
});
});
});
ipcMain.on("config.getConfig", async (event, args) => {
getConfig((err, doc) => {
event.reply("Config.getConfig.hook", {
err: err,
data: doc
});
});
});
ipcMain.on("config.versions", event => {
listVersion((err, doc) => {
event.reply("Config.versions.hook", {
err: err,
data: doc
});
});
});
ipcMain.on("config.hasConfig", event => {
getConfig((err, doc) => {
event.reply("Config.getConfig.hook", {
err: err,
data: doc
});
});
});
ipcMain.on("config.exportConfig", async (event, args) => {
const result = await dialog.showOpenDialog({
properties: ["openDirectory"]
});
const outputDirectory = result.filePaths[0];
log.info(`导出目录 ${outputDirectory} 类型:${args}`);
getConfig((err1, config) => {
if (!err1 && config) {
listProxy((err2, proxys) => {
if (!err2) {
let configContent = "";
if (args === "ini") {
configContent = genIniConfig(config, proxys);
} else if (args === "toml") {
configContent = genTomlConfig(config, proxys);
} }
event.reply("Config.saveConfig.hook", { const configPath = path.join(
err: err, outputDirectory,
numberOfUpdated: numberOfUpdated, `frpc-desktop.${args}`
upsert: upsert );
}); fs.writeFile(
}); configPath, // 配置文件目录
}); configContent, // 配置文件内容
{ flag: "w" },
ipcMain.on("config.getConfig", async (event, args) => { err => {
getConfig((err, doc) => { if (!err) {
event.reply("Config.getConfig.hook", { // callback(filename);
err: err, event.reply("config.exportConfig.hook", {
data: doc data: "导出错误",
}); err: err
}); });
}); }
}
ipcMain.on("config.versions", event => { );
listVersion((err, doc) => { event.reply("Config.exportConfig.hook", {
event.reply("Config.versions.hook", { data: {
err: err, configPath: configPath
data: doc }
});
});
});
ipcMain.on("config.hasConfig", event => {
getConfig((err, doc) => {
event.reply("Config.getConfig.hook", {
err: err,
data: doc
}); });
}
}); });
}
}); });
});
}; };

View File

@ -38,7 +38,7 @@ const getFrpcVersionWorkerPath = (
* @param config * @param config
* @param proxys * @param proxys
*/ */
const genTomlConfig = (config: FrpConfig, proxys: Proxy[]) => { export const genTomlConfig = (config: FrpConfig, proxys: Proxy[]) => {
const proxyToml = proxys.map(m => { const proxyToml = proxys.map(m => {
let toml = ` let toml = `
[[${m.type === "stcp" && m.stcpModel === "visitors" ? "visitors" : "proxies"}]] [[${m.type === "stcp" && m.stcpModel === "visitors" ? "visitors" : "proxies"}]]
@ -155,7 +155,7 @@ ${proxyToml.join("")}
* @param config * @param config
* @param proxys * @param proxys
*/ */
const genIniConfig = (config: FrpConfig, proxys: Proxy[]) => { export const genIniConfig = (config: FrpConfig, proxys: Proxy[]) => {
const proxyIni = proxys.map(m => { const proxyIni = proxys.map(m => {
let ini = ` let ini = `
[${m.name}] [${m.name}]

View File

@ -29,6 +29,9 @@ import ContentCopy from "@iconify-icons/material-symbols/content-copy";
import ContentPasteGo from "@iconify-icons/material-symbols/content-paste-go"; import ContentPasteGo from "@iconify-icons/material-symbols/content-paste-go";
import Edit from "@iconify-icons/material-symbols/edit"; import Edit from "@iconify-icons/material-symbols/edit";
import CheckBox from "@iconify-icons/material-symbols/check-box"; import CheckBox from "@iconify-icons/material-symbols/check-box";
import ExportNotesOutline from "@iconify-icons/material-symbols/export-notes-outline";
import uploadRounded from "@iconify-icons/material-symbols/upload-rounded";
import downloadRounded from "@iconify-icons/material-symbols/download-rounded";
addIcon("cloud", Cloud); addIcon("cloud", Cloud);
addIcon("rocket-launch-rounded", RocketLaunchRounded); addIcon("rocket-launch-rounded", RocketLaunchRounded);
@ -53,4 +56,8 @@ addIcon("content-copy", ContentCopy);
addIcon("content-paste-go", ContentPasteGo); addIcon("content-paste-go", ContentPasteGo);
addIcon("edit", Edit); addIcon("edit", Edit);
addIcon("check-box", CheckBox); addIcon("check-box", CheckBox);
addIcon("export", ExportNotesOutline);
addIcon("uploadRounded", uploadRounded);
addIcon("downloadRounded", downloadRounded);

View File

@ -1,9 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { Icon } from "@iconify/vue";
import { computed, defineComponent } from "vue"; import { computed, defineComponent } from "vue";
import router from "@/router"; import router from "@/router";
defineComponent({ defineComponent({
name: "Breadcrumb" name: "Breadcrumb"
}); });
@ -14,16 +12,17 @@ const currentRoute = computed(() => {
</script> </script>
<template> <template>
<div class="flex justify-between"> <div class="breadcrumb flex justify-between items-center">
<div class="breadcrumb animate__animated animate__lightSpeedInLeft"> <div
<IconifyIconOffline class="inline-block mr-2" :icon="currentRoute.meta['icon'] as string"/> class="flex items-center justify-center breadcrumb-left animate__animated animate__lightSpeedInLeft"
<!-- <Icon--> >
<!-- class="inline-block mr-2"--> <IconifyIconOffline
<!-- :icon="currentRoute.meta['icon'] as string"--> class="inline-block mr-2"
<!-- />--> :icon="currentRoute.meta['icon'] as string"
/>
<span>{{ currentRoute.meta["title"] }}</span> <span>{{ currentRoute.meta["title"] }}</span>
</div> </div>
<div class="right"> <div class="breadcrumb-right">
<slot></slot> <slot></slot>
</div> </div>
</div> </div>

View File

@ -21,24 +21,30 @@ $danger-color: #F56C6C;
} }
.breadcrumb { .breadcrumb {
color: $primary-color; margin-bottom: 15px;
font-size: 36px;
height: 50px;
svg { .breadcrumb-left {
vertical-align: top; color: $primary-color;
} font-size: 36px;
height: 30px;
span { svg {
vertical-align: top; vertical-align: top;
color: black; }
font-size: 18px;
font-weight: bold;
transform: translateY(5px);
display: inline-block;
span {
vertical-align: top;
color: black;
font-size: 18px;
font-weight: bold;
//transform: translateY(5px);
display: inline-block;
}
} }
} }
} }
.left-menu-container { .left-menu-container {
@ -57,6 +63,7 @@ $danger-color: #F56C6C;
font-size: 14px; font-size: 14px;
cursor: pointer; cursor: pointer;
} }
.version:hover { .version:hover {
animation: heartBeat 1s; animation: heartBeat 1s;
} }
@ -91,7 +98,6 @@ $danger-color: #F56C6C;
} }
} }
} }
@ -111,6 +117,10 @@ $danger-color: #F56C6C;
color: $primary-color; color: $primary-color;
} }
.bg-primary {
background: $primary-color;
}
.danger-text { .danger-text {
color: $danger-color !important; color: $danger-color !important;
} }

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 { Base64 } from "js-base64"; import { Base64 } from "js-base64";
import IconifyIconOffline from "@/components/IconifyIcon/src/iconifyIconOffline";
defineComponent({ defineComponent({
name: "Config" name: "Config"
@ -116,9 +117,12 @@ const protocol = ref("frp://");
const visibles = reactive({ const visibles = reactive({
copyServerConfig: false, copyServerConfig: false,
pasteServerConfig: false pasteServerConfig: false,
exportConfig: false
}); });
const exportConfigType = ref("toml");
const handleSubmit = useDebounceFn(() => { const handleSubmit = useDebounceFn(() => {
if (!formRef.value) return; if (!formRef.value) return;
formRef.value.validate(valid => { formRef.value.validate(valid => {
@ -195,6 +199,14 @@ onMounted(() => {
checkAndResetVersion(); checkAndResetVersion();
} }
}); });
ipcRenderer.on("Config.exportConfig.hook", (event, args) => {
const { err, data } = args;
console.log(err, data, "export");
if (!err) {
const { configPath } = data;
ElMessageBox.alert(`配置路径:${configPath}`, `🎉 导出成功`);
}
});
}); });
const handleSelectFile = (type: number, ext: string[]) => { const handleSelectFile = (type: number, ext: string[]) => {
@ -286,15 +298,34 @@ const handlePasteServerConfigBase64 = useDebounceFn(() => {
visibles.pasteServerConfig = false; visibles.pasteServerConfig = false;
}, 300); }, 300);
const handleShowExportDialog = () => {
visibles.exportConfig = true;
};
const handleExportConfig = useDebounceFn(() => {
ipcRenderer.send("config.exportConfig", exportConfigType.value);
visibles.exportConfig = false;
}, 300);
const handleImportConfig = () => {};
onUnmounted(() => { onUnmounted(() => {
ipcRenderer.removeAllListeners("Config.getConfig.hook"); ipcRenderer.removeAllListeners("Config.getConfig.hook");
ipcRenderer.removeAllListeners("Config.saveConfig.hook"); ipcRenderer.removeAllListeners("Config.saveConfig.hook");
ipcRenderer.removeAllListeners("Config.versions.hook"); ipcRenderer.removeAllListeners("Config.versions.hook");
ipcRenderer.removeAllListeners("Config.exportConfig.hook");
}); });
</script> </script>
<template> <template>
<div class="main"> <div class="main">
<breadcrumb /> <breadcrumb>
<el-button plain type="primary">
<IconifyIconOffline icon="uploadRounded" />
</el-button>
<el-button plain type="primary" @click="handleShowExportDialog">
<IconifyIconOffline icon="downloadRounded" />
</el-button>
</breadcrumb>
<div class="app-container-breadcrumb pr-2" v-loading="loading > 0"> <div class="app-container-breadcrumb pr-2" v-loading="loading > 0">
<div class="w-full bg-white p-4 rounded drop-shadow-lg"> <div class="w-full bg-white p-4 rounded drop-shadow-lg">
<el-form <el-form
@ -643,8 +674,8 @@ onUnmounted(() => {
class="ml-2" class="ml-2"
type="primary" type="primary"
@click="handleSelectFile(1, ['crt'])" @click="handleSelectFile(1, ['crt'])"
>选择</el-button >选择
> </el-button>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
@ -682,8 +713,8 @@ onUnmounted(() => {
class="ml-2" class="ml-2"
type="primary" type="primary"
@click="handleSelectFile(2, ['key'])" @click="handleSelectFile(2, ['key'])"
>选择</el-button >选择
> </el-button>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
@ -721,8 +752,8 @@ onUnmounted(() => {
class="ml-2" class="ml-2"
type="primary" type="primary"
@click="handleSelectFile(3, ['crt'])" @click="handleSelectFile(3, ['crt'])"
>选择</el-button >选择
> </el-button>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
@ -896,7 +927,7 @@ onUnmounted(() => {
</el-form> </el-form>
</div> </div>
</div> </div>
<!-- 链接导入服务器 -->
<el-dialog <el-dialog
v-model="visibles.copyServerConfig" v-model="visibles.copyServerConfig"
title="复制链接" title="复制链接"
@ -916,7 +947,7 @@ onUnmounted(() => {
:rows="8" :rows="8"
></el-input> ></el-input>
</el-dialog> </el-dialog>
<!-- 链接导出服务器-->
<el-dialog <el-dialog
v-model="visibles.pasteServerConfig" v-model="visibles.pasteServerConfig"
title="导入链接" title="导入链接"
@ -946,6 +977,39 @@ onUnmounted(() => {
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
<!-- 配置导出-->
<el-dialog
v-model="visibles.exportConfig"
title="导出配置"
width="500"
top="5%"
>
<el-alert
class="mb-4"
:title="`导出文件名为 frpc-desktop.${exportConfigType} 重复导出则覆盖`"
type="warning"
:closable="false"
/>
<el-form>
<el-form-item label="导出类型">
<el-radio-group v-model="exportConfigType">
<el-radio-button label="toml" value="toml" />
<el-radio-button label="ini" value="ini" />
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button plain type="primary" @click="handleExportConfig">
<IconifyIconOffline
class="cursor-pointer mr-2"
icon="downloadRounded"
/>
</el-button>
</div>
</template>
</el-dialog>
</div> </div>
</template> </template>

View File

@ -117,7 +117,7 @@ onUnmounted(() => {
<breadcrumb> <breadcrumb>
<div class="h-full flex items-center justify-center"> <div class="h-full flex items-center justify-center">
<span class="text-sm font-bold">下载源 </span> <span class="text-sm font-bold">下载源 </span>
<el-select class="w-24" v-model="currMirror"> <el-select class="w-40" v-model="currMirror">
<el-option <el-option
v-for="m in mirrors" v-for="m in mirrors"
:label="m.name" :label="m.name"

View File

@ -300,12 +300,9 @@ onUnmounted(() => {
<!-- <coming-soon />--> <!-- <coming-soon />-->
<div class="main"> <div class="main">
<breadcrumb> <breadcrumb>
<div <el-button class="mr-2" plain type="primary" @click="handleOpenInsert">
class="cursor-pointer h-[36px] w-[36px] bg-[#5f3bb0] rounded text-white flex justify-center items-center"
@click="handleOpenInsert"
>
<IconifyIconOffline icon="add" /> <IconifyIconOffline icon="add" />
</div> </el-button>
</breadcrumb> </breadcrumb>
<div class="app-container-breadcrumb pr-2" v-loading="loading.list > 0"> <div class="app-container-breadcrumb pr-2" v-loading="loading.list > 0">
<template v-if="proxys && proxys.length > 0"> <template v-if="proxys && proxys.length > 0">