Compare commits
4 Commits
feature/go
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 4abce26cf2 | |||
| 982a51ef52 | |||
| 11ad2cf722 | |||
| 1189177568 |
@ -7,10 +7,11 @@ exports.default = async function(context) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Running afterPack hook to set icon...');
|
console.log('Running afterPack hook to set icon and version info...');
|
||||||
|
|
||||||
const appOutDir = context.appOutDir;
|
const appOutDir = context.appOutDir;
|
||||||
const productName = context.packager.appInfo.productName;
|
const productName = context.packager.appInfo.productName;
|
||||||
|
const version = context.packager.appInfo.version;
|
||||||
const exePath = path.join(appOutDir, `${productName}.exe`);
|
const exePath = path.join(appOutDir, `${productName}.exe`);
|
||||||
const iconPath = path.join(__dirname, 'icon.ico');
|
const iconPath = path.join(__dirname, 'icon.ico');
|
||||||
|
|
||||||
@ -25,18 +26,29 @@ exports.default = async function(context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 使用 npm 安装的 rcedit 模块
|
// rcedit 是默认导出
|
||||||
const { rcedit } = require('rcedit');
|
const rcedit = require('rcedit');
|
||||||
|
|
||||||
console.log(`Setting icon for: ${exePath}`);
|
console.log(`Setting icon and version info for: ${exePath}`);
|
||||||
console.log(`Using icon: ${iconPath}`);
|
console.log(`Using icon: ${iconPath}`);
|
||||||
|
|
||||||
await rcedit(exePath, {
|
await rcedit(exePath, {
|
||||||
icon: iconPath
|
icon: iconPath,
|
||||||
|
'version-string': {
|
||||||
|
'ProductName': productName,
|
||||||
|
'FileDescription': productName,
|
||||||
|
'CompanyName': 'PHPer',
|
||||||
|
'LegalCopyright': 'Copyright © 2024 PHPer',
|
||||||
|
'OriginalFilename': `${productName}.exe`,
|
||||||
|
'InternalName': productName
|
||||||
|
},
|
||||||
|
'file-version': version,
|
||||||
|
'product-version': version
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('Icon set successfully!');
|
console.log('Icon and version info set successfully!');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to set icon:', error.message);
|
console.error('Failed to set icon:', error.message);
|
||||||
|
// 不阻止打包继续
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
187
electron/main.ts
187
electron/main.ts
@ -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);
|
||||||
@ -241,6 +243,15 @@ function createTray() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设置应用名称和 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();
|
const gotTheLock = app.requestSingleInstanceLock();
|
||||||
|
|
||||||
@ -306,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));
|
||||||
|
|
||||||
@ -323,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",
|
||||||
@ -358,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 管理 ====================
|
||||||
@ -373,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());
|
||||||
@ -437,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());
|
||||||
@ -477,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),
|
||||||
);
|
);
|
||||||
|
|
||||||
// ==================== 应用设置 ====================
|
// ==================== 应用设置 ====================
|
||||||
@ -700,7 +739,7 @@ ipcMain.handle("app:getVersion", async () => {
|
|||||||
version,
|
version,
|
||||||
buildTime,
|
buildTime,
|
||||||
buildDate,
|
buildDate,
|
||||||
isPackaged: app.isPackaged
|
isPackaged: app.isPackaged,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -724,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),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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,
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
457
electron/services/GoManager.ts
Normal file
457
electron/services/GoManager.ts
Normal 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",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "phper-dev-manager",
|
"name": "phper-dev-manager",
|
||||||
"version": "1.0.7",
|
"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": {
|
||||||
@ -75,8 +75,9 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "build/icon.ico",
|
"icon": "build/icon.ico",
|
||||||
|
"executableName": "PHPer开发环境管理器",
|
||||||
"requestedExecutionLevel": "requireAdministrator",
|
"requestedExecutionLevel": "requireAdministrator",
|
||||||
"signAndEditExecutable": false
|
"signAndEditExecutable": true
|
||||||
},
|
},
|
||||||
"nsis": {
|
"nsis": {
|
||||||
"oneClick": false,
|
"oneClick": false,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.0.7",
|
"version": "1.0.10",
|
||||||
"buildTime": "2025-12-31T07:09:02.287Z",
|
"buildTime": "2026-02-05T01:05:37.725Z",
|
||||||
"buildDate": "2025/12/31"
|
"buildDate": "2026/2/5"
|
||||||
}
|
}
|
||||||
149
src/App.vue
149
src/App.vue
@ -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,115 +74,127 @@
|
|||||||
</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) {
|
} catch (error: any) {
|
||||||
ElMessage.error(error.message)
|
ElMessage.error(error.message);
|
||||||
} finally {
|
} finally {
|
||||||
startingAll.value = false
|
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) {
|
} catch (error: any) {
|
||||||
ElMessage.error(error.message)
|
ElMessage.error(error.message);
|
||||||
} finally {
|
} finally {
|
||||||
stoppingAll.value = false
|
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>
|
||||||
@ -226,7 +239,7 @@ onUnmounted(() => {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
font-family: 'Noto Sans SC', 'Microsoft YaHei', sans-serif;
|
font-family: "Noto Sans SC", "Microsoft YaHei", sans-serif;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -366,6 +379,4 @@ onUnmounted(() => {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
background: var(--bg-content);
|
background: var(--bg-content);
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@ -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
439
src/views/GoManager.vue
Normal 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>
|
||||||
@ -352,21 +352,34 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-else class="extensions-list">
|
<div v-else class="extensions-list">
|
||||||
<div class="extensions-count">
|
<div class="extensions-count">
|
||||||
找到 {{ availableExtensions.length }} 个适用于 PHP {{ currentVersion }} 的扩展
|
找到 {{ availableExtensions.length }} 个扩展
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-for="ext in availableExtensions"
|
v-for="ext in availableExtensions"
|
||||||
:key="ext.name"
|
:key="ext.name"
|
||||||
class="extension-item"
|
class="extension-item"
|
||||||
|
:class="{ 'not-available': ext.notAvailableReason }"
|
||||||
>
|
>
|
||||||
<div class="ext-info">
|
<div class="ext-info">
|
||||||
<div class="ext-main">
|
<div class="ext-main">
|
||||||
<span class="ext-name" v-html="highlightKeyword(ext.name)"></span>
|
<span class="ext-name" v-html="highlightKeyword(ext.name)"></span>
|
||||||
<el-tag type="warning" size="small">v{{ ext.version }}</el-tag>
|
<el-tag type="info" size="small">{{ ext.version === 'latest' ? '最新版' : 'v' + ext.version }}</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<span class="ext-desc" v-if="ext.description">{{ ext.description }}</span>
|
<span class="ext-desc" v-if="ext.description">{{ ext.description }}</span>
|
||||||
|
<span class="ext-not-available" v-if="ext.notAvailableReason">
|
||||||
|
<el-icon><Warning /></el-icon>
|
||||||
|
{{ ext.notAvailableReason }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 有明确不可用原因时显示不支持 -->
|
||||||
|
<el-tooltip v-if="ext.notAvailableReason" :content="ext.notAvailableReason" placement="top">
|
||||||
|
<el-button type="info" size="small" disabled>
|
||||||
|
不支持
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<!-- 否则显示安装按钮 -->
|
||||||
<el-button
|
<el-button
|
||||||
|
v-else
|
||||||
type="primary"
|
type="primary"
|
||||||
size="small"
|
size="small"
|
||||||
@click="installExtension(ext)"
|
@click="installExtension(ext)"
|
||||||
@ -415,7 +428,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, computed, onMounted, onUnmounted, onActivated } from 'vue'
|
import { ref, reactive, computed, onMounted, onUnmounted, onActivated } from 'vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { FolderOpened, InfoFilled, VideoPlay, VideoPause, EditPen } from '@element-plus/icons-vue'
|
import { FolderOpened, InfoFilled, VideoPlay, VideoPause, EditPen, Warning } from '@element-plus/icons-vue'
|
||||||
import { useServiceStore } from '@/stores/serviceStore'
|
import { useServiceStore } from '@/stores/serviceStore'
|
||||||
import LogViewer from '@/components/LogViewer.vue'
|
import LogViewer from '@/components/LogViewer.vue'
|
||||||
|
|
||||||
@ -451,6 +464,8 @@ interface AvailableExtension {
|
|||||||
downloadUrl: string
|
downloadUrl: string
|
||||||
description?: string
|
description?: string
|
||||||
packageName?: string // Packagist 包名,用于 PIE 安装
|
packageName?: string // Packagist 包名,用于 PIE 安装
|
||||||
|
supportedPhpVersions?: string[] // 支持的 PHP 版本
|
||||||
|
notAvailableReason?: string // 不可用原因
|
||||||
}
|
}
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@ -1081,6 +1096,11 @@ onUnmounted(() => {
|
|||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.not-available {
|
||||||
|
opacity: 0.7;
|
||||||
|
background-color: rgba(0, 0, 0, 0.02);
|
||||||
|
}
|
||||||
|
|
||||||
.ext-info {
|
.ext-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -1101,6 +1121,14 @@ onUnmounted(() => {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ext-not-available {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--warning-color, #e6a23c);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user