Update version to 1.0.10 in package.json and version.json, add GoManager support in main.ts, and enhance UI components to include Go management features. Refactor preload.ts and App.vue for improved service management and user experience.

This commit is contained in:
ethanfly 2026-02-05 09:08:23 +08:00
parent 982a51ef52
commit 4abce26cf2
9 changed files with 1591 additions and 538 deletions

View File

@ -13,6 +13,7 @@ import { MysqlManager } from "./services/MysqlManager";
import { NginxManager } from "./services/NginxManager"; import { NginxManager } from "./services/NginxManager";
import { RedisManager } from "./services/RedisManager"; import { RedisManager } from "./services/RedisManager";
import { NodeManager } from "./services/NodeManager"; import { NodeManager } from "./services/NodeManager";
import { GoManager } from "./services/GoManager";
import { ServiceManager } from "./services/ServiceManager"; import { ServiceManager } from "./services/ServiceManager";
import { HostsManager } from "./services/HostsManager"; import { HostsManager } from "./services/HostsManager";
import { GitManager } from "./services/GitManager"; import { GitManager } from "./services/GitManager";
@ -98,7 +99,7 @@ export function sendDownloadProgress(
type: string, type: string,
progress: number, progress: number,
downloaded: number, downloaded: number,
total: number total: number,
) { ) {
if (mainWindow && !mainWindow.isDestroyed()) { if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send("download-progress", { mainWindow.webContents.send("download-progress", {
@ -117,6 +118,7 @@ const mysqlManager = new MysqlManager(configStore);
const nginxManager = new NginxManager(configStore); const nginxManager = new NginxManager(configStore);
const redisManager = new RedisManager(configStore); const redisManager = new RedisManager(configStore);
const nodeManager = new NodeManager(configStore); const nodeManager = new NodeManager(configStore);
const goManager = new GoManager(configStore);
const serviceManager = new ServiceManager(configStore); const serviceManager = new ServiceManager(configStore);
const hostsManager = new HostsManager(); const hostsManager = new HostsManager();
const gitManager = new GitManager(configStore); const gitManager = new GitManager(configStore);
@ -315,7 +317,7 @@ ipcMain.handle("window:close", () => mainWindow?.close());
// 打开外部链接 // 打开外部链接
ipcMain.handle("shell:openExternal", (_, url: string) => ipcMain.handle("shell:openExternal", (_, url: string) =>
shell.openExternal(url) shell.openExternal(url),
); );
ipcMain.handle("shell:openPath", (_, path: string) => shell.openPath(path)); ipcMain.handle("shell:openPath", (_, path: string) => shell.openPath(path));
@ -332,33 +334,33 @@ ipcMain.handle("dialog:selectDirectory", async () => {
// ==================== PHP 管理 ==================== // ==================== PHP 管理 ====================
ipcMain.handle("php:getVersions", () => phpManager.getInstalledVersions()); ipcMain.handle("php:getVersions", () => phpManager.getInstalledVersions());
ipcMain.handle("php:getAvailableVersions", () => ipcMain.handle("php:getAvailableVersions", () =>
phpManager.getAvailableVersions() phpManager.getAvailableVersions(),
); );
ipcMain.handle("php:install", (_, version: string) => ipcMain.handle("php:install", (_, version: string) =>
phpManager.install(version) phpManager.install(version),
); );
ipcMain.handle("php:uninstall", (_, version: string) => ipcMain.handle("php:uninstall", (_, version: string) =>
phpManager.uninstall(version) phpManager.uninstall(version),
); );
ipcMain.handle("php:setActive", (_, version: string) => ipcMain.handle("php:setActive", (_, version: string) =>
phpManager.setActive(version) phpManager.setActive(version),
); );
ipcMain.handle("php:getExtensions", (_, version: string) => ipcMain.handle("php:getExtensions", (_, version: string) =>
phpManager.getExtensions(version) phpManager.getExtensions(version),
); );
ipcMain.handle("php:openExtensionDir", (_, version: string) => ipcMain.handle("php:openExtensionDir", (_, version: string) =>
phpManager.openExtensionDir(version) phpManager.openExtensionDir(version),
); );
ipcMain.handle( ipcMain.handle(
"php:getAvailableExtensions", "php:getAvailableExtensions",
(_, version: string, searchKeyword?: string) => (_, version: string, searchKeyword?: string) =>
phpManager.getAvailableExtensions(version, searchKeyword) phpManager.getAvailableExtensions(version, searchKeyword),
); );
ipcMain.handle("php:enableExtension", (_, version: string, ext: string) => ipcMain.handle("php:enableExtension", (_, version: string, ext: string) =>
phpManager.enableExtension(version, ext) phpManager.enableExtension(version, ext),
); );
ipcMain.handle("php:disableExtension", (_, version: string, ext: string) => ipcMain.handle("php:disableExtension", (_, version: string, ext: string) =>
phpManager.disableExtension(version, ext) phpManager.disableExtension(version, ext),
); );
ipcMain.handle( ipcMain.handle(
"php:installExtension", "php:installExtension",
@ -367,14 +369,14 @@ ipcMain.handle(
version: string, version: string,
ext: string, ext: string,
downloadUrl?: string, downloadUrl?: string,
packageName?: string packageName?: string,
) => phpManager.installExtension(version, ext, downloadUrl, packageName) ) => phpManager.installExtension(version, ext, downloadUrl, packageName),
); );
ipcMain.handle("php:getConfig", (_, version: string) => ipcMain.handle("php:getConfig", (_, version: string) =>
phpManager.getConfig(version) phpManager.getConfig(version),
); );
ipcMain.handle("php:saveConfig", (_, version: string, config: string) => ipcMain.handle("php:saveConfig", (_, version: string, config: string) =>
phpManager.saveConfig(version, config) phpManager.saveConfig(version, config),
); );
// ==================== Composer 管理 ==================== // ==================== Composer 管理 ====================
@ -382,62 +384,62 @@ ipcMain.handle("composer:getStatus", () => phpManager.getComposerStatus());
ipcMain.handle("composer:install", () => phpManager.installComposer()); ipcMain.handle("composer:install", () => phpManager.installComposer());
ipcMain.handle("composer:uninstall", () => phpManager.uninstallComposer()); ipcMain.handle("composer:uninstall", () => phpManager.uninstallComposer());
ipcMain.handle("composer:setMirror", (_, mirror: string) => ipcMain.handle("composer:setMirror", (_, mirror: string) =>
phpManager.setComposerMirror(mirror) phpManager.setComposerMirror(mirror),
); );
ipcMain.handle( ipcMain.handle(
"composer:createLaravelProject", "composer:createLaravelProject",
(_, projectName: string, targetDir: string) => (_, projectName: string, targetDir: string) =>
phpManager.createLaravelProject(projectName, targetDir) phpManager.createLaravelProject(projectName, targetDir),
); );
// ==================== MySQL 管理 ==================== // ==================== MySQL 管理 ====================
ipcMain.handle("mysql:getVersions", () => mysqlManager.getInstalledVersions()); ipcMain.handle("mysql:getVersions", () => mysqlManager.getInstalledVersions());
ipcMain.handle("mysql:getAvailableVersions", () => ipcMain.handle("mysql:getAvailableVersions", () =>
mysqlManager.getAvailableVersions() mysqlManager.getAvailableVersions(),
); );
ipcMain.handle("mysql:install", (_, version: string) => ipcMain.handle("mysql:install", (_, version: string) =>
mysqlManager.install(version) mysqlManager.install(version),
); );
ipcMain.handle("mysql:uninstall", (_, version: string) => ipcMain.handle("mysql:uninstall", (_, version: string) =>
mysqlManager.uninstall(version) mysqlManager.uninstall(version),
); );
ipcMain.handle("mysql:start", (_, version: string) => ipcMain.handle("mysql:start", (_, version: string) =>
mysqlManager.start(version) mysqlManager.start(version),
); );
ipcMain.handle("mysql:stop", (_, version: string) => ipcMain.handle("mysql:stop", (_, version: string) =>
mysqlManager.stop(version) mysqlManager.stop(version),
); );
ipcMain.handle("mysql:restart", (_, version: string) => ipcMain.handle("mysql:restart", (_, version: string) =>
mysqlManager.restart(version) mysqlManager.restart(version),
); );
ipcMain.handle("mysql:getStatus", (_, version: string) => ipcMain.handle("mysql:getStatus", (_, version: string) =>
mysqlManager.getStatus(version) mysqlManager.getStatus(version),
); );
ipcMain.handle( ipcMain.handle(
"mysql:changePassword", "mysql:changePassword",
(_, version: string, newPassword: string, currentPassword?: string) => (_, version: string, newPassword: string, currentPassword?: string) =>
mysqlManager.changeRootPassword(version, newPassword, currentPassword) mysqlManager.changeRootPassword(version, newPassword, currentPassword),
); );
ipcMain.handle("mysql:getConfig", (_, version: string) => ipcMain.handle("mysql:getConfig", (_, version: string) =>
mysqlManager.getConfig(version) mysqlManager.getConfig(version),
); );
ipcMain.handle("mysql:saveConfig", (_, version: string, config: string) => ipcMain.handle("mysql:saveConfig", (_, version: string, config: string) =>
mysqlManager.saveConfig(version, config) mysqlManager.saveConfig(version, config),
); );
ipcMain.handle("mysql:reinitialize", (_, version: string) => ipcMain.handle("mysql:reinitialize", (_, version: string) =>
mysqlManager.reinitialize(version) mysqlManager.reinitialize(version),
); );
// ==================== Nginx 管理 ==================== // ==================== Nginx 管理 ====================
ipcMain.handle("nginx:getVersions", () => nginxManager.getInstalledVersions()); ipcMain.handle("nginx:getVersions", () => nginxManager.getInstalledVersions());
ipcMain.handle("nginx:getAvailableVersions", () => ipcMain.handle("nginx:getAvailableVersions", () =>
nginxManager.getAvailableVersions() nginxManager.getAvailableVersions(),
); );
ipcMain.handle("nginx:install", (_, version: string) => ipcMain.handle("nginx:install", (_, version: string) =>
nginxManager.install(version) nginxManager.install(version),
); );
ipcMain.handle("nginx:uninstall", (_, version: string) => ipcMain.handle("nginx:uninstall", (_, version: string) =>
nginxManager.uninstall(version) nginxManager.uninstall(version),
); );
ipcMain.handle("nginx:start", () => nginxManager.start()); ipcMain.handle("nginx:start", () => nginxManager.start());
ipcMain.handle("nginx:stop", () => nginxManager.stop()); ipcMain.handle("nginx:stop", () => nginxManager.stop());
@ -446,39 +448,39 @@ ipcMain.handle("nginx:reload", () => nginxManager.reload());
ipcMain.handle("nginx:getStatus", () => nginxManager.getStatus()); ipcMain.handle("nginx:getStatus", () => nginxManager.getStatus());
ipcMain.handle("nginx:getConfig", () => nginxManager.getConfig()); ipcMain.handle("nginx:getConfig", () => nginxManager.getConfig());
ipcMain.handle("nginx:saveConfig", (_, config: string) => ipcMain.handle("nginx:saveConfig", (_, config: string) =>
nginxManager.saveConfig(config) nginxManager.saveConfig(config),
); );
ipcMain.handle("nginx:getSites", () => nginxManager.getSites()); ipcMain.handle("nginx:getSites", () => nginxManager.getSites());
ipcMain.handle("nginx:addSite", (_, site: any) => nginxManager.addSite(site)); ipcMain.handle("nginx:addSite", (_, site: any) => nginxManager.addSite(site));
ipcMain.handle("nginx:removeSite", (_, name: string) => ipcMain.handle("nginx:removeSite", (_, name: string) =>
nginxManager.removeSite(name) nginxManager.removeSite(name),
); );
ipcMain.handle("nginx:updateSite", (_, originalName: string, site: any) => ipcMain.handle("nginx:updateSite", (_, originalName: string, site: any) =>
nginxManager.updateSite(originalName, site) nginxManager.updateSite(originalName, site),
); );
ipcMain.handle("nginx:enableSite", (_, name: string) => ipcMain.handle("nginx:enableSite", (_, name: string) =>
nginxManager.enableSite(name) nginxManager.enableSite(name),
); );
ipcMain.handle("nginx:disableSite", (_, name: string) => ipcMain.handle("nginx:disableSite", (_, name: string) =>
nginxManager.disableSite(name) nginxManager.disableSite(name),
); );
ipcMain.handle("nginx:generateLaravelConfig", (_, site: any) => ipcMain.handle("nginx:generateLaravelConfig", (_, site: any) =>
nginxManager.generateLaravelConfig(site) nginxManager.generateLaravelConfig(site),
); );
ipcMain.handle("nginx:requestSSL", (_, domain: string, email: string) => ipcMain.handle("nginx:requestSSL", (_, domain: string, email: string) =>
nginxManager.requestSSLCertificate(domain, email) nginxManager.requestSSLCertificate(domain, email),
); );
// ==================== Redis 管理 ==================== // ==================== Redis 管理 ====================
ipcMain.handle("redis:getVersions", () => redisManager.getInstalledVersions()); ipcMain.handle("redis:getVersions", () => redisManager.getInstalledVersions());
ipcMain.handle("redis:getAvailableVersions", () => ipcMain.handle("redis:getAvailableVersions", () =>
redisManager.getAvailableVersions() redisManager.getAvailableVersions(),
); );
ipcMain.handle("redis:install", (_, version: string) => ipcMain.handle("redis:install", (_, version: string) =>
redisManager.install(version) redisManager.install(version),
); );
ipcMain.handle("redis:uninstall", (_, version: string) => ipcMain.handle("redis:uninstall", (_, version: string) =>
redisManager.uninstall(version) redisManager.uninstall(version),
); );
ipcMain.handle("redis:start", () => redisManager.start()); ipcMain.handle("redis:start", () => redisManager.start());
ipcMain.handle("redis:stop", () => redisManager.stop()); ipcMain.handle("redis:stop", () => redisManager.stop());
@ -486,103 +488,131 @@ ipcMain.handle("redis:restart", () => redisManager.restart());
ipcMain.handle("redis:getStatus", () => redisManager.getStatus()); ipcMain.handle("redis:getStatus", () => redisManager.getStatus());
ipcMain.handle("redis:getConfig", () => redisManager.getConfig()); ipcMain.handle("redis:getConfig", () => redisManager.getConfig());
ipcMain.handle("redis:saveConfig", (_, config: string) => ipcMain.handle("redis:saveConfig", (_, config: string) =>
redisManager.saveConfig(config) redisManager.saveConfig(config),
); );
// ==================== Node.js 管理 ==================== // ==================== Node.js 管理 ====================
ipcMain.handle("node:getVersions", () => nodeManager.getInstalledVersions()); ipcMain.handle("node:getVersions", () => nodeManager.getInstalledVersions());
ipcMain.handle("node:getAvailableVersions", () => ipcMain.handle("node:getAvailableVersions", () =>
nodeManager.getAvailableVersions() nodeManager.getAvailableVersions(),
); );
ipcMain.handle("node:install", (_, version: string, downloadUrl: string) => ipcMain.handle("node:install", (_, version: string, downloadUrl: string) =>
nodeManager.install(version, downloadUrl) nodeManager.install(version, downloadUrl),
); );
ipcMain.handle("node:uninstall", (_, version: string) => ipcMain.handle("node:uninstall", (_, version: string) =>
nodeManager.uninstall(version) nodeManager.uninstall(version),
); );
ipcMain.handle("node:setActive", (_, version: string) => ipcMain.handle("node:setActive", (_, version: string) =>
nodeManager.setActive(version) nodeManager.setActive(version),
); );
ipcMain.handle("node:getInfo", (_, version: string) => ipcMain.handle("node:getInfo", (_, version: string) =>
nodeManager.getNodeInfo(version) nodeManager.getNodeInfo(version),
);
// ==================== Go 管理 ====================
ipcMain.handle("go:getVersions", () => goManager.getInstalledVersions());
ipcMain.handle("go:getAvailableVersions", () =>
goManager.getAvailableVersions(),
);
ipcMain.handle("go:install", (_, version: string, downloadUrl: string) =>
goManager.install(version, downloadUrl),
);
ipcMain.handle("go:uninstall", (_, version: string) =>
goManager.uninstall(version),
);
ipcMain.handle("go:setActive", (_, version: string) =>
goManager.setActive(version),
);
ipcMain.handle("go:getInfo", (_, version: string) =>
goManager.getGoInfo(version),
); );
// ==================== 服务管理 ==================== // ==================== 服务管理 ====================
ipcMain.handle("service:getAll", () => serviceManager.getAllServices()); ipcMain.handle("service:getAll", () => serviceManager.getAllServices());
ipcMain.handle("service:setAutoStart", (_, service: string, enabled: boolean) => ipcMain.handle("service:setAutoStart", (_, service: string, enabled: boolean) =>
serviceManager.setAutoStart(service, enabled) serviceManager.setAutoStart(service, enabled),
); );
ipcMain.handle("service:getAutoStart", (_, service: string) => ipcMain.handle("service:getAutoStart", (_, service: string) =>
serviceManager.getAutoStart(service) serviceManager.getAutoStart(service),
); );
ipcMain.handle("service:startAll", () => serviceManager.startAll()); ipcMain.handle("service:startAll", () => serviceManager.startAll());
ipcMain.handle("service:stopAll", () => serviceManager.stopAll()); ipcMain.handle("service:stopAll", () => serviceManager.stopAll());
// PHP-CGI 管理 - 支持多版本 // PHP-CGI 管理 - 支持多版本
ipcMain.handle("service:getPhpCgiStatus", () => serviceManager.getPhpCgiStatus()); ipcMain.handle("service:getPhpCgiStatus", () =>
serviceManager.getPhpCgiStatus(),
);
ipcMain.handle("service:startPhpCgi", () => serviceManager.startPhpCgi()); ipcMain.handle("service:startPhpCgi", () => serviceManager.startPhpCgi());
ipcMain.handle("service:stopPhpCgi", () => serviceManager.stopPhpCgi()); ipcMain.handle("service:stopPhpCgi", () => serviceManager.stopPhpCgi());
ipcMain.handle("service:startAllPhpCgi", () => serviceManager.startAllPhpCgi()); ipcMain.handle("service:startAllPhpCgi", () => serviceManager.startAllPhpCgi());
ipcMain.handle("service:stopAllPhpCgi", () => serviceManager.stopAllPhpCgi()); ipcMain.handle("service:stopAllPhpCgi", () => serviceManager.stopAllPhpCgi());
ipcMain.handle("service:startPhpCgiVersion", (_, version: string) => serviceManager.startPhpCgiVersion(version)); ipcMain.handle("service:startPhpCgiVersion", (_, version: string) =>
ipcMain.handle("service:stopPhpCgiVersion", (_, version: string) => serviceManager.stopPhpCgiVersion(version)); serviceManager.startPhpCgiVersion(version),
ipcMain.handle("service:getPhpCgiPort", (_, version: string) => serviceManager.getPhpCgiPort(version)); );
ipcMain.handle("service:stopPhpCgiVersion", (_, version: string) =>
serviceManager.stopPhpCgiVersion(version),
);
ipcMain.handle("service:getPhpCgiPort", (_, version: string) =>
serviceManager.getPhpCgiPort(version),
);
// ==================== Hosts 管理 ==================== // ==================== Hosts 管理 ====================
ipcMain.handle("hosts:get", () => hostsManager.getHosts()); ipcMain.handle("hosts:get", () => hostsManager.getHosts());
ipcMain.handle("hosts:add", (_, domain: string, ip: string) => ipcMain.handle("hosts:add", (_, domain: string, ip: string) =>
hostsManager.addHost(domain, ip) hostsManager.addHost(domain, ip),
); );
ipcMain.handle("hosts:remove", (_, domain: string) => ipcMain.handle("hosts:remove", (_, domain: string) =>
hostsManager.removeHost(domain) hostsManager.removeHost(domain),
); );
// ==================== Git 管理 ==================== // ==================== Git 管理 ====================
ipcMain.handle("git:getVersions", () => gitManager.getInstalledVersions()); ipcMain.handle("git:getVersions", () => gitManager.getInstalledVersions());
ipcMain.handle("git:getAvailableVersions", () => ipcMain.handle("git:getAvailableVersions", () =>
gitManager.getAvailableVersions() gitManager.getAvailableVersions(),
); );
ipcMain.handle("git:install", (_, version: string) => ipcMain.handle("git:install", (_, version: string) =>
gitManager.install(version) gitManager.install(version),
); );
ipcMain.handle("git:uninstall", () => gitManager.uninstall()); ipcMain.handle("git:uninstall", () => gitManager.uninstall());
ipcMain.handle("git:checkSystem", () => gitManager.checkSystemGit()); ipcMain.handle("git:checkSystem", () => gitManager.checkSystemGit());
ipcMain.handle("git:getConfig", () => gitManager.getGitConfig()); ipcMain.handle("git:getConfig", () => gitManager.getGitConfig());
ipcMain.handle("git:setConfig", (_, name: string, email: string) => ipcMain.handle("git:setConfig", (_, name: string, email: string) =>
gitManager.setGitConfig(name, email) gitManager.setGitConfig(name, email),
); );
// ==================== Python 管理 ==================== // ==================== Python 管理 ====================
ipcMain.handle("python:getVersions", () => pythonManager.getInstalledVersions()); ipcMain.handle("python:getVersions", () =>
pythonManager.getInstalledVersions(),
);
ipcMain.handle("python:getAvailableVersions", () => ipcMain.handle("python:getAvailableVersions", () =>
pythonManager.getAvailableVersions() pythonManager.getAvailableVersions(),
); );
ipcMain.handle("python:install", (_, version: string) => ipcMain.handle("python:install", (_, version: string) =>
pythonManager.install(version) pythonManager.install(version),
); );
ipcMain.handle("python:uninstall", (_, version: string) => ipcMain.handle("python:uninstall", (_, version: string) =>
pythonManager.uninstall(version) pythonManager.uninstall(version),
); );
ipcMain.handle("python:setActive", (_, version: string) => ipcMain.handle("python:setActive", (_, version: string) =>
pythonManager.setActive(version) pythonManager.setActive(version),
); );
ipcMain.handle("python:checkSystem", () => pythonManager.checkSystemPython()); ipcMain.handle("python:checkSystem", () => pythonManager.checkSystemPython());
ipcMain.handle("python:getPipInfo", (_, version: string) => ipcMain.handle("python:getPipInfo", (_, version: string) =>
pythonManager.getPipInfo(version) pythonManager.getPipInfo(version),
); );
ipcMain.handle( ipcMain.handle(
"python:installPackage", "python:installPackage",
(_, version: string, packageName: string) => (_, version: string, packageName: string) =>
pythonManager.installPackage(version, packageName) pythonManager.installPackage(version, packageName),
); );
// ==================== 配置管理 ==================== // ==================== 配置管理 ====================
ipcMain.handle("config:get", (_, key: string) => configStore.get(key)); ipcMain.handle("config:get", (_, key: string) => configStore.get(key));
ipcMain.handle("config:set", (_, key: string, value: any) => ipcMain.handle("config:set", (_, key: string, value: any) =>
configStore.set(key, value) configStore.set(key, value),
); );
ipcMain.handle("config:getBasePath", () => configStore.getBasePath()); ipcMain.handle("config:getBasePath", () => configStore.getBasePath());
ipcMain.handle("config:setBasePath", (_, path: string) => ipcMain.handle("config:setBasePath", (_, path: string) =>
configStore.setBasePath(path) configStore.setBasePath(path),
); );
// ==================== 应用设置 ==================== // ==================== 应用设置 ====================
@ -709,7 +739,7 @@ ipcMain.handle("app:getVersion", async () => {
version, version,
buildTime, buildTime,
buildDate, buildDate,
isPackaged: app.isPackaged isPackaged: app.isPackaged,
}; };
}); });
@ -733,9 +763,13 @@ ipcMain.handle("app:quit", () => {
// ==================== 日志管理 ==================== // ==================== 日志管理 ====================
ipcMain.handle("log:getFiles", () => logManager.getLogFiles()); ipcMain.handle("log:getFiles", () => logManager.getLogFiles());
ipcMain.handle("log:read", (_, logPath: string, lines?: number) => ipcMain.handle("log:read", (_, logPath: string, lines?: number) =>
logManager.readLog(logPath, lines) logManager.readLog(logPath, lines),
); );
ipcMain.handle("log:clear", (_, logPath: string) => logManager.clearLog(logPath)); ipcMain.handle("log:clear", (_, logPath: string) =>
ipcMain.handle("log:getDirectory", (_, type: 'nginx' | 'php' | 'mysql' | 'sites', version?: string) => logManager.clearLog(logPath),
logManager.getLogDirectory(type, version) );
ipcMain.handle(
"log:getDirectory",
(_, type: "nginx" | "php" | "mysql" | "sites", version?: string) =>
logManager.getLogDirectory(type, version),
); );

View File

@ -1,220 +1,318 @@
import { contextBridge, ipcRenderer } from 'electron' import { contextBridge, ipcRenderer } from "electron";
// 暴露安全的 API 到渲染进程 // 暴露安全的 API 到渲染进程
contextBridge.exposeInMainWorld('electronAPI', { contextBridge.exposeInMainWorld("electronAPI", {
// 窗口控制 // 窗口控制
minimize: () => ipcRenderer.invoke('window:minimize'), minimize: () => ipcRenderer.invoke("window:minimize"),
maximize: () => ipcRenderer.invoke('window:maximize'), maximize: () => ipcRenderer.invoke("window:maximize"),
close: () => ipcRenderer.invoke('window:close'), close: () => ipcRenderer.invoke("window:close"),
// Shell // Shell
openExternal: (url: string) => ipcRenderer.invoke('shell:openExternal', url), openExternal: (url: string) => ipcRenderer.invoke("shell:openExternal", url),
openPath: (path: string) => ipcRenderer.invoke('shell:openPath', path), openPath: (path: string) => ipcRenderer.invoke("shell:openPath", path),
// Dialog // Dialog
selectDirectory: () => ipcRenderer.invoke('dialog:selectDirectory'), selectDirectory: () => ipcRenderer.invoke("dialog:selectDirectory"),
// 下载进度监听 // 下载进度监听
onDownloadProgress: (callback: (data: { type: string; progress: number; downloaded: number; total: number }) => void) => { onDownloadProgress: (
ipcRenderer.on('download-progress', (_, data) => callback(data)) callback: (data: {
type: string;
progress: number;
downloaded: number;
total: number;
}) => void,
) => {
ipcRenderer.on("download-progress", (_, data) => callback(data));
}, },
removeDownloadProgressListener: () => { removeDownloadProgressListener: () => {
ipcRenderer.removeAllListeners('download-progress') ipcRenderer.removeAllListeners("download-progress");
}, },
// PHP 管理 // PHP 管理
php: { php: {
getVersions: () => ipcRenderer.invoke('php:getVersions'), getVersions: () => ipcRenderer.invoke("php:getVersions"),
getAvailableVersions: () => ipcRenderer.invoke('php:getAvailableVersions'), getAvailableVersions: () => ipcRenderer.invoke("php:getAvailableVersions"),
install: (version: string) => ipcRenderer.invoke('php:install', version), install: (version: string) => ipcRenderer.invoke("php:install", version),
uninstall: (version: string) => ipcRenderer.invoke('php:uninstall', version), uninstall: (version: string) =>
setActive: (version: string) => ipcRenderer.invoke('php:setActive', version), ipcRenderer.invoke("php:uninstall", version),
getExtensions: (version: string) => ipcRenderer.invoke('php:getExtensions', version), setActive: (version: string) =>
openExtensionDir: (version: string) => ipcRenderer.invoke('php:openExtensionDir', version), ipcRenderer.invoke("php:setActive", version),
getAvailableExtensions: (version: string, searchKeyword?: string) => ipcRenderer.invoke('php:getAvailableExtensions', version, searchKeyword), getExtensions: (version: string) =>
enableExtension: (version: string, ext: string) => ipcRenderer.invoke('php:enableExtension', version, ext), ipcRenderer.invoke("php:getExtensions", version),
disableExtension: (version: string, ext: string) => ipcRenderer.invoke('php:disableExtension', version, ext), openExtensionDir: (version: string) =>
installExtension: (version: string, ext: string, downloadUrl?: string, packageName?: string) => ipcRenderer.invoke('php:installExtension', version, ext, downloadUrl, packageName), ipcRenderer.invoke("php:openExtensionDir", version),
getConfig: (version: string) => ipcRenderer.invoke('php:getConfig', version), getAvailableExtensions: (version: string, searchKeyword?: string) =>
saveConfig: (version: string, config: string) => ipcRenderer.invoke('php:saveConfig', version, config) ipcRenderer.invoke("php:getAvailableExtensions", version, searchKeyword),
enableExtension: (version: string, ext: string) =>
ipcRenderer.invoke("php:enableExtension", version, ext),
disableExtension: (version: string, ext: string) =>
ipcRenderer.invoke("php:disableExtension", version, ext),
installExtension: (
version: string,
ext: string,
downloadUrl?: string,
packageName?: string,
) =>
ipcRenderer.invoke(
"php:installExtension",
version,
ext,
downloadUrl,
packageName,
),
getConfig: (version: string) =>
ipcRenderer.invoke("php:getConfig", version),
saveConfig: (version: string, config: string) =>
ipcRenderer.invoke("php:saveConfig", version, config),
}, },
// Composer 管理 // Composer 管理
composer: { composer: {
getStatus: () => ipcRenderer.invoke('composer:getStatus'), getStatus: () => ipcRenderer.invoke("composer:getStatus"),
install: () => ipcRenderer.invoke('composer:install'), install: () => ipcRenderer.invoke("composer:install"),
uninstall: () => ipcRenderer.invoke('composer:uninstall'), uninstall: () => ipcRenderer.invoke("composer:uninstall"),
setMirror: (mirror: string) => ipcRenderer.invoke('composer:setMirror', mirror), setMirror: (mirror: string) =>
createLaravelProject: (projectName: string, targetDir: string) => ipcRenderer.invoke('composer:createLaravelProject', projectName, targetDir) ipcRenderer.invoke("composer:setMirror", mirror),
createLaravelProject: (projectName: string, targetDir: string) =>
ipcRenderer.invoke(
"composer:createLaravelProject",
projectName,
targetDir,
),
}, },
// MySQL 管理 // MySQL 管理
mysql: { mysql: {
getVersions: () => ipcRenderer.invoke('mysql:getVersions'), getVersions: () => ipcRenderer.invoke("mysql:getVersions"),
getAvailableVersions: () => ipcRenderer.invoke('mysql:getAvailableVersions'), getAvailableVersions: () =>
install: (version: string) => ipcRenderer.invoke('mysql:install', version), ipcRenderer.invoke("mysql:getAvailableVersions"),
uninstall: (version: string) => ipcRenderer.invoke('mysql:uninstall', version), install: (version: string) => ipcRenderer.invoke("mysql:install", version),
start: (version: string) => ipcRenderer.invoke('mysql:start', version), uninstall: (version: string) =>
stop: (version: string) => ipcRenderer.invoke('mysql:stop', version), ipcRenderer.invoke("mysql:uninstall", version),
restart: (version: string) => ipcRenderer.invoke('mysql:restart', version), start: (version: string) => ipcRenderer.invoke("mysql:start", version),
getStatus: (version: string) => ipcRenderer.invoke('mysql:getStatus', version), stop: (version: string) => ipcRenderer.invoke("mysql:stop", version),
changePassword: (version: string, newPassword: string, currentPassword?: string) => ipcRenderer.invoke('mysql:changePassword', version, newPassword, currentPassword), restart: (version: string) => ipcRenderer.invoke("mysql:restart", version),
getConfig: (version: string) => ipcRenderer.invoke('mysql:getConfig', version), getStatus: (version: string) =>
saveConfig: (version: string, config: string) => ipcRenderer.invoke('mysql:saveConfig', version, config) ipcRenderer.invoke("mysql:getStatus", version),
changePassword: (
version: string,
newPassword: string,
currentPassword?: string,
) =>
ipcRenderer.invoke(
"mysql:changePassword",
version,
newPassword,
currentPassword,
),
getConfig: (version: string) =>
ipcRenderer.invoke("mysql:getConfig", version),
saveConfig: (version: string, config: string) =>
ipcRenderer.invoke("mysql:saveConfig", version, config),
}, },
// Nginx 管理 // Nginx 管理
nginx: { nginx: {
getVersions: () => ipcRenderer.invoke('nginx:getVersions'), getVersions: () => ipcRenderer.invoke("nginx:getVersions"),
getAvailableVersions: () => ipcRenderer.invoke('nginx:getAvailableVersions'), getAvailableVersions: () =>
install: (version: string) => ipcRenderer.invoke('nginx:install', version), ipcRenderer.invoke("nginx:getAvailableVersions"),
uninstall: (version: string) => ipcRenderer.invoke('nginx:uninstall', version), install: (version: string) => ipcRenderer.invoke("nginx:install", version),
start: () => ipcRenderer.invoke('nginx:start'), uninstall: (version: string) =>
stop: () => ipcRenderer.invoke('nginx:stop'), ipcRenderer.invoke("nginx:uninstall", version),
restart: () => ipcRenderer.invoke('nginx:restart'), start: () => ipcRenderer.invoke("nginx:start"),
reload: () => ipcRenderer.invoke('nginx:reload'), stop: () => ipcRenderer.invoke("nginx:stop"),
getStatus: () => ipcRenderer.invoke('nginx:getStatus'), restart: () => ipcRenderer.invoke("nginx:restart"),
getConfig: () => ipcRenderer.invoke('nginx:getConfig'), reload: () => ipcRenderer.invoke("nginx:reload"),
saveConfig: (config: string) => ipcRenderer.invoke('nginx:saveConfig', config), getStatus: () => ipcRenderer.invoke("nginx:getStatus"),
getSites: () => ipcRenderer.invoke('nginx:getSites'), getConfig: () => ipcRenderer.invoke("nginx:getConfig"),
addSite: (site: any) => ipcRenderer.invoke('nginx:addSite', site), saveConfig: (config: string) =>
removeSite: (name: string) => ipcRenderer.invoke('nginx:removeSite', name), ipcRenderer.invoke("nginx:saveConfig", config),
updateSite: (originalName: string, site: any) => ipcRenderer.invoke('nginx:updateSite', originalName, site), getSites: () => ipcRenderer.invoke("nginx:getSites"),
enableSite: (name: string) => ipcRenderer.invoke('nginx:enableSite', name), addSite: (site: any) => ipcRenderer.invoke("nginx:addSite", site),
disableSite: (name: string) => ipcRenderer.invoke('nginx:disableSite', name), removeSite: (name: string) => ipcRenderer.invoke("nginx:removeSite", name),
generateLaravelConfig: (site: any) => ipcRenderer.invoke('nginx:generateLaravelConfig', site), updateSite: (originalName: string, site: any) =>
requestSSL: (domain: string, email: string) => ipcRenderer.invoke('nginx:requestSSL', domain, email) ipcRenderer.invoke("nginx:updateSite", originalName, site),
enableSite: (name: string) => ipcRenderer.invoke("nginx:enableSite", name),
disableSite: (name: string) =>
ipcRenderer.invoke("nginx:disableSite", name),
generateLaravelConfig: (site: any) =>
ipcRenderer.invoke("nginx:generateLaravelConfig", site),
requestSSL: (domain: string, email: string) =>
ipcRenderer.invoke("nginx:requestSSL", domain, email),
}, },
// Redis 管理 // Redis 管理
redis: { redis: {
getVersions: () => ipcRenderer.invoke('redis:getVersions'), getVersions: () => ipcRenderer.invoke("redis:getVersions"),
getAvailableVersions: () => ipcRenderer.invoke('redis:getAvailableVersions'), getAvailableVersions: () =>
install: (version: string) => ipcRenderer.invoke('redis:install', version), ipcRenderer.invoke("redis:getAvailableVersions"),
uninstall: (version: string) => ipcRenderer.invoke('redis:uninstall', version), install: (version: string) => ipcRenderer.invoke("redis:install", version),
start: () => ipcRenderer.invoke('redis:start'), uninstall: (version: string) =>
stop: () => ipcRenderer.invoke('redis:stop'), ipcRenderer.invoke("redis:uninstall", version),
restart: () => ipcRenderer.invoke('redis:restart'), start: () => ipcRenderer.invoke("redis:start"),
getStatus: () => ipcRenderer.invoke('redis:getStatus'), stop: () => ipcRenderer.invoke("redis:stop"),
getConfig: () => ipcRenderer.invoke('redis:getConfig'), restart: () => ipcRenderer.invoke("redis:restart"),
saveConfig: (config: string) => ipcRenderer.invoke('redis:saveConfig', config) getStatus: () => ipcRenderer.invoke("redis:getStatus"),
getConfig: () => ipcRenderer.invoke("redis:getConfig"),
saveConfig: (config: string) =>
ipcRenderer.invoke("redis:saveConfig", config),
},
// Go 管理
go: {
getVersions: () => ipcRenderer.invoke("go:getVersions"),
getAvailableVersions: () => ipcRenderer.invoke("go:getAvailableVersions"),
install: (version: string, downloadUrl: string) =>
ipcRenderer.invoke("go:install", version, downloadUrl),
uninstall: (version: string) => ipcRenderer.invoke("go:uninstall", version),
setActive: (version: string) => ipcRenderer.invoke("go:setActive", version),
getInfo: (version: string) => ipcRenderer.invoke("go:getInfo", version),
}, },
// Node.js 管理 // Node.js 管理
node: { node: {
getVersions: () => ipcRenderer.invoke('node:getVersions'), getVersions: () => ipcRenderer.invoke("node:getVersions"),
getAvailableVersions: () => ipcRenderer.invoke('node:getAvailableVersions'), getAvailableVersions: () => ipcRenderer.invoke("node:getAvailableVersions"),
install: (version: string, downloadUrl: string) => ipcRenderer.invoke('node:install', version, downloadUrl), install: (version: string, downloadUrl: string) =>
uninstall: (version: string) => ipcRenderer.invoke('node:uninstall', version), ipcRenderer.invoke("node:install", version, downloadUrl),
setActive: (version: string) => ipcRenderer.invoke('node:setActive', version), uninstall: (version: string) =>
getInfo: (version: string) => ipcRenderer.invoke('node:getInfo', version) ipcRenderer.invoke("node:uninstall", version),
setActive: (version: string) =>
ipcRenderer.invoke("node:setActive", version),
getInfo: (version: string) => ipcRenderer.invoke("node:getInfo", version),
}, },
// Git 管理 // Git 管理
git: { git: {
getVersions: () => ipcRenderer.invoke('git:getVersions'), getVersions: () => ipcRenderer.invoke("git:getVersions"),
getAvailableVersions: () => ipcRenderer.invoke('git:getAvailableVersions'), getAvailableVersions: () => ipcRenderer.invoke("git:getAvailableVersions"),
install: (version: string) => ipcRenderer.invoke('git:install', version), install: (version: string) => ipcRenderer.invoke("git:install", version),
uninstall: () => ipcRenderer.invoke('git:uninstall'), uninstall: () => ipcRenderer.invoke("git:uninstall"),
checkSystem: () => ipcRenderer.invoke('git:checkSystem'), checkSystem: () => ipcRenderer.invoke("git:checkSystem"),
getConfig: () => ipcRenderer.invoke('git:getConfig'), getConfig: () => ipcRenderer.invoke("git:getConfig"),
setConfig: (name: string, email: string) => ipcRenderer.invoke('git:setConfig', name, email) setConfig: (name: string, email: string) =>
ipcRenderer.invoke("git:setConfig", name, email),
}, },
// Python 管理 // Python 管理
python: { python: {
getVersions: () => ipcRenderer.invoke('python:getVersions'), getVersions: () => ipcRenderer.invoke("python:getVersions"),
getAvailableVersions: () => ipcRenderer.invoke('python:getAvailableVersions'), getAvailableVersions: () =>
install: (version: string) => ipcRenderer.invoke('python:install', version), ipcRenderer.invoke("python:getAvailableVersions"),
uninstall: (version: string) => ipcRenderer.invoke('python:uninstall', version), install: (version: string) => ipcRenderer.invoke("python:install", version),
setActive: (version: string) => ipcRenderer.invoke('python:setActive', version), uninstall: (version: string) =>
checkSystem: () => ipcRenderer.invoke('python:checkSystem'), ipcRenderer.invoke("python:uninstall", version),
getPipInfo: (version: string) => ipcRenderer.invoke('python:getPipInfo', version), setActive: (version: string) =>
installPackage: (version: string, packageName: string) => ipcRenderer.invoke('python:installPackage', version, packageName) ipcRenderer.invoke("python:setActive", version),
checkSystem: () => ipcRenderer.invoke("python:checkSystem"),
getPipInfo: (version: string) =>
ipcRenderer.invoke("python:getPipInfo", version),
installPackage: (version: string, packageName: string) =>
ipcRenderer.invoke("python:installPackage", version, packageName),
}, },
// 服务管理 // 服务管理
service: { service: {
getAll: () => ipcRenderer.invoke('service:getAll'), getAll: () => ipcRenderer.invoke("service:getAll"),
setAutoStart: (service: string, enabled: boolean) => ipcRenderer.invoke('service:setAutoStart', service, enabled), setAutoStart: (service: string, enabled: boolean) =>
getAutoStart: (service: string) => ipcRenderer.invoke('service:getAutoStart', service), ipcRenderer.invoke("service:setAutoStart", service, enabled),
startAll: () => ipcRenderer.invoke('service:startAll'), getAutoStart: (service: string) =>
stopAll: () => ipcRenderer.invoke('service:stopAll'), ipcRenderer.invoke("service:getAutoStart", service),
startAll: () => ipcRenderer.invoke("service:startAll"),
stopAll: () => ipcRenderer.invoke("service:stopAll"),
// PHP-CGI 多版本管理 // PHP-CGI 多版本管理
getPhpCgiStatus: () => ipcRenderer.invoke('service:getPhpCgiStatus'), getPhpCgiStatus: () => ipcRenderer.invoke("service:getPhpCgiStatus"),
startPhpCgi: () => ipcRenderer.invoke('service:startPhpCgi'), startPhpCgi: () => ipcRenderer.invoke("service:startPhpCgi"),
stopPhpCgi: () => ipcRenderer.invoke('service:stopPhpCgi'), stopPhpCgi: () => ipcRenderer.invoke("service:stopPhpCgi"),
startAllPhpCgi: () => ipcRenderer.invoke('service:startAllPhpCgi'), startAllPhpCgi: () => ipcRenderer.invoke("service:startAllPhpCgi"),
stopAllPhpCgi: () => ipcRenderer.invoke('service:stopAllPhpCgi'), stopAllPhpCgi: () => ipcRenderer.invoke("service:stopAllPhpCgi"),
startPhpCgiVersion: (version: string) => ipcRenderer.invoke('service:startPhpCgiVersion', version), startPhpCgiVersion: (version: string) =>
stopPhpCgiVersion: (version: string) => ipcRenderer.invoke('service:stopPhpCgiVersion', version), ipcRenderer.invoke("service:startPhpCgiVersion", version),
getPhpCgiPort: (version: string) => ipcRenderer.invoke('service:getPhpCgiPort', version) stopPhpCgiVersion: (version: string) =>
ipcRenderer.invoke("service:stopPhpCgiVersion", version),
getPhpCgiPort: (version: string) =>
ipcRenderer.invoke("service:getPhpCgiPort", version),
}, },
// Hosts 管理 // Hosts 管理
hosts: { hosts: {
get: () => ipcRenderer.invoke('hosts:get'), get: () => ipcRenderer.invoke("hosts:get"),
add: (domain: string, ip: string) => ipcRenderer.invoke('hosts:add', domain, ip), add: (domain: string, ip: string) =>
remove: (domain: string) => ipcRenderer.invoke('hosts:remove', domain) ipcRenderer.invoke("hosts:add", domain, ip),
remove: (domain: string) => ipcRenderer.invoke("hosts:remove", domain),
}, },
// 配置管理 // 配置管理
config: { config: {
get: (key: string) => ipcRenderer.invoke('config:get', key), get: (key: string) => ipcRenderer.invoke("config:get", key),
set: (key: string, value: any) => ipcRenderer.invoke('config:set', key, value), set: (key: string, value: any) =>
getBasePath: () => ipcRenderer.invoke('config:getBasePath'), ipcRenderer.invoke("config:set", key, value),
setBasePath: (path: string) => ipcRenderer.invoke('config:setBasePath', path) getBasePath: () => ipcRenderer.invoke("config:getBasePath"),
setBasePath: (path: string) =>
ipcRenderer.invoke("config:setBasePath", path),
}, },
// 日志管理 // 日志管理
log: { log: {
getFiles: () => ipcRenderer.invoke('log:getFiles'), getFiles: () => ipcRenderer.invoke("log:getFiles"),
read: (logPath: string, lines?: number) => ipcRenderer.invoke('log:read', logPath, lines), read: (logPath: string, lines?: number) =>
clear: (logPath: string) => ipcRenderer.invoke('log:clear', logPath), ipcRenderer.invoke("log:read", logPath, lines),
getDirectory: (type: 'nginx' | 'php' | 'mysql' | 'sites', version?: string) => clear: (logPath: string) => ipcRenderer.invoke("log:clear", logPath),
ipcRenderer.invoke('log:getDirectory', type, version) getDirectory: (
type: "nginx" | "php" | "mysql" | "sites",
version?: string,
) => ipcRenderer.invoke("log:getDirectory", type, version),
}, },
// 应用设置 // 应用设置
app: { app: {
setAutoLaunch: (enabled: boolean) => ipcRenderer.invoke('app:setAutoLaunch', enabled), setAutoLaunch: (enabled: boolean) =>
getAutoLaunch: () => ipcRenderer.invoke('app:getAutoLaunch'), ipcRenderer.invoke("app:setAutoLaunch", enabled),
setStartMinimized: (enabled: boolean) => ipcRenderer.invoke('app:setStartMinimized', enabled), getAutoLaunch: () => ipcRenderer.invoke("app:getAutoLaunch"),
getStartMinimized: () => ipcRenderer.invoke('app:getStartMinimized'), setStartMinimized: (enabled: boolean) =>
getVersion: () => ipcRenderer.invoke('app:getVersion') as Promise<{ version: string; buildTime: string; buildDate: string; isPackaged: boolean }>, ipcRenderer.invoke("app:setStartMinimized", enabled),
setAutoStartServices: (enabled: boolean) => ipcRenderer.invoke('app:setAutoStartServices', enabled), getStartMinimized: () => ipcRenderer.invoke("app:getStartMinimized"),
getAutoStartServices: () => ipcRenderer.invoke('app:getAutoStartServices'), getVersion: () =>
quit: () => ipcRenderer.invoke('app:quit') ipcRenderer.invoke("app:getVersion") as Promise<{
version: string;
buildTime: string;
buildDate: string;
isPackaged: boolean;
}>,
setAutoStartServices: (enabled: boolean) =>
ipcRenderer.invoke("app:setAutoStartServices", enabled),
getAutoStartServices: () => ipcRenderer.invoke("app:getAutoStartServices"),
quit: () => ipcRenderer.invoke("app:quit"),
}, },
// 监听服务状态变化 // 监听服务状态变化
onServiceStatusChanged: (callback: () => void) => { onServiceStatusChanged: (callback: () => void) => {
ipcRenderer.on('service-status-changed', callback) ipcRenderer.on("service-status-changed", callback);
}, },
removeServiceStatusChangedListener: (callback: () => void) => { removeServiceStatusChangedListener: (callback: () => void) => {
ipcRenderer.removeListener('service-status-changed', callback) ipcRenderer.removeListener("service-status-changed", callback);
} },
}) });
// 声明 Window 接口扩展 // 声明 Window 接口扩展
declare global { declare global {
interface Window { interface Window {
electronAPI: typeof api electronAPI: typeof api;
} }
} }
const api = { const api = {
minimize: () => ipcRenderer.invoke('window:minimize'), minimize: () => ipcRenderer.invoke("window:minimize"),
maximize: () => ipcRenderer.invoke('window:maximize'), maximize: () => ipcRenderer.invoke("window:maximize"),
close: () => ipcRenderer.invoke('window:close'), close: () => ipcRenderer.invoke("window:close"),
openExternal: (url: string) => ipcRenderer.invoke('shell:openExternal', url), openExternal: (url: string) => ipcRenderer.invoke("shell:openExternal", url),
openPath: (path: string) => ipcRenderer.invoke('shell:openPath', path), openPath: (path: string) => ipcRenderer.invoke("shell:openPath", path),
php: {} as any, php: {} as any,
mysql: {} as any, mysql: {} as any,
nginx: {} as any, nginx: {} as any,
redis: {} as any, redis: {} as any,
service: {} as any, service: {} as any,
hosts: {} as any, hosts: {} as any,
config: {} as any config: {} as any,
} };

View File

@ -10,8 +10,10 @@ interface ConfigSchema {
nginxVersions: string[]; nginxVersions: string[];
redisVersions: string[]; redisVersions: string[];
nodeVersions: string[]; nodeVersions: string[];
goVersions: string[];
activePhpVersion: string; activePhpVersion: string;
activeNodeVersion: string; activeNodeVersion: string;
activeGoVersion: string;
autoStart: { autoStart: {
nginx: boolean; nginx: boolean;
mysql: boolean; mysql: boolean;
@ -66,8 +68,10 @@ export class ConfigStore {
nginxVersions: [], nginxVersions: [],
redisVersions: [], redisVersions: [],
nodeVersions: [], nodeVersions: [],
goVersions: [],
activePhpVersion: "", activePhpVersion: "",
activeNodeVersion: "", activeNodeVersion: "",
activeGoVersion: "",
autoStart: { autoStart: {
nginx: false, nginx: false,
mysql: false, mysql: false,
@ -99,6 +103,7 @@ export class ConfigStore {
join(this.basePath, "nginx", "ssl"), join(this.basePath, "nginx", "ssl"),
join(this.basePath, "redis"), join(this.basePath, "redis"),
join(this.basePath, "nodejs"), join(this.basePath, "nodejs"),
join(this.basePath, "go"),
join(this.basePath, "logs"), join(this.basePath, "logs"),
join(this.basePath, "temp"), join(this.basePath, "temp"),
join(this.basePath, "www"), join(this.basePath, "www"),
@ -149,6 +154,10 @@ export class ConfigStore {
return join(this.basePath, "nodejs"); return join(this.basePath, "nodejs");
} }
getGoPath(): string {
return join(this.basePath, "go");
}
getLogsPath(): string { getLogsPath(): string {
return join(this.basePath, "logs"); return join(this.basePath, "logs");
} }

View File

@ -0,0 +1,457 @@
import { ConfigStore } from "./ConfigStore";
import { exec } from "child_process";
import { promisify } from "util";
import {
existsSync,
mkdirSync,
readdirSync,
rmSync,
unlinkSync,
renameSync,
} from "fs";
import { join } from "path";
import https from "https";
import http from "http";
import { createWriteStream } from "fs";
import unzipper from "unzipper";
import { sendDownloadProgress } from "../main";
const execAsync = promisify(exec);
interface GoVersion {
version: string;
path: string;
isActive: boolean;
}
interface AvailableGoVersion {
version: string;
stable: boolean;
downloadUrl: string;
filename: string;
}
export class GoManager {
private configStore: ConfigStore;
private versionsCache: AvailableGoVersion[] = [];
private cacheTime: number = 0;
private readonly CACHE_DURATION = 5 * 60 * 1000; // 5 分钟缓存
constructor(configStore: ConfigStore) {
this.configStore = configStore;
}
/**
* Go
*/
async getInstalledVersions(): Promise<GoVersion[]> {
const versions: GoVersion[] = [];
const goPath = this.configStore.getGoPath();
const activeVersion = this.configStore.get("activeGoVersion") || "";
if (!existsSync(goPath)) {
return versions;
}
const dirs = readdirSync(goPath, { withFileTypes: true });
for (const dir of dirs) {
if (dir.isDirectory() && dir.name.startsWith("go-")) {
const versionDir = join(goPath, dir.name);
const goExe = join(versionDir, "bin", "go.exe");
if (existsSync(goExe)) {
const version = dir.name.replace("go-", "");
versions.push({
version,
path: versionDir,
isActive: version === activeVersion,
});
}
}
}
versions.sort((a, b) => {
const aParts = a.version.replace("go", "").split(".").map(Number);
const bParts = b.version.replace("go", "").split(".").map(Number);
for (let i = 0; i < 3; i++) {
if (aParts[i] !== bParts[i]) return bParts[i] - aParts[i];
}
return 0;
});
return versions;
}
/**
* go.dev
*/
async getAvailableVersions(): Promise<AvailableGoVersion[]> {
if (
this.versionsCache.length > 0 &&
Date.now() - this.cacheTime < this.CACHE_DURATION
) {
return this.versionsCache;
}
try {
const versions = await this.fetchVersionsFromGoDev();
if (versions.length > 0) {
this.versionsCache = versions;
this.cacheTime = Date.now();
return versions;
}
} catch (error) {
console.error("[Go] Failed to fetch versions:", error);
}
return this.getFallbackVersions();
}
/**
* go.dev/dl API
*/
private async fetchVersionsFromGoDev(): Promise<AvailableGoVersion[]> {
return new Promise((resolve, reject) => {
const url = "https://go.dev/dl/?mode=json";
https
.get(
url,
{
headers: { "User-Agent": "PHPer-Dev-Manager/1.0" },
timeout: 30000,
},
(res) => {
let data = "";
res.on("data", (chunk) => (data += chunk));
res.on("end", () => {
try {
const releases = JSON.parse(data);
const versions: AvailableGoVersion[] = [];
for (const rel of releases) {
const winZip = rel.files?.find(
(f: any) =>
f.os === "windows" &&
f.arch === "amd64" &&
f.filename?.endsWith(".zip"),
);
if (winZip) {
versions.push({
version: rel.version,
stable: rel.stable ?? true,
downloadUrl: `https://go.dev/dl/${winZip.filename}`,
filename: winZip.filename,
});
}
}
resolve(versions.slice(0, 30));
} catch (e) {
reject(e);
}
});
},
)
.on("error", reject)
.on("timeout", () => reject(new Error("Request timeout")));
});
}
/**
* Go
*/
async install(
version: string,
downloadUrl: string,
): Promise<{ success: boolean; message: string }> {
try {
const goPath = this.configStore.getGoPath();
const tempPath = this.configStore.getTempPath();
const zipPath = join(tempPath, `go-${version}.zip`);
const extractDir = join(tempPath, `go-extract-${version}`);
const versionDir = join(goPath, `go-${version}`);
if (!existsSync(goPath)) mkdirSync(goPath, { recursive: true });
if (!existsSync(tempPath)) mkdirSync(tempPath, { recursive: true });
if (
existsSync(versionDir) &&
existsSync(join(versionDir, "bin", "go.exe"))
) {
return { success: false, message: `Go ${version} 已安装` };
}
console.log(`[Go] Downloading ${version}...`);
await this.downloadFile(downloadUrl, zipPath, `go-${version}`);
console.log(`[Go] Extracting ${version}...`);
await this.extractZip(zipPath, extractDir);
// Go zip 解压后根目录是 "go" 文件夹,需要重命名为 go-version
const innerGoDir = join(extractDir, "go");
if (existsSync(innerGoDir)) {
renameSync(innerGoDir, versionDir);
} else {
return { success: false, message: "解压失败:未找到 go 目录" };
}
try {
unlinkSync(zipPath);
rmSync(extractDir, { recursive: true, force: true });
} catch (e) {
// 忽略清理错误
}
if (!existsSync(join(versionDir, "bin", "go.exe"))) {
return { success: false, message: "安装失败go.exe 不存在" };
}
const goVersions = this.configStore.get("goVersions") || [];
if (!goVersions.includes(version)) {
goVersions.push(version);
this.configStore.set("goVersions", goVersions);
}
if (goVersions.length === 1) {
await this.setActive(version);
}
return { success: true, message: `Go ${version} 安装成功` };
} catch (error: any) {
return { success: false, message: `安装失败: ${error.message}` };
}
}
/**
* Go
*/
async uninstall(
version: string,
): Promise<{ success: boolean; message: string }> {
try {
const goPath = this.configStore.getGoPath();
const versionDir = join(goPath, `go-${version}`);
if (!existsSync(versionDir)) {
return { success: false, message: `Go ${version} 未安装` };
}
const activeVersion = this.configStore.get("activeGoVersion");
if (activeVersion === version) {
this.configStore.set("activeGoVersion", "");
}
rmSync(versionDir, { recursive: true, force: true });
const goVersions = this.configStore.get("goVersions") || [];
const index = goVersions.indexOf(version);
if (index > -1) {
goVersions.splice(index, 1);
this.configStore.set("goVersions", goVersions);
}
return { success: true, message: `Go ${version} 已卸载` };
} catch (error: any) {
return { success: false, message: `卸载失败: ${error.message}` };
}
}
/**
* Go
*/
async setActive(
version: string,
): Promise<{ success: boolean; message: string }> {
try {
const goPath = this.configStore.getGoPath();
const versionDir = join(goPath, `go-${version}`);
if (!existsSync(join(versionDir, "bin", "go.exe"))) {
return { success: false, message: `Go ${version} 未安装` };
}
await this.addToPath(versionDir);
this.configStore.set("activeGoVersion", version);
return { success: true, message: `已将 Go ${version} 设为默认版本` };
} catch (error: any) {
return { success: false, message: `设置失败: ${error.message}` };
}
}
/**
* Go
*/
async getGoInfo(
version: string,
): Promise<{ goVersion: string; path: string } | null> {
const goPath = this.configStore.getGoPath();
const versionDir = join(goPath, `go-${version}`);
const goExe = join(versionDir, "bin", "go.exe");
if (!existsSync(goExe)) return null;
try {
const { stdout } = await execAsync(`"${goExe}" version`, {
timeout: 5000,
});
return {
goVersion: stdout.trim(),
path: versionDir,
};
} catch {
return null;
}
}
private async downloadFile(
url: string,
dest: string,
name: string,
): Promise<void> {
return new Promise((resolve, reject) => {
const protocol = url.startsWith("https") ? https : http;
const request = protocol.get(
url,
{
headers: { "User-Agent": "PHPer-Dev-Manager/1.0" },
timeout: 600000,
},
(response) => {
if (response.statusCode === 301 || response.statusCode === 302) {
const redirectUrl = response.headers.location;
if (redirectUrl) {
this.downloadFile(redirectUrl, dest, name)
.then(resolve)
.catch(reject);
return;
}
}
if (response.statusCode !== 200) {
reject(new Error(`Download failed: HTTP ${response.statusCode}`));
return;
}
const totalSize = parseInt(
response.headers["content-length"] || "0",
10,
);
let downloadedSize = 0;
let lastProgressTime = 0;
const file = createWriteStream(dest);
response.on("data", (chunk) => {
downloadedSize += chunk.length;
const now = Date.now();
if (now - lastProgressTime > 500) {
const progress =
totalSize > 0
? Math.round((downloadedSize / totalSize) * 100)
: 0;
sendDownloadProgress("go", progress, downloadedSize, totalSize);
lastProgressTime = now;
}
});
response.pipe(file);
file.on("finish", () => {
file.close();
sendDownloadProgress("go", 100, totalSize, totalSize);
resolve();
});
file.on("error", (err) => {
unlinkSync(dest);
reject(err);
});
},
);
request.on("error", reject);
request.on("timeout", () => {
request.destroy();
reject(new Error("Download timeout"));
});
});
}
private async extractZip(zipPath: string, destDir: string): Promise<void> {
return new Promise((resolve, reject) => {
const { createReadStream } = require("fs");
createReadStream(zipPath)
.pipe(unzipper.Extract({ path: destDir }))
.on("close", resolve)
.on("error", reject);
});
}
private async addToPath(goPath: string): Promise<void> {
const binPath = join(goPath, "bin");
const psScript = `
$ErrorActionPreference = 'Stop'
$newPath = '${binPath.replace(/\\/g, "\\\\")}'
$currentPath = [Environment]::GetEnvironmentVariable('Path', 'User')
$pathArray = $currentPath -split ';' | Where-Object { $_ -ne '' }
$filteredPaths = $pathArray | Where-Object {
$p = $_.ToLower()
-not ($p -like '*\\\\go-*\\\\bin*' -or $p -like '*\\\\go\\\\bin*' -or $p -like '*phper*go*')
}
$newPathArray = @($newPath) + $filteredPaths
$finalPath = ($newPathArray | Select-Object -Unique) -join ';'
[Environment]::SetEnvironmentVariable('Path', $finalPath, 'User')
Write-Output "PATH updated"
`;
const tempPs1 = join(this.configStore.getTempPath(), "update_go_path.ps1");
const { writeFileSync } = require("fs");
writeFileSync(tempPs1, psScript, "utf-8");
try {
await execAsync(`powershell -ExecutionPolicy Bypass -File "${tempPs1}"`, {
timeout: 30000,
});
} finally {
try {
unlinkSync(tempPs1);
} catch (e) {}
}
}
private getFallbackVersions(): AvailableGoVersion[] {
return [
{
version: "go1.25.7",
stable: true,
downloadUrl: "https://go.dev/dl/go1.25.7.windows-amd64.zip",
filename: "go1.25.7.windows-amd64.zip",
},
{
version: "go1.24.13",
stable: true,
downloadUrl: "https://go.dev/dl/go1.24.13.windows-amd64.zip",
filename: "go1.24.13.windows-amd64.zip",
},
{
version: "go1.23.5",
stable: true,
downloadUrl: "https://go.dev/dl/go1.23.5.windows-amd64.zip",
filename: "go1.23.5.windows-amd64.zip",
},
{
version: "go1.22.14",
stable: true,
downloadUrl: "https://go.dev/dl/go1.22.14.windows-amd64.zip",
filename: "go1.22.14.windows-amd64.zip",
},
];
}
}

View File

@ -1,6 +1,6 @@
{ {
"name": "phper-dev-manager", "name": "phper-dev-manager",
"version": "1.0.8", "version": "1.0.10",
"description": "PHP开发环境管理器 - 管理PHP、MySQL、Nginx、Redis服务", "description": "PHP开发环境管理器 - 管理PHP、MySQL、Nginx、Redis服务",
"main": "dist-electron/main.js", "main": "dist-electron/main.js",
"scripts": { "scripts": {

View File

@ -1,5 +1,5 @@
{ {
"version": "1.0.8", "version": "1.0.10",
"buildTime": "2026-01-03T18:53:37.506Z", "buildTime": "2026-02-05T01:05:37.725Z",
"buildDate": "2026/1/4" "buildDate": "2026/2/5"
} }

View File

@ -34,15 +34,16 @@
:key="item.path" :key="item.path"
:to="item.path" :to="item.path"
class="nav-item" class="nav-item"
:class="{ active: $route.path === item.path }" :class="{ active: $route.path === item.path }">
>
<el-icon class="nav-icon"><component :is="item.icon" /></el-icon> <el-icon class="nav-icon"><component :is="item.icon" /></el-icon>
<span class="nav-label">{{ item.label }}</span> <span class="nav-label">{{ item.label }}</span>
<span <span
v-if="item.service" v-if="item.service"
class="status-dot" class="status-dot"
:class="{ running: serviceStatus[item.service as keyof typeof serviceStatus] }" :class="{
></span> running:
serviceStatus[item.service as keyof typeof serviceStatus],
}"></span>
</router-link> </router-link>
</nav> </nav>
@ -73,299 +74,309 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue' import { ref, computed, onMounted, onUnmounted } from "vue";
import { ElMessage } from 'element-plus' import { ElMessage } from "element-plus";
import { useServiceStore } from './stores/serviceStore' import { useServiceStore } from "./stores/serviceStore";
const store = useServiceStore() const store = useServiceStore();
const isDark = ref(true) const isDark = ref(true);
const startingAll = ref(false) const startingAll = ref(false);
const stoppingAll = ref(false) const stoppingAll = ref(false);
// - // -
const cachedViews = [ const cachedViews = [
'Dashboard', "Dashboard",
'PhpManager', "PhpManager",
'MysqlManager', "MysqlManager",
'NginxManager', "NginxManager",
'RedisManager', "RedisManager",
'NodeManager', "NodeManager",
'PythonManager', "GoManager",
'GitManager', "PythonManager",
'SitesManager', "GitManager",
'HostsManager', "SitesManager",
'Settings' "HostsManager",
] "Settings",
];
// store // store
const serviceStatus = computed(() => ({ const serviceStatus = computed(() => ({
nginx: store.serviceStatus.nginx, nginx: store.serviceStatus.nginx,
mysql: store.serviceStatus.mysql, mysql: store.serviceStatus.mysql,
redis: store.serviceStatus.redis redis: store.serviceStatus.redis,
})) }));
const menuItems = [ const menuItems = [
{ path: '/', label: '仪表盘', icon: 'Odometer', service: null }, { path: "/", label: "仪表盘", icon: "Odometer", service: null },
{ path: '/php', label: 'PHP 管理', icon: 'Files', service: null }, { path: "/php", label: "PHP 管理", icon: "Files", service: null },
{ path: '/mysql', label: 'MySQL 管理', icon: 'Coin', service: 'mysql' }, { path: "/mysql", label: "MySQL 管理", icon: "Coin", service: "mysql" },
{ path: '/nginx', label: 'Nginx 管理', icon: 'Connection', service: 'nginx' }, {
{ path: '/redis', label: 'Redis 管理', icon: 'Grid', service: 'redis' }, path: "/nginx",
{ path: '/nodejs', label: 'Node.js 管理', icon: 'Promotion', service: null }, label: "Nginx 管理",
{ path: '/python', label: 'Python 管理', icon: 'Platform', service: null }, icon: "Connection",
{ path: '/git', label: 'Git 管理', icon: 'Share', service: null }, service: "nginx",
{ path: '/sites', label: '站点管理', icon: 'Monitor', service: null }, },
{ path: '/hosts', label: 'Hosts 管理', icon: 'Document', service: null }, { path: "/redis", label: "Redis 管理", icon: "Grid", service: "redis" },
{ path: '/settings', label: '设置', icon: 'Setting', service: null } {
] path: "/nodejs",
label: "Node.js 管理",
icon: "Promotion",
service: null,
},
{ path: "/go", label: "Go 管理", icon: "Aim", service: null },
{ path: "/python", label: "Python 管理", icon: "Platform", service: null },
{ path: "/git", label: "Git 管理", icon: "Share", service: null },
{ path: "/sites", label: "站点管理", icon: "Monitor", service: null },
{ path: "/hosts", label: "Hosts 管理", icon: "Document", service: null },
{ path: "/settings", label: "设置", icon: "Setting", service: null },
];
let statusInterval: ReturnType<typeof setInterval> | null = null let statusInterval: ReturnType<typeof setInterval> | null = null;
// //
const minimize = () => window.electronAPI?.minimize() const minimize = () => window.electronAPI?.minimize();
const maximize = () => window.electronAPI?.maximize() const maximize = () => window.electronAPI?.maximize();
const close = () => window.electronAPI?.close() const close = () => window.electronAPI?.close();
// //
const toggleDark = () => { const toggleDark = () => {
isDark.value = !isDark.value isDark.value = !isDark.value;
document.documentElement.classList.toggle('dark', isDark.value) document.documentElement.classList.toggle("dark", isDark.value);
} };
// //
const startAll = async () => { const startAll = async () => {
startingAll.value = true startingAll.value = true;
try { try {
const result = await window.electronAPI?.service.startAll() const result = await window.electronAPI?.service.startAll();
if (result?.success) { if (result?.success) {
ElMessage.success(result.message) ElMessage.success(result.message);
// //
setTimeout(() => store.refreshServiceStatus(), 2000) setTimeout(() => store.refreshServiceStatus(), 2000);
} else { } else {
ElMessage.error(result?.message || '启动失败') ElMessage.error(result?.message || "启动失败");
}
} catch (error: any) {
ElMessage.error(error.message);
} finally {
startingAll.value = false;
} }
} catch (error: any) { };
ElMessage.error(error.message)
} finally {
startingAll.value = false
}
}
// //
const stopAll = async () => { const stopAll = async () => {
stoppingAll.value = true stoppingAll.value = true;
try { try {
const result = await window.electronAPI?.service.stopAll() const result = await window.electronAPI?.service.stopAll();
if (result?.success) { if (result?.success) {
ElMessage.success(result.message) ElMessage.success(result.message);
await store.refreshServiceStatus() await store.refreshServiceStatus();
} else { } else {
ElMessage.error(result?.message || '停止失败') ElMessage.error(result?.message || "停止失败");
}
} catch (error: any) {
ElMessage.error(error.message);
} finally {
stoppingAll.value = false;
} }
} catch (error: any) { };
ElMessage.error(error.message)
} finally {
stoppingAll.value = false
}
}
onMounted(() => { onMounted(() => {
document.documentElement.classList.add('dark') document.documentElement.classList.add("dark");
// //
store.refreshAll() store.refreshAll();
// 5 // 5
statusInterval = setInterval(() => store.refreshServiceStatus(), 5000) statusInterval = setInterval(() => store.refreshServiceStatus(), 5000);
}) });
onUnmounted(() => { onUnmounted(() => {
if (statusInterval) { if (statusInterval) {
clearInterval(statusInterval) clearInterval(statusInterval);
} }
}) });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.app-container { .app-container {
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background: var(--bg-primary); background: var(--bg-primary);
color: var(--text-primary);
overflow: hidden;
}
.title-bar {
height: 40px;
display: flex;
justify-content: space-between;
align-items: center;
background: var(--bg-titlebar);
border-bottom: 1px solid var(--border-color);
-webkit-app-region: drag;
padding: 0 12px;
}
.title-bar-left {
display: flex;
align-items: center;
gap: 12px;
}
.app-logo {
display: flex;
align-items: center;
gap: 8px;
.logo-icon {
width: 24px;
height: 24px;
}
.app-name {
font-size: 14px;
font-weight: 600;
color: var(--text-primary);
font-family: 'Noto Sans SC', 'Microsoft YaHei', sans-serif;
}
}
.title-bar-right {
display: flex;
gap: 4px;
-webkit-app-region: no-drag;
}
.title-btn {
width: 36px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: none;
border-radius: 6px;
color: var(--text-secondary);
cursor: pointer;
transition: all 0.2s;
&:hover {
background: var(--bg-hover);
color: var(--text-primary); color: var(--text-primary);
overflow: hidden;
} }
&.close-btn:hover { .title-bar {
background: #e81123; height: 40px;
color: white; display: flex;
} justify-content: space-between;
} align-items: center;
background: var(--bg-titlebar);
.main-container { border-bottom: 1px solid var(--border-color);
flex: 1; -webkit-app-region: drag;
display: flex; padding: 0 12px;
overflow: hidden;
}
.sidebar {
width: 220px;
background: var(--bg-sidebar);
border-right: 1px solid var(--border-color);
display: flex;
flex-direction: column;
padding: 16px 12px;
}
.nav-menu {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
}
.nav-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
border-radius: 10px;
text-decoration: none;
color: var(--text-secondary);
transition: all 0.2s;
&:hover {
background: var(--bg-hover);
color: var(--text-primary);
} }
&.active { .title-bar-left {
background: var(--accent-gradient); display: flex;
color: white; align-items: center;
box-shadow: 0 4px 12px rgba(123, 97, 255, 0.3); gap: 12px;
}
.app-logo {
display: flex;
align-items: center;
gap: 8px;
.logo-icon {
width: 24px;
height: 24px;
}
.app-name {
font-size: 14px;
font-weight: 600;
color: var(--text-primary);
font-family: "Noto Sans SC", "Microsoft YaHei", sans-serif;
}
}
.title-bar-right {
display: flex;
gap: 4px;
-webkit-app-region: no-drag;
}
.title-btn {
width: 36px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: none;
border-radius: 6px;
color: var(--text-secondary);
cursor: pointer;
transition: all 0.2s;
&:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
&.close-btn:hover {
background: #e81123;
color: white;
}
}
.main-container {
flex: 1;
display: flex;
overflow: hidden;
}
.sidebar {
width: 220px;
background: var(--bg-sidebar);
border-right: 1px solid var(--border-color);
display: flex;
flex-direction: column;
padding: 16px 12px;
}
.nav-menu {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
}
.nav-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
border-radius: 10px;
text-decoration: none;
color: var(--text-secondary);
transition: all 0.2s;
&:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
&.active {
background: var(--accent-gradient);
color: white;
box-shadow: 0 4px 12px rgba(123, 97, 255, 0.3);
.status-dot {
border-color: rgba(255, 255, 255, 0.3);
}
}
.nav-icon {
font-size: 20px;
}
.nav-label {
font-size: 14px;
font-weight: 500;
flex: 1;
}
.status-dot { .status-dot {
border-color: rgba(255, 255, 255, 0.3); width: 8px;
height: 8px;
border-radius: 50%;
background: #6b7280;
border: 2px solid var(--bg-sidebar);
transition: all 0.3s;
&.running {
background: #10b981;
box-shadow: 0 0 8px rgba(16, 185, 129, 0.6);
}
} }
} }
.nav-icon { .sidebar-footer {
font-size: 20px; padding-top: 16px;
border-top: 1px solid var(--border-color);
} }
.nav-label { .quick-actions {
font-size: 14px; display: flex;
font-weight: 500; flex-direction: column;
gap: 10px;
padding: 0 12px;
:deep(.el-button) {
width: 100% !important;
height: 40px !important;
min-width: 100% !important;
max-width: 100% !important;
font-size: 14px !important;
justify-content: center !important;
border-radius: 8px !important;
padding: 0 16px !important;
margin-left: 0 !important;
}
:deep(.el-button + .el-button) {
margin-left: 0 !important;
}
}
.content {
flex: 1; flex: 1;
padding: 24px;
overflow-y: auto;
background: var(--bg-content);
} }
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #6b7280;
border: 2px solid var(--bg-sidebar);
transition: all 0.3s;
&.running {
background: #10b981;
box-shadow: 0 0 8px rgba(16, 185, 129, 0.6);
}
}
}
.sidebar-footer {
padding-top: 16px;
border-top: 1px solid var(--border-color);
}
.quick-actions {
display: flex;
flex-direction: column;
gap: 10px;
padding: 0 12px;
:deep(.el-button) {
width: 100% !important;
height: 40px !important;
min-width: 100% !important;
max-width: 100% !important;
font-size: 14px !important;
justify-content: center !important;
border-radius: 8px !important;
padding: 0 16px !important;
margin-left: 0 !important;
}
:deep(.el-button + .el-button) {
margin-left: 0 !important;
}
}
.content {
flex: 1;
padding: 24px;
overflow-y: auto;
background: var(--bg-content);
}
</style> </style>

View File

@ -1,76 +1,81 @@
import { createRouter, createWebHashHistory } from 'vue-router' import { createRouter, createWebHashHistory } from "vue-router";
const router = createRouter({ const router = createRouter({
history: createWebHashHistory(), history: createWebHashHistory(),
routes: [ routes: [
{ {
path: '/', path: "/",
name: 'dashboard', name: "dashboard",
component: () => import('@/views/Dashboard.vue'), component: () => import("@/views/Dashboard.vue"),
meta: { title: '仪表盘' } meta: { title: "仪表盘" },
}, },
{ {
path: '/php', path: "/php",
name: 'php', name: "php",
component: () => import('@/views/PhpManager.vue'), component: () => import("@/views/PhpManager.vue"),
meta: { title: 'PHP 管理' } meta: { title: "PHP 管理" },
}, },
{ {
path: '/mysql', path: "/mysql",
name: 'mysql', name: "mysql",
component: () => import('@/views/MysqlManager.vue'), component: () => import("@/views/MysqlManager.vue"),
meta: { title: 'MySQL 管理' } meta: { title: "MySQL 管理" },
}, },
{ {
path: '/nginx', path: "/nginx",
name: 'nginx', name: "nginx",
component: () => import('@/views/NginxManager.vue'), component: () => import("@/views/NginxManager.vue"),
meta: { title: 'Nginx 管理' } meta: { title: "Nginx 管理" },
}, },
{ {
path: '/redis', path: "/redis",
name: 'redis', name: "redis",
component: () => import('@/views/RedisManager.vue'), component: () => import("@/views/RedisManager.vue"),
meta: { title: 'Redis 管理' } meta: { title: "Redis 管理" },
}, },
{ {
path: '/nodejs', path: "/nodejs",
name: 'nodejs', name: "nodejs",
component: () => import('@/views/NodeManager.vue'), component: () => import("@/views/NodeManager.vue"),
meta: { title: 'Node.js 管理' } meta: { title: "Node.js 管理" },
}, },
{ {
path: '/sites', path: "/go",
name: 'sites', name: "go",
component: () => import('@/views/SitesManager.vue'), component: () => import("@/views/GoManager.vue"),
meta: { title: '站点管理' } meta: { title: "Go 管理" },
}, },
{ {
path: '/hosts', path: "/sites",
name: 'hosts', name: "sites",
component: () => import('@/views/HostsManager.vue'), component: () => import("@/views/SitesManager.vue"),
meta: { title: 'Hosts 管理' } meta: { title: "站点管理" },
}, },
{ {
path: '/git', path: "/hosts",
name: 'git', name: "hosts",
component: () => import('@/views/GitManager.vue'), component: () => import("@/views/HostsManager.vue"),
meta: { title: 'Git 管理' } meta: { title: "Hosts 管理" },
}, },
{ {
path: '/python', path: "/git",
name: 'python', name: "git",
component: () => import('@/views/PythonManager.vue'), component: () => import("@/views/GitManager.vue"),
meta: { title: 'Python 管理' } meta: { title: "Git 管理" },
}, },
{ {
path: '/settings', path: "/python",
name: 'settings', name: "python",
component: () => import('@/views/Settings.vue'), component: () => import("@/views/PythonManager.vue"),
meta: { title: '设置' } meta: { title: "Python 管理" },
} },
] {
}) path: "/settings",
name: "settings",
export default router component: () => import("@/views/Settings.vue"),
meta: { title: "设置" },
},
],
});
export default router;

439
src/views/GoManager.vue Normal file
View File

@ -0,0 +1,439 @@
<template>
<div class="page-container">
<div class="page-header">
<h1 class="page-title">
<span class="title-icon"
><el-icon><Aim /></el-icon
></span>
Go 管理
</h1>
<p class="page-description">管理本地 Go 版本支持多版本切换</p>
</div>
<!-- 下载进度 -->
<div
v-if="downloadProgress.percent > 0 && downloadProgress.percent < 100"
class="download-progress">
<div class="progress-info">
<span>正在下载 Go...</span>
<span
>{{ formatSize(downloadProgress.downloaded) }} /
{{ formatSize(downloadProgress.total) }}</span
>
</div>
<el-progress :percentage="downloadProgress.percent" :stroke-width="10" />
</div>
<!-- 已安装版本 -->
<div class="card">
<div class="card-header">
<span class="card-title">已安装版本</span>
<el-button type="primary" @click="showInstallDialog = true">
<el-icon><Plus /></el-icon>
安装新版本
</el-button>
</div>
<div class="card-content">
<div v-if="versions.length > 0" class="version-grid">
<div
v-for="version in versions"
:key="version.version"
class="version-card"
:class="{ active: version.isActive }">
<div class="version-main">
<div class="version-icon">
<el-icon :size="32"><Aim /></el-icon>
</div>
<div class="version-content">
<div class="version-title">
<span class="version-number">Go {{ version.version }}</span>
<el-tag
v-if="version.isActive"
type="success"
size="small"
effect="dark"
>当前版本</el-tag
>
</div>
</div>
</div>
<div class="version-actions">
<el-button
v-if="!version.isActive"
type="primary"
size="small"
@click="setActiveVersion(version.version)"
:loading="settingActive === version.version">
设为默认
</el-button>
<el-button
type="danger"
size="small"
plain
@click="uninstallVersion(version.version)"
:loading="uninstalling === version.version">
卸载
</el-button>
</div>
</div>
</div>
<el-empty v-else description="暂未安装 Go" />
</div>
</div>
<!-- 安装新版本对话框 -->
<el-dialog v-model="showInstallDialog" title="安装 Go" width="700px">
<el-alert type="info" :closable="false" class="mb-4">
<template #title>
<el-icon><InfoFilled /></el-icon>
下载源说明
</template>
Go 将从官方网站
<a href="https://go.dev/dl/" target="_blank">go.dev/dl</a> 下载 Windows
amd64 版本
</el-alert>
<div v-if="loadingAvailableVersions" class="loading-state">
<el-icon class="is-loading"><Loading /></el-icon>
<span>正在获取可用版本列表...</span>
</div>
<div v-else-if="availableVersions.length === 0" class="empty-hint">
<span>暂无可用版本</span>
</div>
<div v-else class="available-versions">
<el-table
:data="availableVersions"
style="width: 100%"
max-height="400">
<el-table-column prop="version" label="版本" width="140" />
<el-table-column label="类型" width="100">
<template #default="{ row }">
<el-tag v-if="row.stable" type="success" size="small"
>Stable</el-tag
>
<el-tag v-else type="warning" size="small">Unstable</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="120">
<template #default="{ row }">
<el-button
v-if="!isInstalled(row.version)"
type="primary"
size="small"
@click="installVersion(row)"
:loading="installing === row.version">
安装
</el-button>
<el-tag v-else type="info" size="small">已安装</el-tag>
</template>
</el-table-column>
</el-table>
</div>
<template #footer>
<el-button @click="showInstallDialog = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, onUnmounted } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { Plus, Aim, InfoFilled, Loading } from "@element-plus/icons-vue";
defineOptions({
name: "GoManager",
});
interface GoVersion {
version: string;
path: string;
isActive: boolean;
}
interface AvailableGoVersion {
version: string;
stable: boolean;
downloadUrl: string;
filename: string;
}
const versions = ref<GoVersion[]>([]);
const availableVersions = ref<AvailableGoVersion[]>([]);
const showInstallDialog = ref(false);
const installing = ref("");
const uninstalling = ref("");
const settingActive = ref("");
const downloadProgress = reactive({
percent: 0,
downloaded: 0,
total: 0,
});
const loadVersions = async () => {
try {
versions.value = (await window.electronAPI?.go.getVersions()) || [];
} catch (error: any) {
console.error("加载版本失败:", error);
}
};
const loadingAvailableVersions = ref(false);
const loadAvailableVersions = async () => {
loadingAvailableVersions.value = true;
try {
availableVersions.value =
(await window.electronAPI?.go.getAvailableVersions()) || [];
} catch (error: any) {
console.error("加载可用版本失败:", error);
} finally {
loadingAvailableVersions.value = false;
}
};
const isInstalled = (version: string) => {
return versions.value.some((v) => v.version === version);
};
const installVersion = async (row: AvailableGoVersion) => {
installing.value = row.version;
downloadProgress.percent = 0;
downloadProgress.downloaded = 0;
downloadProgress.total = 0;
try {
const result = await window.electronAPI?.go.install(
row.version,
row.downloadUrl,
);
if (result?.success) {
ElMessage.success(result.message);
await loadVersions();
} else {
ElMessage.error(result?.message || "安装失败");
}
} catch (error: any) {
ElMessage.error(error.message);
} finally {
installing.value = "";
downloadProgress.percent = 0;
}
};
const uninstallVersion = async (version: string) => {
try {
await ElMessageBox.confirm(`确定要卸载 Go ${version} 吗?`, "确认卸载", {
type: "warning",
});
uninstalling.value = version;
const result = await window.electronAPI?.go.uninstall(version);
if (result?.success) {
ElMessage.success(result.message);
await loadVersions();
} else {
ElMessage.error(result?.message || "卸载失败");
}
} catch (error: any) {
if (error !== "cancel") {
ElMessage.error(error.message);
}
} finally {
uninstalling.value = "";
}
};
const setActiveVersion = async (version: string) => {
settingActive.value = version;
try {
const result = await window.electronAPI?.go.setActive(version);
if (result?.success) {
ElMessage.success(result.message);
await loadVersions();
} else {
ElMessage.error(result?.message || "设置失败");
}
} catch (error: any) {
ElMessage.error(error.message);
} finally {
settingActive.value = "";
}
};
const formatSize = (bytes: number) => {
if (bytes === 0) return "0 B";
const k = 1024;
const sizes = ["B", "KB", "MB", "GB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
};
const onDownloadProgress = (data: any) => {
if (data.type === "go") {
downloadProgress.percent = data.progress;
downloadProgress.downloaded = data.downloaded;
downloadProgress.total = data.total;
}
};
onMounted(() => {
loadVersions();
loadAvailableVersions();
window.electronAPI?.onDownloadProgress(onDownloadProgress);
});
onUnmounted(() => {
window.electronAPI?.removeDownloadProgressListener(onDownloadProgress);
});
</script>
<style lang="scss" scoped>
.version-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 20px;
}
.version-card {
background: var(--bg-input);
border: 1px solid var(--border-color);
border-radius: 16px;
padding: 24px;
transition: all 0.3s;
display: flex;
flex-direction: column;
gap: 20px;
&:hover {
border-color: var(--accent-color);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
}
&.active {
border-color: var(--success-color);
background: linear-gradient(
135deg,
rgba(16, 185, 129, 0.08) 0%,
rgba(16, 185, 129, 0.02) 100%
);
.version-icon {
background: linear-gradient(135deg, #00add8 0%, #00add8 100%);
}
}
.version-main {
display: flex;
align-items: flex-start;
gap: 16px;
}
.version-icon {
width: 56px;
height: 56px;
border-radius: 14px;
background: linear-gradient(135deg, #00add8 0%, #00add8 100%);
display: flex;
align-items: center;
justify-content: center;
color: white;
flex-shrink: 0;
}
.version-content {
flex: 1;
min-width: 0;
}
.version-title {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
flex-wrap: wrap;
}
.version-number {
font-size: 20px;
font-weight: 700;
color: var(--text-primary);
letter-spacing: -0.5px;
}
.version-actions {
display: flex;
gap: 10px;
padding-top: 4px;
border-top: 1px solid var(--border-color);
.el-button {
flex: 1;
}
}
}
.available-versions {
.el-table {
--el-table-bg-color: transparent;
--el-table-tr-bg-color: transparent;
--el-table-header-bg-color: var(--bg-input);
}
}
.download-progress {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 16px;
margin-bottom: 20px;
.progress-info {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
font-size: 14px;
color: var(--text-secondary);
}
}
.loading-state {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
padding: 40px;
color: var(--text-secondary);
.is-loading {
font-size: 24px;
animation: spin 1s linear infinite;
}
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.mb-4 {
margin-bottom: 16px;
a {
color: var(--accent-color);
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
.empty-hint {
text-align: center;
padding: 40px 20px;
color: var(--text-muted);
}
</style>