From 7b2d2125495c111ce7c27554524a625f4cd68cb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=98=89=E4=BC=9F?= <8473136@qq.com> Date: Mon, 27 Nov 2023 15:03:25 +0800 Subject: [PATCH] =?UTF-8?q?:tada:=20=E9=A6=96=E6=AC=A1=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 30 ++ .prettierrc.js | 6 + LICENSE | 2 +- electron-builder.json5 | 46 +++ electron/api/config.ts | 42 +++ electron/api/frpc.ts | 183 ++++++++++ electron/api/github.ts | 121 +++++++ electron/api/logger.ts | 30 ++ electron/api/proxy.ts | 69 ++++ electron/electron-env.d.ts | 11 + electron/main/index.ts | 136 ++++++++ electron/main/index111.ts | 127 +++++++ electron/preload/index.ts | 92 +++++ electron/storage/config.ts | 37 ++ electron/storage/proxy.ts | 74 ++++ electron/storage/version.ts | 38 +++ index.html | 14 + package.json | 62 ++++ postcss.config.js | 8 + public/logo/1024x1024.png | Bin 0 -> 38069 bytes public/logo/128x128.png | Bin 0 -> 2171 bytes public/logo/16x16.png | Bin 0 -> 352 bytes public/logo/24x24.png | Bin 0 -> 488 bytes public/logo/256x256.png | Bin 0 -> 4801 bytes public/logo/32x32.png | Bin 0 -> 600 bytes public/logo/48x48.png | Bin 0 -> 819 bytes public/logo/512x512.png | Bin 0 -> 13186 bytes public/logo/64x64.png | Bin 0 -> 1062 bytes src/App.vue | 23 ++ src/assets/logo.png | Bin 0 -> 10430 bytes src/layout/compoenets/AppMain.vue | 22 ++ src/layout/compoenets/Breadcrumb.vue | 28 ++ src/layout/compoenets/LeftMenu.vue | 54 +++ src/layout/index.vue | 15 + src/main.ts | 13 + src/router/index.ts | 82 +++++ src/styles/element.scss | 9 + src/styles/index.scss | 21 ++ src/styles/layout.scss | 86 +++++ src/styles/reset.scss | 275 +++++++++++++++ src/styles/tailwind.scss | 3 + src/types/global.d.ts | 9 + src/types/shims-tsx.d.ts | 22 ++ src/types/shims-vue.d.ts | 10 + src/utils/clone.ts | 19 ++ src/views/comingSoon/index.vue | 15 + src/views/config/index.vue | 203 +++++++++++ src/views/download/index.vue | 116 +++++++ src/views/home/index.vue | 199 +++++++++++ src/views/logger/index.vue | 67 ++++ src/views/proxy/index.vue | 487 +++++++++++++++++++++++++++ tailwind.config.js | 20 ++ tsconfig.json | 49 +++ vite.config.ts | 105 ++++++ 54 files changed, 3079 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 .prettierrc.js create mode 100644 electron-builder.json5 create mode 100644 electron/api/config.ts create mode 100644 electron/api/frpc.ts create mode 100644 electron/api/github.ts create mode 100644 electron/api/logger.ts create mode 100644 electron/api/proxy.ts create mode 100644 electron/electron-env.d.ts create mode 100644 electron/main/index.ts create mode 100644 electron/main/index111.ts create mode 100644 electron/preload/index.ts create mode 100644 electron/storage/config.ts create mode 100644 electron/storage/proxy.ts create mode 100644 electron/storage/version.ts create mode 100644 index.html create mode 100644 package.json create mode 100644 postcss.config.js create mode 100644 public/logo/1024x1024.png create mode 100644 public/logo/128x128.png create mode 100644 public/logo/16x16.png create mode 100644 public/logo/24x24.png create mode 100644 public/logo/256x256.png create mode 100644 public/logo/32x32.png create mode 100644 public/logo/48x48.png create mode 100644 public/logo/512x512.png create mode 100644 public/logo/64x64.png create mode 100644 src/App.vue create mode 100644 src/assets/logo.png create mode 100644 src/layout/compoenets/AppMain.vue create mode 100644 src/layout/compoenets/Breadcrumb.vue create mode 100644 src/layout/compoenets/LeftMenu.vue create mode 100644 src/layout/index.vue create mode 100644 src/main.ts create mode 100644 src/router/index.ts create mode 100644 src/styles/element.scss create mode 100644 src/styles/index.scss create mode 100644 src/styles/layout.scss create mode 100644 src/styles/reset.scss create mode 100644 src/styles/tailwind.scss create mode 100644 src/types/global.d.ts create mode 100644 src/types/shims-tsx.d.ts create mode 100644 src/types/shims-vue.d.ts create mode 100644 src/utils/clone.ts create mode 100644 src/views/comingSoon/index.vue create mode 100644 src/views/config/index.vue create mode 100644 src/views/download/index.vue create mode 100644 src/views/home/index.vue create mode 100644 src/views/logger/index.vue create mode 100644 src/views/proxy/index.vue create mode 100644 tailwind.config.js create mode 100644 tsconfig.json create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f798fbc --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +dist-electron +release +*.local + +# Editor directories and files +.vscode/.debug.env +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# lockfile +package-lock.json +pnpm-lock.yaml +yarn.lock \ No newline at end of file diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..16bb32c --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,6 @@ +module.exports = { + bracketSpacing: true, + singleQuote: false, + arrowParens: "avoid", + trailingComma: "none" +}; diff --git a/LICENSE b/LICENSE index 1cbdade..8d3fda8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 8473136 +Copyright (c) 2023 刘嘉伟 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/electron-builder.json5 b/electron-builder.json5 new file mode 100644 index 0000000..5ba2076 --- /dev/null +++ b/electron-builder.json5 @@ -0,0 +1,46 @@ +/** + * @see https://www.electron.build/configuration/configuration + */ +{ + "$schema": "https://raw.githubusercontent.com/electron-userland/electron-builder/master/packages/app-builder-lib/scheme.json", + "appId": "priv.liujiawei.frpc.desktop", + "asar": true, + "productName": "Frpc-Desktop", + "directories": { + "output": "release/${version}" + }, + "icon": "./public/logo/512x512.png", + "files": [ + "dist", + "dist-electron" + ], + "mac": { + "target": [ + "dmg" + ], + "artifactName": "${productName}-Mac-${version}-Installer.${ext}" + }, + "win": { + "target": [ + { + "target": "nsis", + "arch": [ + "x64" + ] + } + ], + "artifactName": "${productName}-Windows-${version}-Setup.${ext}" + }, + "nsis": { + "oneClick": false, + "perMachine": false, + "allowToChangeInstallationDirectory": true, + "deleteAppDataOnUninstall": false + }, + "linux": { + "target": [ + "AppImage" + ], + "artifactName": "${productName}-Linux-${version}.${ext}" + }, +} diff --git a/electron/api/config.ts b/electron/api/config.ts new file mode 100644 index 0000000..44cb054 --- /dev/null +++ b/electron/api/config.ts @@ -0,0 +1,42 @@ +import { ipcMain } from "electron"; +import { getConfig, saveConfig } from "../storage/config"; +import { listVersion } from "../storage/version"; + +export const initConfigApi = () => { + ipcMain.on("config.saveConfig", async (event, args) => { + saveConfig(args, (err, numberOfUpdated, upsert) => { + 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 + }); + }); + }); +}; diff --git a/electron/api/frpc.ts b/electron/api/frpc.ts new file mode 100644 index 0000000..95c547f --- /dev/null +++ b/electron/api/frpc.ts @@ -0,0 +1,183 @@ +import { app, ipcMain } from "electron"; +import { Config, getConfig } from "../storage/config"; +import { listProxy } from "../storage/proxy"; +import { getVersionById } from "../storage/version"; + +const fs = require("fs"); +const path = require("path"); + +const { exec, spawn } = require("child_process"); +export let frpcProcess = null; + +const runningCmd = { + commandPath: null, + configPath: null +}; + +// const getFrpc = (config: Config) => { +// getVersionById(config.currentVersion, (err, document) => { +// if (!err) { +// } +// }); +// }; + +/** + * 获取选择版本的工作目录 + * @param versionId 版本ID + * @param callback + */ +const getFrpcVersionWorkerPath = ( + versionId: string, + callback: (workerPath: string) => void +) => { + getVersionById(versionId, (err2, version) => { + if (!err2) { + callback(version["frpcVersionPath"]); + } + }); +}; + +/** + * 生成配置文件 + */ +export const generateConfig = ( + config: Config, + callback: (configPath: string) => void +) => { + listProxy((err3, proxys) => { + if (!err3) { + const proxyToml = proxys.map(m => { + let toml = ` +[[proxies]] +name = "${m.name}" +type = "${m.type}" +localIP = "${m.localIp}" +localPort = ${m.localPort} +`; + switch (m.type) { + case "tcp": + toml += `remotePort = ${m.remotePort}`; + break; + case "http": + case "https": + toml += `customDomains=[${m.customDomains.map(m => `"${m}"`)}]`; + break; + default: + break; + } + + return toml; + }); + let toml = ` +serverAddr = "${config.serverAddr}" +serverPort = ${config.serverPort} +auth.method = "${config.authMethod}" +auth.token = "${config.authToken}" +log.to = "frpc.log" +log.level = "debug" +log.maxDays = 3 +webServer.addr = "127.0.0.1" +webServer.port = 57400 + +${proxyToml} + `; + + // const configPath = path.join("frp.toml"); + const filename = "frp.toml"; + fs.writeFile( + path.join(app.getPath("userData"), filename), // 配置文件目录 + toml, // 配置文件内容 + { flag: "w" }, + err => { + if (!err) { + callback(filename); + } + } + ); + } + }); +}; + +/** + * 启动frpc子进程 + * @param cwd + * @param commandPath + * @param configPath + */ +const startFrpcProcess = (commandPath: string, configPath: string) => { + const command = `${commandPath} -c ${configPath}`; + frpcProcess = spawn(command, { + cwd: app.getPath("userData"), + shell: true + }); + runningCmd.commandPath = commandPath; + runningCmd.configPath = configPath; + frpcProcess.stdout.on("data", data => { + console.log(`命令输出: ${data}`); + }); + frpcProcess.stdout.on("error", data => { + console.log(`执行错误: ${data}`); + frpcProcess.kill("SIGINT"); + }); +}; + +export const reloadFrpcProcess = () => { + if (frpcProcess && !frpcProcess.killed) { + getConfig((err1, config) => { + if (!err1) { + if (config) { + generateConfig(config, configPath => { + const command = `${runningCmd.commandPath} reload -c ${configPath}`; + console.log("重启", command); + exec(command, { + cwd: app.getPath("userData"), + shell: true + }); + }); + } + } + }); + } +}; + +export const initFrpcApi = () => { + ipcMain.handle("frpc.running", async (event, args) => { + if (!frpcProcess) { + return false; + } else { + return !frpcProcess.killed; + } + }); + + ipcMain.on("frpc.start", async (event, args) => { + getConfig((err1, config) => { + if (!err1) { + if (config) { + getFrpcVersionWorkerPath( + config.currentVersion, + (frpcVersionPath: string) => { + generateConfig(config, configPath => { + startFrpcProcess( + path.join(frpcVersionPath, "frpc"), + configPath + ); + }); + } + ); + } else { + event.reply( + "Home.frpc.start.error.hook", + "请先前往设置页面,修改配置后再启动" + ); + } + } + }); + }); + + ipcMain.on("frpc.stop", () => { + if (frpcProcess && !frpcProcess.killed) { + console.log("关闭"); + frpcProcess.kill(); + } + }); +}; diff --git a/electron/api/github.ts b/electron/api/github.ts new file mode 100644 index 0000000..fe896ae --- /dev/null +++ b/electron/api/github.ts @@ -0,0 +1,121 @@ +import { app, BrowserWindow, ipcMain, net } from "electron"; +import { insertVersion } from "../storage/version"; + +const fs = require("fs"); +const path = require("path"); +const zlib = require("zlib"); +const { download } = require("electron-dl"); + +const unTarGZ = tarGzPath => { + const tar = require("tar"); + const targetPath = path.resolve(path.join(app.getPath("userData"), "frp")); + const unzip = zlib.createGunzip(); + const readStream = fs.createReadStream(tarGzPath); + if (!fs.existsSync(unzip)) { + fs.mkdirSync(targetPath, { recursive: true }); + } + readStream.pipe(unzip).pipe( + tar.extract({ + cwd: targetPath, + filter: filePath => path.basename(filePath) === "frpc" + }) + ); + return path.join("frp", path.basename(tarGzPath, ".tar.gz")); + // .on("finish", () => { + // console.log("解压完成!"); + // }); +}; +export const initGitHubApi = () => { + // 版本 + let versions = []; + + const getVersion = versionId => { + return versions.find(f => f.id === versionId); + }; + + const getAdaptiveAsset = versionId => { + const version = getVersion(versionId); + const arch = process.arch; + const platform = process.platform; + const { assets } = version; + const asset = assets.find( + f => f.name.indexOf(platform) != -1 && f.name.indexOf(arch) != -1 + ); + return asset; + }; + + /** + * 获取github上的frp所有版本 + */ + ipcMain.on("github.getFrpVersions", async event => { + const request = net.request({ + method: "get", + url: "https://api.github.com/repos/fatedier/frp/releases" + }); + request.on("response", response => { + let responseData: Buffer = Buffer.alloc(0); + response.on("data", (data: Buffer) => { + responseData = Buffer.concat([responseData, data]); + }); + response.on("end", () => { + versions = JSON.parse(responseData.toString()); + // const borderContent: Electron.WebContents = + // BrowserWindow.getFocusedWindow().webContents; + const downloadPath = path.join(app.getPath("userData"), "download"); + const returnVersionsData = versions + .filter(f => getAdaptiveAsset(f.id)) + .map(m => { + const asset = getAdaptiveAsset(m.id); + if (asset) { + // const absPath = `${downloadPath}/${asset.id}_${asset.name}`; + const absPath = `${downloadPath}/${asset.name}`; + m.download_completed = fs.existsSync(absPath); + } else { + console.log("buzhicih"); + } + // m.download_completed = fs.existsSync( + // `${downloadPath}/${asset.id}_${asset.name}` + // ); + return m; + }); + event.reply("Download.frpVersionHook", returnVersionsData); + }); + }); + request.end(); + }); + + /** + * 下载请求 + */ + ipcMain.on("github.download", async (event, args) => { + const version = getVersion(args); + const asset = getAdaptiveAsset(args); + const { browser_download_url } = asset; + // 数据目录 + await download(BrowserWindow.getFocusedWindow(), browser_download_url, { + filename: `${asset.name}`, + directory: path.join(app.getPath("userData"), "download"), + onProgress: progress => { + event.reply("Download.frpVersionDownloadOnProgress", { + id: args, + progress: progress + }); + }, + onCompleted: () => { + const frpcVersionPath = unTarGZ( + path.join( + path.join(app.getPath("userData"), "download"), + `${asset.name}` + ) + ); + version["frpcVersionPath"] = frpcVersionPath; + insertVersion(version, (err, document) => { + if (!err) { + event.reply("Download.frpVersionDownloadOnCompleted", args); + version.download_completed = true; + } + }); + } + }); + }); +}; diff --git a/electron/api/logger.ts b/electron/api/logger.ts new file mode 100644 index 0000000..f76f024 --- /dev/null +++ b/electron/api/logger.ts @@ -0,0 +1,30 @@ +import { app, ipcMain } from "electron"; + +const fs = require("fs"); +const path = require("path"); +export const initLoggerApi = () => { + const logPath = path.join(app.getPath("userData"), "frpc.log"); + + const readLogger = (callback: (content: string) => void) => { + fs.readFile(logPath, "utf-8", (error, data) => { + if (!error) { + callback(data); + } + }); + }; + + ipcMain.on("logger.getLog", async (event, args) => { + readLogger(content => { + event.reply("Logger.getLog.hook", content); + }); + }); + ipcMain.on("logger.update", (event, args) => { + fs.watch(logPath, (eventType, filename) => { + if (eventType === "change") { + readLogger(content => { + event.reply("Logger.update.hook", content); + }); + } + }); + }); +}; diff --git a/electron/api/proxy.ts b/electron/api/proxy.ts new file mode 100644 index 0000000..2ded76c --- /dev/null +++ b/electron/api/proxy.ts @@ -0,0 +1,69 @@ +import { ipcMain } from "electron"; +import { + deleteProxyById, + getProxyById, + insertProxy, + listProxy, + updateProxyById +} from "../storage/proxy"; +import { reloadFrpcProcess } from "./frpc"; + + + +export const initProxyApi = () => { + ipcMain.on("proxy.getProxys", async (event, args) => { + listProxy((err, documents) => { + event.reply("Proxy.getProxys.hook", { + err: err, + data: documents + }); + }); + }); + + ipcMain.on("proxy.insertProxy", async (event, args) => { + delete args["_id"]; + insertProxy(args, (err, documents) => { + if (!err) { + reloadFrpcProcess() + } + event.reply("Proxy.insertProxy.hook", { + err: err, + data: documents + }); + }); + }); + + ipcMain.on("proxy.deleteProxyById", async (event, args) => { + deleteProxyById(args, (err, documents) => { + if (!err) { + reloadFrpcProcess() + } + event.reply("Proxy.deleteProxyById.hook", { + err: err, + data: documents + }); + }); + }); + + ipcMain.on("proxy.getProxyById", async (event, args) => { + getProxyById(args, (err, documents) => { + event.reply("Proxy.getProxyById.hook", { + err: err, + data: documents + }); + }); + }); + + ipcMain.on("proxy.updateProxy", async (event, args) => { + if (!args._id) return; + updateProxyById(args, (err, documents) => { + if (!err) { + reloadFrpcProcess() + } + event.reply("Proxy.updateProxy.hook", { + err: err, + data: documents + }); + }); + }); +}; diff --git a/electron/electron-env.d.ts b/electron/electron-env.d.ts new file mode 100644 index 0000000..b4c4211 --- /dev/null +++ b/electron/electron-env.d.ts @@ -0,0 +1,11 @@ +/// + +declare namespace NodeJS { + interface ProcessEnv { + VSCODE_DEBUG?: 'true' + DIST_ELECTRON: string + DIST: string + /** /dist/ or /public/ */ + VITE_PUBLIC: string + } +} diff --git a/electron/main/index.ts b/electron/main/index.ts new file mode 100644 index 0000000..a0f03cc --- /dev/null +++ b/electron/main/index.ts @@ -0,0 +1,136 @@ +import { app, BrowserWindow, ipcMain, shell } from "electron"; +import { release } from "node:os"; +import { join } from "node:path"; +import { initGitHubApi } from "../api/github"; +import { initConfigApi } from "../api/config"; +import { initProxyApi } from "../api/proxy"; +import { initFrpcApi } from "../api/frpc"; +import { initLoggerApi } from "../api/logger"; +// The built directory structure +// +// ├─┬ dist-electron +// │ ├─┬ main +// │ │ └── index.js > Electron-Main +// │ └─┬ preload +// │ └── index.js > Preload-Scripts +// ├─┬ dist +// │ └── index.html > Electron-Renderer +// +process.env.DIST_ELECTRON = join(__dirname, ".."); +process.env.DIST = join(process.env.DIST_ELECTRON, "../dist"); +process.env.VITE_PUBLIC = process.env.VITE_DEV_SERVER_URL + ? join(process.env.DIST_ELECTRON, "../public") + : process.env.DIST; + +// Disable GPU Acceleration for Windows 7 +if (release().startsWith("6.1")) app.disableHardwareAcceleration(); + +// Set application name for Windows 10+ notifications +if (process.platform === "win32") app.setAppUserModelId(app.getName()); + +if (!app.requestSingleInstanceLock()) { + app.quit(); + process.exit(0); +} + +// Remove electron security warnings +// This warning only shows in development mode +// Read more on https://www.electronjs.org/docs/latest/tutorial/security +// process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true' + +let win: BrowserWindow | null = null; +// Here, you can also use other preload +const preload = join(__dirname, "../preload/index.js"); +const url = process.env.VITE_DEV_SERVER_URL; +const indexHtml = join(process.env.DIST, "index.html"); + +async function createWindow() { + win = new BrowserWindow({ + title: "Main window", + icon: join(process.env.VITE_PUBLIC, "favicon.ico"), + webPreferences: { + preload, + // Warning: Enable nodeIntegration and disable contextIsolation is not secure in production + // Consider using contextBridge.exposeInMainWorld + // Read more on https://www.electronjs.org/docs/latest/tutorial/context-isolation + nodeIntegration: true, + contextIsolation: false + } + }); + + if (process.env.VITE_DEV_SERVER_URL) { + // electron-vite-vue#298 + win.loadURL(url); + // Open devTool if the app is not packaged + win.webContents.openDevTools(); + } else { + win.loadFile(indexHtml); + } + + // Test actively push message to the Electron-Renderer + win.webContents.on("did-finish-load", () => { + win?.webContents.send("main-process-message", new Date().toLocaleString()); + }); + + // Make all links open with the browser, not with the application + win.webContents.setWindowOpenHandler(({ url }) => { + if (url.startsWith("https:")) shell.openExternal(url); + return { action: "deny" }; + }); + + // 隐藏菜单栏 + const { Menu } = require("electron"); + Menu.setApplicationMenu(null); + // hide menu for Mac + if (process.platform !== "darwin") { + app.dock.hide(); + } + // win.webContents.on('will-navigate', (event, url) => { }) #344 +} + +app.whenReady().then(createWindow); + +app.on("window-all-closed", () => { + win = null; + if (process.platform !== "darwin") app.quit(); +}); + +app.on("second-instance", () => { + if (win) { + // Focus on the main window if the user tried to open another + if (win.isMinimized()) win.restore(); + win.focus(); + } +}); + +app.on("activate", () => { + const allWindows = BrowserWindow.getAllWindows(); + if (allWindows.length) { + allWindows[0].focus(); + } else { + createWindow(); + } +}); + +// New window example arg: new windows url +ipcMain.handle("open-win", (_, arg) => { + const childWindow = new BrowserWindow({ + webPreferences: { + preload, + nodeIntegration: true, + contextIsolation: false + } + }); + + if (process.env.VITE_DEV_SERVER_URL) { + childWindow.loadURL(`${url}#${arg}`); + } else { + childWindow.loadFile(indexHtml, { hash: arg }); + } +}); + +initGitHubApi(); +initConfigApi(); +initProxyApi(); +initFrpcApi(); +initLoggerApi(); diff --git a/electron/main/index111.ts b/electron/main/index111.ts new file mode 100644 index 0000000..71dd9b7 --- /dev/null +++ b/electron/main/index111.ts @@ -0,0 +1,127 @@ +import { app, BrowserWindow, ipcMain, shell } from "electron"; +import { release } from "node:os"; +import { join } from "node:path"; +import { initGitHubApi } from "../api/github"; +import { initConfigApi } from "../api/config"; +import { initProxyApi } from "../api/proxy"; +import { initFrpcApi } from "../api/frpc"; +import { initLoggerApi } from "../api/logger"; + +// The built directory structure +// +// ├─┬ dist-electron +// │ ├─┬ main +// │ │ └── index.js > Electron-Main +// │ └─┬ preload +// │ └── index.js > Preload-Scripts +// ├─┬ dist +// │ └── index.html > Electron-Renderer +// +process.env.DIST_ELECTRON = join(__dirname, ".."); +process.env.DIST = join(process.env.DIST_ELECTRON, "../dist"); +process.env.VITE_PUBLIC = process.env.VITE_DEV_SERVER_URL + ? join(process.env.DIST_ELECTRON, "../public") + : process.env.DIST; + +// Disable GPU Acceleration for Windows 7 +if (release().startsWith("6.1")) app.disableHardwareAcceleration(); + +// Set application name for Windows 10+ notifications +if (process.platform === "win32") app.setAppUserModelId(app.getName()); + +if (!app.requestSingleInstanceLock()) { + app.quit(); + process.exit(0); +} + +// Remove electron security warnings +// This warning only shows in development mode +// Read more on https://www.electronjs.org/docs/latest/tutorial/security +// process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true' + +let win: BrowserWindow | null = null; +// Here, you can also use other preload +const preload = join(__dirname, '../preload/index.js') +const url = process.env.VITE_DEV_SERVER_URL; +const indexHtml = join(process.env.DIST, "index.html"); + +async function createWindow() { + win = new BrowserWindow({ + width: 800, + height: 600, + resizable: false, + title: "Frpc Desktop", + icon: join(process.env.VITE_PUBLIC, "favicon.ico"), + webPreferences: { + preload, + // Warning: Enable nodeIntegration and disable contextIsolation is not secure in production + // Consider using contextBridge.exposeInMainWorld + // Read more on https://www.electronjs.org/docs/latest/tutorial/context-isolation + nodeIntegration: true, + contextIsolation: false + } + }); + + if (process.env.VITE_DEV_SERVER_URL) { + // electron-vite-vue#298 + win.loadURL(url); + // Open devTool if the app is not packaged + win.webContents.openDevTools(); + } else { + win.loadFile(indexHtml); + } + + // Make all links open with the browser, not with the application + win.webContents.setWindowOpenHandler(({ url }) => { + if (url.startsWith("https:")) shell.openExternal(url); + return { action: "deny" }; + }); + // win.webContents.on('will-navigate', (event, url) => { }) #344 +} + +app.whenReady().then(createWindow); + +app.on("window-all-closed", () => { + win = null; + if (process.platform !== "darwin") app.quit(); +}); + +app.on("second-instance", () => { + if (win) { + // Focus on the main window if the user tried to open another + if (win.isMinimized()) win.restore(); + win.focus(); + } +}); + +app.on("activate", () => { + const allWindows = BrowserWindow.getAllWindows(); + if (allWindows.length) { + allWindows[0].focus(); + } else { + createWindow(); + } +}); + +// New window example arg: new windows url +ipcMain.handle('open-win', (_, arg) => { + const childWindow = new BrowserWindow({ + webPreferences: { + preload, + nodeIntegration: true, + contextIsolation: false, + }, + }) + + if (process.env.VITE_DEV_SERVER_URL) { + childWindow.loadURL(`${url}#${arg}`) + } else { + childWindow.loadFile(indexHtml, { hash: arg }) + } +}) + +initGitHubApi(); +initConfigApi(); +initProxyApi(); +initFrpcApi(); +initLoggerApi(); diff --git a/electron/preload/index.ts b/electron/preload/index.ts new file mode 100644 index 0000000..ebf1276 --- /dev/null +++ b/electron/preload/index.ts @@ -0,0 +1,92 @@ +function domReady(condition: DocumentReadyState[] = ['complete', 'interactive']) { + return new Promise((resolve) => { + if (condition.includes(document.readyState)) { + resolve(true) + } else { + document.addEventListener('readystatechange', () => { + if (condition.includes(document.readyState)) { + resolve(true) + } + }) + } + }) +} + +const safeDOM = { + append(parent: HTMLElement, child: HTMLElement) { + if (!Array.from(parent.children).find(e => e === child)) { + return parent.appendChild(child) + } + }, + remove(parent: HTMLElement, child: HTMLElement) { + if (Array.from(parent.children).find(e => e === child)) { + return parent.removeChild(child) + } + }, +} + +/** + * https://tobiasahlin.com/spinkit + * https://connoratherton.com/loaders + * https://projects.lukehaas.me/css-loaders + * https://matejkustec.github.io/SpinThatShit + */ +function useLoading() { + const className = `loaders-css__square-spin` + const styleContent = ` +@keyframes square-spin { + 25% { transform: perspective(100px) rotateX(180deg) rotateY(0); } + 50% { transform: perspective(100px) rotateX(180deg) rotateY(180deg); } + 75% { transform: perspective(100px) rotateX(0) rotateY(180deg); } + 100% { transform: perspective(100px) rotateX(0) rotateY(0); } +} +.${className} > div { + animation-fill-mode: both; + width: 50px; + height: 50px; + background: #fff; + animation: square-spin 3s 0s cubic-bezier(0.09, 0.57, 0.49, 0.9) infinite; +} +.app-loading-wrap { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + display: flex; + align-items: center; + justify-content: center; + background: #282c34; + z-index: 9; +} + ` + const oStyle = document.createElement('style') + const oDiv = document.createElement('div') + + oStyle.id = 'app-loading-style' + oStyle.innerHTML = styleContent + oDiv.className = 'app-loading-wrap' + oDiv.innerHTML = `
` + + return { + appendLoading() { + safeDOM.append(document.head, oStyle) + safeDOM.append(document.body, oDiv) + }, + removeLoading() { + safeDOM.remove(document.head, oStyle) + safeDOM.remove(document.body, oDiv) + }, + } +} + +// ---------------------------------------------------------------------- + +const { appendLoading, removeLoading } = useLoading() +domReady().then(appendLoading) + +window.onmessage = (ev) => { + ev.data.payload === 'removeLoading' && removeLoading() +} + +setTimeout(removeLoading, 4999) diff --git a/electron/storage/config.ts b/electron/storage/config.ts new file mode 100644 index 0000000..625e427 --- /dev/null +++ b/electron/storage/config.ts @@ -0,0 +1,37 @@ +import Datastore from "nedb"; +import path from "path"; +import { app } from "electron"; + +const configDB = new Datastore({ + autoload: true, + filename: path.join(app.getPath("userData"), "config.db") +}); + +export type Config = { + currentVersion: any; + serverAddr: string; + serverPort: number; + authMethod: string; + authToken: string; +}; + +/** + * 保存 + */ +export const saveConfig = ( + document: Config, + cb?: (err: Error | null, numberOfUpdated: number, upsert: boolean) => void +) => { + document["_id"] = "1"; + configDB.update({ _id: "1" }, document, { upsert: true }, cb); +}; + +/** + * 查找 + * @param cb + */ +export const getConfig = ( + cb: (err: Error | null, document: Config) => void +) => { + configDB.findOne({ _id: "1" }, cb); +}; diff --git a/electron/storage/proxy.ts b/electron/storage/proxy.ts new file mode 100644 index 0000000..4b85855 --- /dev/null +++ b/electron/storage/proxy.ts @@ -0,0 +1,74 @@ +import Datastore from "nedb"; +import path from "path"; +import { app } from "electron"; + +const proxyDB = new Datastore({ + autoload: true, + filename: path.join(app.getPath("userData"), "proxy.db") +}); + +export type Proxy = { + _id: string; + name: string; + type: string; + localIp: string; + localPort: number; + remotePort: number; + customDomains: string[]; +}; +/** + * 新增代理 + * @param proxy + * @param cb + */ +export const insertProxy = ( + proxy: Proxy, + cb?: (err: Error | null, document: Proxy) => void +) => { + console.log("新增", proxy); + proxyDB.insert(proxy, cb); +}; + +/** + * 删除代理 + * @param _id + * @param cb + */ +export const deleteProxyById = ( + _id: string, + cb?: (err: Error | null, n: number) => void +) => { + proxyDB.remove({ _id: _id }, cb); +}; + +/** + * 修改代理 + */ +export const updateProxyById = ( + proxy: Proxy, + cb?: (err: Error | null, numberOfUpdated: number, upsert: boolean) => void +) => { + proxyDB.update({ _id: proxy._id }, proxy, {}, cb); +}; + +/** + * 查找 + * @param cb + */ +export const listProxy = ( + callback: (err: Error | null, documents: Proxy[]) => void +) => { + proxyDB.find({}, callback); +}; + +/** + * 根据id查询 + * @param id + * @param callback + */ +export const getProxyById = ( + id: string, + callback: (err: Error | null, document: Proxy) => void +) => { + proxyDB.findOne({ _id: id }, callback); +}; diff --git a/electron/storage/version.ts b/electron/storage/version.ts new file mode 100644 index 0000000..ce9a80c --- /dev/null +++ b/electron/storage/version.ts @@ -0,0 +1,38 @@ +import Datastore from "nedb"; +import path from "path"; +import { Proxy } from "./proxy"; +import { app } from "electron"; + +const versionDB = new Datastore({ + autoload: true, + filename: path.join(app.getPath("userData"), "version.db") +}); + +/** + * 新增代理 + * @param proxy + * @param cb + */ +export const insertVersion = ( + version: any, + cb?: (err: Error | null, document: any) => void +) => { + versionDB.insert(version, cb); +}; + +/** + * 查找 + * @param cb + */ +export const listVersion = ( + callback: (err: Error | null, documents: any[]) => void +) => { + versionDB.find({}, callback); +}; + +export const getVersionById = ( + id: string, + callback: (err: Error | null, document: any) => void +) => { + versionDB.findOne({ id: id }, callback); +}; diff --git a/index.html b/index.html new file mode 100644 index 0000000..d1364fd --- /dev/null +++ b/index.html @@ -0,0 +1,14 @@ + + + + + + + + Frpc Desktop + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..71d4896 --- /dev/null +++ b/package.json @@ -0,0 +1,62 @@ +{ + "name": "Frpc-Desktop", + "version": "1.0.0", + "main": "dist-electron/main/index.js", + "description": "一个frpc桌面客户端", + "author": "刘嘉伟 <8473136@qq.com>", + "license": "MIT", + "private": true, + "keywords": [ + "electron", + "rollup", + "vite", + "vue3", + "vue" + ], + "debug": { + "env": { + "VITE_DEV_SERVER_URL": "http://127.0.0.1:3344/" + } + }, + "scripts": { + "dev": "vite", + "build": "vue-tsc --noEmit && vite build && electron-builder", + "build:win": "vue-tsc --noEmit && vite build && electron-builder -w", + "preview": "vite preview", + "electron:generate-icons": "electron-icon-builder --input=./public/logo.png --output=build --flatten" + }, + "devDependencies": { + "@iconify/vue": "^4.1.1", + "@types/nedb": "^1.8.16", + "@vitejs/plugin-vue": "^4.3.3", + "@vue/eslint-config-prettier": "^7.1.0", + "@vueuse/core": "^9.13.0", + "autoprefixer": "^10.4.15", + "cssnano": "^6.0.1", + "electron": "^26.0.0", + "electron-builder": "^24.6.3", + "element-plus": "^2.4.2", + "eslint-plugin-prettier": "^4.2.1", + "moment": "^2.29.4", + "nedb": "^1.8.0", + "node-cmd": "^5.0.0", + "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", + "vite": "^4.4.9", + "vite-plugin-electron": "^0.15.3", + "vite-plugin-electron-renderer": "^0.14.5", + "vue-tsc": "^1.8.8", + "vue-types": "^5.1.1", + "electron-icon-builder": "^2.0.1", + "vue": "^3.3.4", + "vue-router": "^4.2.4" + }, + "dependencies": { + "electron-dl": "^3.5.1", + "tar": "^6.2.0" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..20d42d8 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,8 @@ +module.exports = { + plugins: { + "postcss-import": {}, + tailwindcss: {}, + autoprefixer: {}, + ...(process.env.NODE_ENV === "production" ? { cssnano: {} } : {}) + } +}; diff --git a/public/logo/1024x1024.png b/public/logo/1024x1024.png new file mode 100644 index 0000000000000000000000000000000000000000..f60b50619c8c467dfa070c57d343b02150084933 GIT binary patch literal 38069 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

literal 0
HcmV?d00001

diff --git a/public/logo/16x16.png b/public/logo/16x16.png
new file mode 100644
index 0000000000000000000000000000000000000000..0941120e5140e7fe578d363f18b46344e4e650e5
GIT binary patch
literal 352
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

literal 0
HcmV?d00001

diff --git a/public/logo/32x32.png b/public/logo/32x32.png
new file mode 100644
index 0000000000000000000000000000000000000000..3500143704d51a8d9c1ddcd29aff9f4586b7d940
GIT binary patch
literal 600
zcmV-e0;m0nP)%))Ea7
zNC`DWgF{72NJ0sxciz3fC&D0m-@Etqcf9ub2wnPAnLc5{4PX%H1so6o5uJ?<%$~x??0Lffjb}*$W8WStUUU
z)YQn)okkS*%#)x5GLbr6P|x)PZ_g}};K(s`@=XaZWg>|%UvD%U`|GX;=RMgBHCt0}
z?_6X1?S1cgL>odIcg7Yd_y0HzTmudR!e2Ep8R%^_R$__7L?l*G=VQOE)wIZaTj}YQ
zHRlww2*V%jj)c`;*yvvY=liXj*RC?E({9g#J12nol=UycJ$`NQ~^
zIOWjQ<1rAC&5Ls6{;+-^gCoJ%l!$z49!CykYk6`gp@a~9-X9F9PuLWWg~c!g)8I-;-H1V_S)E0bjLXze0T4)UAeP4MEa1r~!g
zsul4BaHI(t6z{7?i8QlMY1(Wnhe13uaS0000?D}`Ir0(-Mn=P8Wj|1omq;)&s)}L{`9%_pjF!u(UF5e$f{{^_ZX&<)Bp4Yvs>w{Q
z_en5v5^H26=a*o`t0V+6lKUF@{M1|3L%02WaEXLK18?p>ixAt!swcjR?~ph?sP1W7D;e6I2$Ufoqsv_rQ_2Wj^qeOfB^yo
z7`W8u(p1UVf3|
zC)f}X(M7FD4b;Lb$A)rWJ+;@!A&@2-*zOk>-`;2E>5!3^(=j7WG~;y4NE6LOI%Z@f
z#hQ*8X+{I?wn)c}G|_-m|3Jr#G%>!fqT4(jGtvsivnu@A>H9yWYk@Rlv_-S{Xm94s
xk6Z_Vv;|-nQ1MR#;q$t`Lmk>ju
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
+import { defineComponent } from "vue";
+import { ElConfigProvider } from "element-plus";
+import zhCn from "element-plus/dist/locale/zh-cn.mjs";
+
+export default defineComponent({
+  name: "app",
+  components: {
+    [ElConfigProvider.name]: ElConfigProvider
+  },
+  computed: {
+    currentLocale() {
+      return zhCn;
+    }
+  }
+});
+
+
+
diff --git a/src/assets/logo.png b/src/assets/logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..1f6665cb285fee4bf6eb1efafece263d0da663d4
GIT binary patch
literal 10430
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

literal 0
HcmV?d00001

diff --git a/src/layout/compoenets/AppMain.vue b/src/layout/compoenets/AppMain.vue
new file mode 100644
index 0000000..01b33d9
--- /dev/null
+++ b/src/layout/compoenets/AppMain.vue
@@ -0,0 +1,22 @@
+
+
diff --git a/src/layout/compoenets/Breadcrumb.vue b/src/layout/compoenets/Breadcrumb.vue
new file mode 100644
index 0000000..48091e8
--- /dev/null
+++ b/src/layout/compoenets/Breadcrumb.vue
@@ -0,0 +1,28 @@
+
+
+
diff --git a/src/layout/compoenets/LeftMenu.vue b/src/layout/compoenets/LeftMenu.vue
new file mode 100644
index 0000000..917bd25
--- /dev/null
+++ b/src/layout/compoenets/LeftMenu.vue
@@ -0,0 +1,54 @@
+
+
+
diff --git a/src/layout/index.vue b/src/layout/index.vue
new file mode 100644
index 0000000..957aa21
--- /dev/null
+++ b/src/layout/index.vue
@@ -0,0 +1,15 @@
+
+
diff --git a/src/main.ts b/src/main.ts
new file mode 100644
index 0000000..a2afdf3
--- /dev/null
+++ b/src/main.ts
@@ -0,0 +1,13 @@
+import { createApp } from "vue";
+import App from "./App.vue";
+import router from "./router";
+import "./styles/index.scss";
+import ElementPlus from "element-plus";
+
+createApp(App)
+  .use(router)
+  .use(ElementPlus)
+  .mount("#app")
+  .$nextTick(() => {
+    postMessage({ payload: "removeLoading" }, "*");
+  });
diff --git a/src/router/index.ts b/src/router/index.ts
new file mode 100644
index 0000000..af55e5b
--- /dev/null
+++ b/src/router/index.ts
@@ -0,0 +1,82 @@
+import { createRouter, createWebHistory, createWebHashHistory, RouteRecordRaw } from "vue-router";
+
+const Layout = () => import("@/layout/index.vue");
+
+const routes: RouteRecordRaw[] = [
+  {
+    path: "/",
+    name: "Index",
+    component: Layout,
+    redirect: "/home",
+    children: [
+      {
+        path: "/home",
+        name: "Home",
+        meta: {
+          title: "连接",
+          icon: "material-symbols:rocket-launch-rounded",
+          keepAlive: true
+        },
+        component: () => import("@/views/home/index.vue")
+      },
+      {
+        path: "/proxy",
+        name: "Proxy",
+        meta: {
+          title: "穿透列表",
+          icon: "material-symbols:cloud",
+          keepAlive: true
+        },
+        component: () => import("@/views/proxy/index.vue")
+      },
+      {
+        path: "/download",
+        name: "Download",
+        meta: {
+          title: "版本下载",
+          icon: "material-symbols:download-2",
+          keepAlive: true
+        },
+        component: () => import("@/views/download/index.vue")
+      },
+      {
+        path: "/config",
+        name: "Config",
+        meta: {
+          title: "系统配置",
+          icon: "material-symbols:settings",
+          keepAlive: true
+        },
+        component: () => import("@/views/config/index.vue")
+      },
+      {
+        path: "/logger",
+        name: "Logger",
+        meta: {
+          title: "日志",
+          icon: "material-symbols:file-copy-sharp",
+          keepAlive: true,
+          hidden: false
+        },
+        component: () => import("@/views/logger/index.vue")
+      }
+      // {
+      //   path: "/comingSoon",
+      //   name: "ComingSoon",
+      //   meta: {
+      //     title: "敬请期待",
+      //     icon: "material-symbols:file-copy-sharp",
+      //     keepAlive: false,
+      //     hidden: true
+      //   },
+      //   component: () => import("@/views/comingSoon/index.vue")
+      // }
+    ]
+  }
+];
+
+const router = createRouter({
+  history: createWebHashHistory(),
+  routes
+});
+export default router;
diff --git a/src/styles/element.scss b/src/styles/element.scss
new file mode 100644
index 0000000..c3f9dad
--- /dev/null
+++ b/src/styles/element.scss
@@ -0,0 +1,9 @@
+@forward 'element-plus/theme-chalk/src/common/var.scss' with (
+  $colors: (
+    'primary': (
+      'base': #5F3BB0,
+    ),
+  ),
+);
+
+@use "element-plus/theme-chalk/src/index.scss" as *;
diff --git a/src/styles/index.scss b/src/styles/index.scss
new file mode 100644
index 0000000..892b598
--- /dev/null
+++ b/src/styles/index.scss
@@ -0,0 +1,21 @@
+@import "reset";
+@import "layout";
+@import "element";
+@import "tailwind";
+/* 自定义全局 CssVar */
+:root {
+  --pure-transition-duration: 0.016s;
+}
+
+/* 灰色模式 */
+.html-grey {
+  filter: grayscale(100%);
+}
+
+/* 色弱模式 */
+.html-weakness {
+  filter: invert(80%);
+}
+
+html {
+}
diff --git a/src/styles/layout.scss b/src/styles/layout.scss
new file mode 100644
index 0000000..8970772
--- /dev/null
+++ b/src/styles/layout.scss
@@ -0,0 +1,86 @@
+$main-bg: #F3F3F3;
+$primary-color: #5F3BB0;
+
+.main-container {
+  background: $main-bg;
+  width: calc(100% - 60px);
+  height: 100vh;
+  padding: 20px;
+
+  .main {
+    height: 100%;
+    width: 100%;
+    overflow: hidden;
+  }
+
+  .app-container-breadcrumb {
+    height: calc(100% - 50px);
+    overflow-y: auto;
+    overflow-x: hidden;
+  }
+
+  .breadcrumb {
+    color: $primary-color;
+    font-size: 36px;
+    height: 50px;
+
+    svg {
+      vertical-align: top;
+    }
+
+    span {
+      vertical-align: top;
+      color: black;
+      font-size: 18px;
+      font-weight: bold;
+      transform: translateY(5px);
+      display: inline-block;
+
+    }
+  }
+}
+
+.left-menu-container {
+  background: #fff;
+  width: 60px;
+  height: 100vh;
+
+  .menu-container {
+
+    .menu {
+      display: flex;
+      height: 40px;
+      justify-content: center;
+      align-items: center;
+      font-size: 20px;
+      color: #ADADAD;
+      margin-bottom: 10px;
+    }
+
+    .menu-selected {
+      background: #EBE6F7;
+      color: $primary-color;
+    }
+
+    .menu:hover {
+      color: $primary-color;
+      cursor: pointer;
+    }
+  }
+}
+
+.logo-container {
+  height: 60px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+
+  .logo {
+    width: 60%;
+  }
+}
+
+
+.primary-text {
+  color: $primary-color;
+}
diff --git a/src/styles/reset.scss b/src/styles/reset.scss
new file mode 100644
index 0000000..9d1d252
--- /dev/null
+++ b/src/styles/reset.scss
@@ -0,0 +1,275 @@
+*,
+::before,
+::after {
+  box-sizing: border-box;
+  border-width: 0;
+  border-style: solid;
+  border-color: currentColor;
+}
+
+*{
+  -webkit-touch-callout:none;  /*系统默认菜单被禁用*/
+  -webkit-user-select:none; /*webkit浏览器*/
+  -khtml-user-select:none; /*早期浏览器*/
+  -moz-user-select:none;/*火狐*/
+  -ms-user-select:none; /*IE10*/
+  user-select:none;
+}
+
+input{
+  -webkit-user-select:auto; /*webkit浏览器*/
+}
+textarea{
+  -webkit-user-select:auto; /*webkit浏览器*/
+}
+
+#app {
+  width: 100%;
+  height: 100%;
+}
+
+html {
+  line-height: 1.5;
+  -webkit-text-size-adjust: 100%;
+  -moz-tab-size: 4;
+  tab-size: 4;
+  width: 100%;
+  height: 100%;
+  box-sizing: border-box;
+}
+
+body {
+  margin: 0;
+  line-height: inherit;
+  width: 100%;
+  height: 100%;
+  -moz-osx-font-smoothing: grayscale;
+  -webkit-font-smoothing: antialiased;
+  text-rendering: optimizelegibility;
+  font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB",
+    "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
+}
+
+hr {
+  height: 0;
+  color: inherit;
+  border-top-width: 1px;
+}
+
+abbr:where([title]) {
+  text-decoration: underline dotted;
+}
+
+a {
+  color: inherit;
+  text-decoration: inherit;
+}
+
+b,
+strong {
+  font-weight: bolder;
+}
+
+code,
+kbd,
+samp,
+pre {
+  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
+    "Liberation Mono", "Courier New", monospace;
+  font-size: 1em;
+}
+
+small {
+  font-size: 80%;
+}
+
+sub,
+sup {
+  font-size: 75%;
+  line-height: 0;
+  position: relative;
+  vertical-align: baseline;
+}
+
+sub {
+  bottom: -0.25em;
+}
+
+sup {
+  top: -0.5em;
+}
+
+table {
+  text-indent: 0;
+  border-color: inherit;
+  border-collapse: collapse;
+}
+
+button,
+input,
+optgroup,
+select,
+textarea {
+  font-family: inherit;
+  font-size: 100%;
+  line-height: inherit;
+  color: inherit;
+  margin: 0;
+  padding: 0;
+}
+
+button,
+select {
+  text-transform: none;
+}
+
+button,
+[type="button"],
+[type="reset"],
+[type="submit"] {
+  background-image: none;
+}
+
+:-moz-focusring {
+  outline: auto;
+}
+
+:-moz-ui-invalid {
+  box-shadow: none;
+}
+
+progress {
+  vertical-align: baseline;
+}
+
+::-webkit-inner-spin-button,
+::-webkit-outer-spin-button {
+  height: auto;
+}
+
+[type="search"] {
+  outline-offset: -2px;
+}
+
+::-webkit-file-upload-button {
+  font: inherit;
+}
+
+summary {
+  display: list-item;
+}
+
+blockquote,
+dl,
+dd,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+hr,
+figure,
+p,
+pre {
+  margin: 0;
+}
+
+fieldset {
+  margin: 0;
+  padding: 0;
+}
+
+legend {
+  padding: 0;
+}
+
+ol,
+ul,
+menu {
+  list-style: none;
+  margin: 0;
+  padding: 0;
+}
+
+textarea {
+  resize: vertical;
+}
+
+input::placeholder,
+textarea::placeholder {
+  opacity: 1;
+  color: #9ca3af;
+}
+
+button,
+[role="button"] {
+  cursor: pointer;
+}
+
+:disabled {
+  cursor: default;
+}
+
+//img,
+svg,
+video,
+canvas,
+audio,
+iframe,
+embed,
+object {
+  display: block;
+}
+
+img,
+video {
+  max-width: 100%;
+  height: auto;
+}
+
+[hidden] {
+  display: none;
+}
+
+.dark {
+  color-scheme: dark;
+}
+
+label {
+  font-weight: 700;
+}
+
+*,
+*::before,
+*::after {
+  box-sizing: inherit;
+}
+
+a:focus,
+a:active {
+  outline: none;
+}
+
+a,
+a:focus,
+a:hover {
+  cursor: pointer;
+  color: inherit;
+  text-decoration: none;
+}
+
+div:focus {
+  outline: none;
+}
+
+.clearfix {
+  &::after {
+    visibility: hidden;
+    display: block;
+    font-size: 0;
+    content: " ";
+    clear: both;
+    height: 0;
+  }
+}
diff --git a/src/styles/tailwind.scss b/src/styles/tailwind.scss
new file mode 100644
index 0000000..b5c61c9
--- /dev/null
+++ b/src/styles/tailwind.scss
@@ -0,0 +1,3 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
diff --git a/src/types/global.d.ts b/src/types/global.d.ts
new file mode 100644
index 0000000..277926a
--- /dev/null
+++ b/src/types/global.d.ts
@@ -0,0 +1,9 @@
+declare module 'element-plus/dist/locale/zh-cn.mjs' {
+  const zhLocale: any;
+  export default zhLocale;
+}
+
+declare module 'element-plus/dist/locale/en.mjs' {
+  const enLocale: any;
+  export default enLocale;
+}
diff --git a/src/types/shims-tsx.d.ts b/src/types/shims-tsx.d.ts
new file mode 100644
index 0000000..199f979
--- /dev/null
+++ b/src/types/shims-tsx.d.ts
@@ -0,0 +1,22 @@
+import Vue, { VNode } from "vue";
+
+declare module "*.tsx" {
+  import Vue from "compatible-vue";
+  export default Vue;
+}
+
+declare global {
+  namespace JSX {
+    interface Element extends VNode {}
+    interface ElementClass extends Vue {}
+    interface ElementAttributesProperty {
+      $props: any;
+    }
+    interface IntrinsicElements {
+      [elem: string]: any;
+    }
+    interface IntrinsicAttributes {
+      [elem: string]: any;
+    }
+  }
+}
diff --git a/src/types/shims-vue.d.ts b/src/types/shims-vue.d.ts
new file mode 100644
index 0000000..9fa8db3
--- /dev/null
+++ b/src/types/shims-vue.d.ts
@@ -0,0 +1,10 @@
+declare module "*.vue" {
+  import { DefineComponent } from "vue";
+  const component: DefineComponent<{}, {}, any>;
+  export default component;
+}
+
+declare module "*.scss" {
+  const scss: Record;
+  export default scss;
+}
diff --git a/src/utils/clone.ts b/src/utils/clone.ts
new file mode 100644
index 0000000..f40201b
--- /dev/null
+++ b/src/utils/clone.ts
@@ -0,0 +1,19 @@
+export function clone(value: T): T {
+  /** 空 */
+  if (!value) return value;
+  /** 数组 */
+  if (Array.isArray(value))
+    return value.map(item => clone(item)) as unknown as T;
+  /** 日期 */
+  if (value instanceof Date) return new Date(value) as unknown as T;
+  /** 普通对象 */
+  if (typeof value === "object") {
+    return Object.fromEntries(
+      Object.entries(value).map(([k, v]: [string, any]) => {
+        return [k, clone(v)];
+      })
+    ) as unknown as T;
+  }
+  /** 基本类型 */
+  return value;
+}
diff --git a/src/views/comingSoon/index.vue b/src/views/comingSoon/index.vue
new file mode 100644
index 0000000..229688d
--- /dev/null
+++ b/src/views/comingSoon/index.vue
@@ -0,0 +1,15 @@
+
+
+
diff --git a/src/views/config/index.vue b/src/views/config/index.vue
new file mode 100644
index 0000000..8f4284c
--- /dev/null
+++ b/src/views/config/index.vue
@@ -0,0 +1,203 @@
+
+
+
+
diff --git a/src/views/download/index.vue b/src/views/download/index.vue
new file mode 100644
index 0000000..75042a4
--- /dev/null
+++ b/src/views/download/index.vue
@@ -0,0 +1,116 @@
+
+
+
+
diff --git a/src/views/home/index.vue b/src/views/home/index.vue
new file mode 100644
index 0000000..7a5526e
--- /dev/null
+++ b/src/views/home/index.vue
@@ -0,0 +1,199 @@
+
+
+
+
+
diff --git a/src/views/logger/index.vue b/src/views/logger/index.vue
new file mode 100644
index 0000000..a24402e
--- /dev/null
+++ b/src/views/logger/index.vue
@@ -0,0 +1,67 @@
+
+
+
+
diff --git a/src/views/proxy/index.vue b/src/views/proxy/index.vue
new file mode 100644
index 0000000..02a1e99
--- /dev/null
+++ b/src/views/proxy/index.vue
@@ -0,0 +1,487 @@
+
+
+
+
diff --git a/tailwind.config.js b/tailwind.config.js
new file mode 100644
index 0000000..11bb37f
--- /dev/null
+++ b/tailwind.config.js
@@ -0,0 +1,20 @@
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+  darkMode: "class",
+  corePlugins: {
+    preflight: false
+  },
+  content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
+  theme: {
+    extend: {
+      colors: {
+        bg_color: "var(--el-bg-color)",
+        primary: "var(--el-color-primary)",
+        primary_light_9: "var(--el-color-primary-light-9)",
+        text_color_primary: "var(--el-text-color-primary)",
+        text_color_regular: "var(--el-text-color-regular)",
+        text_color_disabled: "var(--el-text-color-disabled)"
+      }
+    }
+  }
+};
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..afca1ed
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,49 @@
+{
+  "compilerOptions": {
+    "target": "ESNext",
+    "useDefineForClassFields": true,
+    "module": "ESNext",
+    "moduleResolution": "Node",
+    "strict": false,
+    "jsx": "preserve",
+    "resolveJsonModule": true,
+    "isolatedModules": true,
+    "esModuleInterop": true,
+    "lib": [
+      "ESNext",
+      "DOM"
+    ],
+    "skipLibCheck": true,
+    "noEmit": true,
+    "baseUrl": ".",
+    "paths": {
+      "@/*": [
+        "src/*"
+      ]
+    },
+    "types": [
+      "node",
+      "vite/client",
+      "element-plus/global"
+    ],
+    "typeRoots": [
+      "./types",
+      "./node_modules/@types/",
+      "./node_modules"
+    ]
+  },
+  "include": [
+    "src/**/*.ts",
+    "src/**/*.tsx",
+    "src/**/*.vue",
+    "types/*.d.ts",
+    "package.json",
+    "electron",
+    "vite.config.ts"
+  ],
+  "exclude": [
+    "node_modules",
+    "dist",
+    "**/*.js"
+  ]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..c8b26b9
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,105 @@
+import { rmSync } from "node:fs";
+import { defineConfig } from "vite";
+import vue from "@vitejs/plugin-vue";
+import electron from "vite-plugin-electron";
+import renderer from "vite-plugin-electron-renderer";
+import { notBundle } from "vite-plugin-electron/plugin";
+import { resolve } from "path";
+
+
+import pkg from "./package.json";
+
+/** 路径查找 */
+const pathResolve = (dir: string): string => {
+  return resolve(__dirname, ".", dir);
+};
+
+// https://vitejs.dev/config/
+export default defineConfig(({ command }) => {
+  rmSync("dist-electron", { recursive: true, force: true });
+
+  const isServe = command === "serve";
+  const isBuild = command === "build";
+  const sourcemap = isServe || !!process.env.VSCODE_DEBUG;
+
+  return {
+    plugins: [
+      vue(),
+      electron([
+        {
+          // Main process entry file of the Electron App.
+          entry: "electron/main/index.ts",
+          onstart({ startup }) {
+            if (process.env.VSCODE_DEBUG) {
+              console.log(
+                /* For `.vscode/.debug.script.mjs` */ "[startup] Electron App"
+              );
+            } else {
+              startup();
+            }
+          },
+          vite: {
+            build: {
+              sourcemap,
+              minify: isBuild,
+              outDir: "dist-electron/main",
+              rollupOptions: {
+                // Some third-party Node.js libraries may not be built correctly by Vite, especially `C/C++` addons,
+                // we can use `external` to exclude them to ensure they work correctly.
+                // Others need to put them in `dependencies` to ensure they are collected into `app.asar` after the app is built.
+                // Of course, this is not absolute, just this way is relatively simple. :)
+                external: Object.keys(
+                  "dependencies" in pkg ? pkg.dependencies : {}
+                )
+              }
+            },
+            plugins: [
+              // This is just an option to improve build performance, it's non-deterministic!
+              // e.g. `import log from 'electron-log'` -> `const log = require('electron-log')`
+              isServe && notBundle()
+            ]
+          }
+        },
+        {
+          entry: "electron/preload/index.ts",
+          onstart({ reload }) {
+            // Notify the Renderer process to reload the page when the Preload scripts build is complete,
+            // instead of restarting the entire Electron App.
+            reload();
+          },
+          vite: {
+            build: {
+              sourcemap: sourcemap ? "inline" : undefined, // #332
+              minify: isBuild,
+              outDir: "dist-electron/preload",
+              rollupOptions: {
+                external: Object.keys(
+                  "dependencies" in pkg ? pkg.dependencies : {}
+                )
+              }
+            },
+            plugins: [isServe && notBundle()]
+          }
+        }
+      ]),
+      // Use Node.js API in the Renderer process
+      renderer()
+    ],
+    resolve: {
+      alias: {
+        "@": pathResolve("src"),
+        "@build": pathResolve("build")
+      }
+    },
+    server:
+      process.env.VSCODE_DEBUG &&
+      (() => {
+        const url = new URL(pkg.debug.env.VITE_DEV_SERVER_URL);
+        return {
+          host: url.hostname,
+          port: +url.port
+        };
+      })(),
+    clearScreen: false
+  };
+});