phper/electron/main.ts

776 lines
23 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
app,
BrowserWindow,
ipcMain,
shell,
Tray,
Menu,
nativeImage,
} from "electron";
import { join } from "path";
import { PhpManager } from "./services/PhpManager";
import { MysqlManager } from "./services/MysqlManager";
import { NginxManager } from "./services/NginxManager";
import { RedisManager } from "./services/RedisManager";
import { NodeManager } from "./services/NodeManager";
import { GoManager } from "./services/GoManager";
import { ServiceManager } from "./services/ServiceManager";
import { HostsManager } from "./services/HostsManager";
import { GitManager } from "./services/GitManager";
import { PythonManager } from "./services/PythonManager";
import { LogManager } from "./services/LogManager";
import { ConfigStore } from "./services/ConfigStore";
// 获取图标路径
function getIconPath(filename: string): string {
const { existsSync } = require("fs");
// 打包后的路径
if (app.isPackaged) {
const paths = [
join(process.resourcesPath, "public", filename),
join(process.resourcesPath, filename),
join(__dirname, "../public", filename),
];
for (const p of paths) {
if (existsSync(p)) return p;
}
}
// 开发环境路径
const devPaths = [
join(__dirname, "../public", filename),
join(__dirname, "../dist", filename),
];
for (const p of devPaths) {
if (existsSync(p)) return p;
}
return join(__dirname, "../public/icon.ico");
}
// 获取托盘图标路径
function getTrayIconPath(): string {
return getIconPath("icon.ico");
}
// 获取窗口图标路径
function getWindowIconPath(): string {
return getIconPath("icon.ico");
}
// 创建托盘图标
function createTrayIcon(): Electron.NativeImage {
const iconPath = getTrayIconPath();
console.log("Tray icon path:", iconPath);
try {
const icon = nativeImage.createFromPath(iconPath);
if (!icon.isEmpty()) {
// 托盘图标需要较小尺寸
return icon.resize({ width: 16, height: 16 });
}
} catch (e) {
console.error("Failed to load tray icon:", e);
}
return nativeImage.createEmpty();
}
// 创建窗口图标
function createWindowIcon(): Electron.NativeImage {
const iconPath = getWindowIconPath();
console.log("Window icon path:", iconPath);
try {
const icon = nativeImage.createFromPath(iconPath);
if (!icon.isEmpty()) {
return icon;
}
} catch (e) {
console.error("Failed to load window icon:", e);
}
return nativeImage.createEmpty();
}
let mainWindow: BrowserWindow | null = null;
let tray: Tray | null = null;
let isQuitting = false;
// 发送下载进度到渲染进程
export function sendDownloadProgress(
type: string,
progress: number,
downloaded: number,
total: number,
) {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send("download-progress", {
type,
progress,
downloaded,
total,
});
}
}
// 初始化各服务管理器
const configStore = new ConfigStore();
const phpManager = new PhpManager(configStore);
const mysqlManager = new MysqlManager(configStore);
const nginxManager = new NginxManager(configStore);
const redisManager = new RedisManager(configStore);
const nodeManager = new NodeManager(configStore);
const goManager = new GoManager(configStore);
const serviceManager = new ServiceManager(configStore);
const hostsManager = new HostsManager();
const gitManager = new GitManager(configStore);
const pythonManager = new PythonManager(configStore);
const logManager = new LogManager(configStore);
function createWindow() {
const appIcon = createWindowIcon();
mainWindow = new BrowserWindow({
width: 1400,
height: 900,
minWidth: 1200,
minHeight: 700,
webPreferences: {
preload: join(__dirname, "preload.js"),
contextIsolation: true,
nodeIntegration: false,
},
titleBarStyle: "hidden",
titleBarOverlay: {
color: "#1a1a2e",
symbolColor: "#ffffff",
height: 40,
},
frame: false,
icon: appIcon,
show: false, // 先不显示,等 ready-to-show
});
// 开发环境加载 Vite 开发服务器
const VITE_DEV_SERVER_URL = process.env.VITE_DEV_SERVER_URL;
if (VITE_DEV_SERVER_URL) {
mainWindow.loadURL(VITE_DEV_SERVER_URL);
mainWindow.webContents.openDevTools();
} else {
mainWindow.loadFile(join(__dirname, "../dist/index.html"));
}
// 窗口准备好后显示
mainWindow.once("ready-to-show", () => {
// 检查是否开机自启且静默启动
const startMinimized = configStore.get("startMinimized");
if (!startMinimized) {
mainWindow?.show();
}
});
// 关闭按钮改为最小化到托盘
mainWindow.on("close", (event) => {
if (!isQuitting) {
event.preventDefault();
mainWindow?.hide();
}
});
mainWindow.on("closed", () => {
mainWindow = null;
});
}
// 创建系统托盘
function createTray() {
// 创建托盘图标
const trayIcon = createTrayIcon();
tray = new Tray(trayIcon);
tray.setToolTip("PHPer 开发环境管理器");
// 创建托盘菜单
const contextMenu = Menu.buildFromTemplate([
{
label: "显示主窗口",
click: () => {
if (mainWindow) {
mainWindow.show();
mainWindow.focus();
} else {
createWindow();
}
},
},
{ type: "separator" },
{
label: "启动全部服务",
click: async () => {
const result = await serviceManager.startAll();
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send("service-status-changed");
}
},
},
{
label: "停止全部服务",
click: async () => {
const result = await serviceManager.stopAll();
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send("service-status-changed");
}
},
},
{ type: "separator" },
{
label: "退出",
click: () => {
isQuitting = true;
app.quit();
},
},
]);
tray.setContextMenu(contextMenu);
// 双击托盘图标显示窗口
tray.on("double-click", () => {
if (mainWindow) {
mainWindow.show();
mainWindow.focus();
} else {
createWindow();
}
});
}
// 设置应用名称和 Windows AppUserModelId用于任务栏图标分组和进程名称显示
const APP_NAME = "PHPer开发环境管理器";
const APP_ID = "com.phper.devmanager";
app.setName(APP_NAME);
if (process.platform === "win32") {
app.setAppUserModelId(APP_ID);
}
// 单实例锁定
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
// 如果获取不到锁,说明已有实例在运行,退出当前实例
app.quit();
} else {
// 当第二个实例启动时,聚焦到第一个实例的窗口
app.on("second-instance", () => {
if (mainWindow) {
if (mainWindow.isMinimized()) {
mainWindow.restore();
}
mainWindow.show();
mainWindow.focus();
}
});
app.whenReady().then(async () => {
createTray();
createWindow();
// 根据配置自动启动服务
try {
const result = await serviceManager.startAutoStartServices();
if (result.details.length > 0) {
console.log("自动启动服务:", result.details.join(", "));
}
} catch (error) {
console.error("自动启动服务失败:", error);
}
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
// 不要在所有窗口关闭时退出,保持托盘运行
app.on("window-all-closed", () => {
// 什么都不做,保持后台运行
});
// 真正退出前清理
app.on("before-quit", () => {
isQuitting = true;
});
}
// ==================== IPC 处理程序 ====================
// 窗口控制
ipcMain.handle("window:minimize", () => mainWindow?.minimize());
ipcMain.handle("window:maximize", () => {
if (mainWindow?.isMaximized()) {
mainWindow.unmaximize();
} else {
mainWindow?.maximize();
}
});
ipcMain.handle("window:close", () => mainWindow?.close());
// 打开外部链接
ipcMain.handle("shell:openExternal", (_, url: string) =>
shell.openExternal(url),
);
ipcMain.handle("shell:openPath", (_, path: string) => shell.openPath(path));
// 选择文件夹对话框
ipcMain.handle("dialog:selectDirectory", async () => {
const { dialog } = await import("electron");
const result = await dialog.showOpenDialog(mainWindow!, {
properties: ["openDirectory"],
title: "选择目录",
});
return result.canceled ? null : result.filePaths[0];
});
// ==================== PHP 管理 ====================
ipcMain.handle("php:getVersions", () => phpManager.getInstalledVersions());
ipcMain.handle("php:getAvailableVersions", () =>
phpManager.getAvailableVersions(),
);
ipcMain.handle("php:install", (_, version: string) =>
phpManager.install(version),
);
ipcMain.handle("php:uninstall", (_, version: string) =>
phpManager.uninstall(version),
);
ipcMain.handle("php:setActive", (_, version: string) =>
phpManager.setActive(version),
);
ipcMain.handle("php:getExtensions", (_, version: string) =>
phpManager.getExtensions(version),
);
ipcMain.handle("php:openExtensionDir", (_, version: string) =>
phpManager.openExtensionDir(version),
);
ipcMain.handle(
"php:getAvailableExtensions",
(_, version: string, searchKeyword?: string) =>
phpManager.getAvailableExtensions(version, searchKeyword),
);
ipcMain.handle("php:enableExtension", (_, version: string, ext: string) =>
phpManager.enableExtension(version, ext),
);
ipcMain.handle("php:disableExtension", (_, version: string, ext: string) =>
phpManager.disableExtension(version, ext),
);
ipcMain.handle(
"php:installExtension",
(
_,
version: string,
ext: string,
downloadUrl?: string,
packageName?: string,
) => phpManager.installExtension(version, ext, downloadUrl, packageName),
);
ipcMain.handle("php:getConfig", (_, version: string) =>
phpManager.getConfig(version),
);
ipcMain.handle("php:saveConfig", (_, version: string, config: string) =>
phpManager.saveConfig(version, config),
);
// ==================== Composer 管理 ====================
ipcMain.handle("composer:getStatus", () => phpManager.getComposerStatus());
ipcMain.handle("composer:install", () => phpManager.installComposer());
ipcMain.handle("composer:uninstall", () => phpManager.uninstallComposer());
ipcMain.handle("composer:setMirror", (_, mirror: string) =>
phpManager.setComposerMirror(mirror),
);
ipcMain.handle(
"composer:createLaravelProject",
(_, projectName: string, targetDir: string) =>
phpManager.createLaravelProject(projectName, targetDir),
);
// ==================== MySQL 管理 ====================
ipcMain.handle("mysql:getVersions", () => mysqlManager.getInstalledVersions());
ipcMain.handle("mysql:getAvailableVersions", () =>
mysqlManager.getAvailableVersions(),
);
ipcMain.handle("mysql:install", (_, version: string) =>
mysqlManager.install(version),
);
ipcMain.handle("mysql:uninstall", (_, version: string) =>
mysqlManager.uninstall(version),
);
ipcMain.handle("mysql:start", (_, version: string) =>
mysqlManager.start(version),
);
ipcMain.handle("mysql:stop", (_, version: string) =>
mysqlManager.stop(version),
);
ipcMain.handle("mysql:restart", (_, version: string) =>
mysqlManager.restart(version),
);
ipcMain.handle("mysql:getStatus", (_, version: string) =>
mysqlManager.getStatus(version),
);
ipcMain.handle(
"mysql:changePassword",
(_, version: string, newPassword: string, currentPassword?: string) =>
mysqlManager.changeRootPassword(version, newPassword, currentPassword),
);
ipcMain.handle("mysql:getConfig", (_, version: string) =>
mysqlManager.getConfig(version),
);
ipcMain.handle("mysql:saveConfig", (_, version: string, config: string) =>
mysqlManager.saveConfig(version, config),
);
ipcMain.handle("mysql:reinitialize", (_, version: string) =>
mysqlManager.reinitialize(version),
);
// ==================== Nginx 管理 ====================
ipcMain.handle("nginx:getVersions", () => nginxManager.getInstalledVersions());
ipcMain.handle("nginx:getAvailableVersions", () =>
nginxManager.getAvailableVersions(),
);
ipcMain.handle("nginx:install", (_, version: string) =>
nginxManager.install(version),
);
ipcMain.handle("nginx:uninstall", (_, version: string) =>
nginxManager.uninstall(version),
);
ipcMain.handle("nginx:start", () => nginxManager.start());
ipcMain.handle("nginx:stop", () => nginxManager.stop());
ipcMain.handle("nginx:restart", () => nginxManager.restart());
ipcMain.handle("nginx:reload", () => nginxManager.reload());
ipcMain.handle("nginx:getStatus", () => nginxManager.getStatus());
ipcMain.handle("nginx:getConfig", () => nginxManager.getConfig());
ipcMain.handle("nginx:saveConfig", (_, config: string) =>
nginxManager.saveConfig(config),
);
ipcMain.handle("nginx:getSites", () => nginxManager.getSites());
ipcMain.handle("nginx:addSite", (_, site: any) => nginxManager.addSite(site));
ipcMain.handle("nginx:removeSite", (_, name: string) =>
nginxManager.removeSite(name),
);
ipcMain.handle("nginx:updateSite", (_, originalName: string, site: any) =>
nginxManager.updateSite(originalName, site),
);
ipcMain.handle("nginx:enableSite", (_, name: string) =>
nginxManager.enableSite(name),
);
ipcMain.handle("nginx:disableSite", (_, name: string) =>
nginxManager.disableSite(name),
);
ipcMain.handle("nginx:generateLaravelConfig", (_, site: any) =>
nginxManager.generateLaravelConfig(site),
);
ipcMain.handle("nginx:requestSSL", (_, domain: string, email: string) =>
nginxManager.requestSSLCertificate(domain, email),
);
// ==================== Redis 管理 ====================
ipcMain.handle("redis:getVersions", () => redisManager.getInstalledVersions());
ipcMain.handle("redis:getAvailableVersions", () =>
redisManager.getAvailableVersions(),
);
ipcMain.handle("redis:install", (_, version: string) =>
redisManager.install(version),
);
ipcMain.handle("redis:uninstall", (_, version: string) =>
redisManager.uninstall(version),
);
ipcMain.handle("redis:start", () => redisManager.start());
ipcMain.handle("redis:stop", () => redisManager.stop());
ipcMain.handle("redis:restart", () => redisManager.restart());
ipcMain.handle("redis:getStatus", () => redisManager.getStatus());
ipcMain.handle("redis:getConfig", () => redisManager.getConfig());
ipcMain.handle("redis:saveConfig", (_, config: string) =>
redisManager.saveConfig(config),
);
// ==================== Node.js 管理 ====================
ipcMain.handle("node:getVersions", () => nodeManager.getInstalledVersions());
ipcMain.handle("node:getAvailableVersions", () =>
nodeManager.getAvailableVersions(),
);
ipcMain.handle("node:install", (_, version: string, downloadUrl: string) =>
nodeManager.install(version, downloadUrl),
);
ipcMain.handle("node:uninstall", (_, version: string) =>
nodeManager.uninstall(version),
);
ipcMain.handle("node:setActive", (_, version: string) =>
nodeManager.setActive(version),
);
ipcMain.handle("node:getInfo", (_, version: string) =>
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:setAutoStart", (_, service: string, enabled: boolean) =>
serviceManager.setAutoStart(service, enabled),
);
ipcMain.handle("service:getAutoStart", (_, service: string) =>
serviceManager.getAutoStart(service),
);
ipcMain.handle("service:startAll", () => serviceManager.startAll());
ipcMain.handle("service:stopAll", () => serviceManager.stopAll());
// PHP-CGI 管理 - 支持多版本
ipcMain.handle("service:getPhpCgiStatus", () =>
serviceManager.getPhpCgiStatus(),
);
ipcMain.handle("service:startPhpCgi", () => serviceManager.startPhpCgi());
ipcMain.handle("service:stopPhpCgi", () => serviceManager.stopPhpCgi());
ipcMain.handle("service:startAllPhpCgi", () => serviceManager.startAllPhpCgi());
ipcMain.handle("service:stopAllPhpCgi", () => serviceManager.stopAllPhpCgi());
ipcMain.handle("service:startPhpCgiVersion", (_, version: string) =>
serviceManager.startPhpCgiVersion(version),
);
ipcMain.handle("service:stopPhpCgiVersion", (_, version: string) =>
serviceManager.stopPhpCgiVersion(version),
);
ipcMain.handle("service:getPhpCgiPort", (_, version: string) =>
serviceManager.getPhpCgiPort(version),
);
// ==================== Hosts 管理 ====================
ipcMain.handle("hosts:get", () => hostsManager.getHosts());
ipcMain.handle("hosts:add", (_, domain: string, ip: string) =>
hostsManager.addHost(domain, ip),
);
ipcMain.handle("hosts:remove", (_, domain: string) =>
hostsManager.removeHost(domain),
);
// ==================== Git 管理 ====================
ipcMain.handle("git:getVersions", () => gitManager.getInstalledVersions());
ipcMain.handle("git:getAvailableVersions", () =>
gitManager.getAvailableVersions(),
);
ipcMain.handle("git:install", (_, version: string) =>
gitManager.install(version),
);
ipcMain.handle("git:uninstall", () => gitManager.uninstall());
ipcMain.handle("git:checkSystem", () => gitManager.checkSystemGit());
ipcMain.handle("git:getConfig", () => gitManager.getGitConfig());
ipcMain.handle("git:setConfig", (_, name: string, email: string) =>
gitManager.setGitConfig(name, email),
);
// ==================== Python 管理 ====================
ipcMain.handle("python:getVersions", () =>
pythonManager.getInstalledVersions(),
);
ipcMain.handle("python:getAvailableVersions", () =>
pythonManager.getAvailableVersions(),
);
ipcMain.handle("python:install", (_, version: string) =>
pythonManager.install(version),
);
ipcMain.handle("python:uninstall", (_, version: string) =>
pythonManager.uninstall(version),
);
ipcMain.handle("python:setActive", (_, version: string) =>
pythonManager.setActive(version),
);
ipcMain.handle("python:checkSystem", () => pythonManager.checkSystemPython());
ipcMain.handle("python:getPipInfo", (_, version: string) =>
pythonManager.getPipInfo(version),
);
ipcMain.handle(
"python:installPackage",
(_, version: string, packageName: string) =>
pythonManager.installPackage(version, packageName),
);
// ==================== 配置管理 ====================
ipcMain.handle("config:get", (_, key: string) => configStore.get(key));
ipcMain.handle("config:set", (_, key: string, value: any) =>
configStore.set(key, value),
);
ipcMain.handle("config:getBasePath", () => configStore.getBasePath());
ipcMain.handle("config:setBasePath", (_, path: string) =>
configStore.setBasePath(path),
);
// ==================== 应用设置 ====================
// 设置开机自启(以管理员模式,使用任务计划程序,静默启动)
ipcMain.handle("app:setAutoLaunch", async (_, enabled: boolean) => {
const { execSync, exec } = require("child_process");
const { writeFileSync, unlinkSync, existsSync } = require("fs");
const { join } = require("path");
const exePath = app.getPath("exe");
const taskName = "PHPerDevManager";
// 开发模式下不支持
if (!app.isPackaged) {
return {
success: false,
message: "开发模式下不支持开机自启,请打包后使用",
};
}
try {
if (enabled) {
// 先删除可能存在的旧任务
try {
execSync(`schtasks /delete /tn "${taskName}" /f`, {
encoding: "buffer",
windowsHide: true,
});
} catch (e) {
// 忽略删除失败(可能任务不存在)
}
// 创建 VBS 启动脚本(确保静默启动)
const appDir = require("path").dirname(exePath);
const vbsPath = join(appDir, "silent_start.vbs");
const vbsContent = `Set WshShell = CreateObject("WScript.Shell")\nWshShell.Run """${exePath.replace(/\\/g, "\\\\")}""", 0, False`;
writeFileSync(vbsPath, vbsContent);
// 创建任务计划程序任务,运行 VBS 脚本实现静默启动
const command = `schtasks /create /tn "${taskName}" /tr "wscript.exe \\"${vbsPath}\\"" /sc onlogon /rl highest /f`;
execSync(command, { encoding: "buffer", windowsHide: true });
configStore.set("autoLaunch", true);
return { success: true, message: "已启用开机自启(静默模式)" };
} else {
// 删除任务计划程序任务
try {
execSync(`schtasks /delete /tn "${taskName}" /f`, {
encoding: "buffer",
windowsHide: true,
});
} catch (e) {
// 忽略删除失败
}
// 删除 VBS 脚本
const appDir = require("path").dirname(exePath);
const vbsPath = join(appDir, "silent_start.vbs");
if (existsSync(vbsPath)) {
try {
unlinkSync(vbsPath);
} catch (e) {
// 忽略删除失败
}
}
configStore.set("autoLaunch", false);
return { success: true, message: "已禁用开机自启" };
}
} catch (error: any) {
console.error("任务计划操作失败:", error);
return {
success: false,
message: "操作失败,请确保应用以管理员身份运行",
};
}
});
// 获取开机自启状态
ipcMain.handle("app:getAutoLaunch", async () => {
const { execSync } = require("child_process");
const taskName = "PHPerDevManager";
// 开发模式下返回 false
if (!app.isPackaged) {
return false;
}
try {
execSync(`schtasks /query /tn "${taskName}"`, {
encoding: "buffer",
windowsHide: true,
});
return true;
} catch (e) {
return false;
}
});
// 获取应用版本信息
ipcMain.handle("app:getVersion", async () => {
const { existsSync, readFileSync } = require("fs");
const { join } = require("path");
const version = app.getVersion();
let buildTime = "";
let buildDate = "";
// 尝试读取版本信息文件
try {
const versionFilePath = app.isPackaged
? join(process.resourcesPath, "public", "version.json")
: join(__dirname, "..", "public", "version.json");
if (existsSync(versionFilePath)) {
const versionInfo = JSON.parse(readFileSync(versionFilePath, "utf-8"));
buildTime = versionInfo.buildTime || "";
buildDate = versionInfo.buildDate || "";
}
} catch (e) {
// 忽略错误
}
return {
version,
buildTime,
buildDate,
isPackaged: app.isPackaged,
};
});
// 设置启动时最小化到托盘
ipcMain.handle("app:setStartMinimized", (_, enabled: boolean) => {
configStore.set("startMinimized", enabled);
return { success: true };
});
// 获取启动时最小化状态
ipcMain.handle("app:getStartMinimized", () => {
return configStore.get("startMinimized") || false;
});
// 真正退出应用
ipcMain.handle("app:quit", () => {
isQuitting = true;
app.quit();
});
// ==================== 日志管理 ====================
ipcMain.handle("log:getFiles", () => logManager.getLogFiles());
ipcMain.handle("log:read", (_, logPath: string, lines?: number) =>
logManager.readLog(logPath, lines),
);
ipcMain.handle("log:clear", (_, logPath: string) =>
logManager.clearLog(logPath),
);
ipcMain.handle(
"log:getDirectory",
(_, type: "nginx" | "php" | "mysql" | "sites", version?: string) =>
logManager.getLogDirectory(type, version),
);