mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2025-12-26 20:50:30 +08:00
feat: add kernel version selection
* feat: add kernel version selection * full * update translations
This commit is contained in:
parent
75762c1263
commit
ecd92417e4
232
src/main/utils/github.ts
Normal file
232
src/main/utils/github.ts
Normal file
@ -0,0 +1,232 @@
|
||||
import axios from 'axios'
|
||||
import { createWriteStream, createReadStream } from 'fs'
|
||||
import { mihomoCoreDir } from './dirs'
|
||||
import AdmZip from 'adm-zip'
|
||||
import { execSync } from 'child_process'
|
||||
import { platform } from 'os'
|
||||
import { join } from 'path'
|
||||
import { existsSync, rmSync } from 'fs'
|
||||
import { createGunzip } from 'zlib'
|
||||
import { stopCore } from '../core/manager'
|
||||
|
||||
export interface GitHubTag {
|
||||
name: string
|
||||
zipball_url: string
|
||||
tarball_url: string
|
||||
}
|
||||
|
||||
interface VersionCache {
|
||||
data: GitHubTag[]
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
const CACHE_EXPIRY = 5 * 60 * 1000
|
||||
|
||||
const GITHUB_API_CONFIG = {
|
||||
BASE_URL: 'https://api.github.com',
|
||||
API_VERSION: '2022-11-28',
|
||||
TAGS_PER_PAGE: 100
|
||||
}
|
||||
|
||||
const PLATFORM_MAP: Record<string, string> = {
|
||||
'win32-x64': 'mihomo-windows-amd64-compatible',
|
||||
'win32-ia32': 'mihomo-windows-386',
|
||||
'win32-arm64': 'mihomo-windows-arm64',
|
||||
'darwin-x64': 'mihomo-darwin-amd64-compatible',
|
||||
'darwin-arm64': 'mihomo-darwin-arm64',
|
||||
'linux-x64': 'mihomo-linux-amd64-compatible',
|
||||
'linux-arm64': 'mihomo-linux-arm64'
|
||||
}
|
||||
|
||||
const versionCache = new Map<string, VersionCache>()
|
||||
|
||||
/**
|
||||
* 获取GitHub仓库的标签列表(带缓存)
|
||||
* @param owner 仓库所有者
|
||||
* @param repo 仓库名称
|
||||
* @param forceRefresh 是否强制刷新缓存
|
||||
* @returns 标签列表
|
||||
*/
|
||||
export async function getGitHubTags(owner: string, repo: string, forceRefresh = false): Promise<GitHubTag[]> {
|
||||
const cacheKey = `${owner}/${repo}`
|
||||
|
||||
// 检查缓存
|
||||
if (!forceRefresh && versionCache.has(cacheKey)) {
|
||||
const cache = versionCache.get(cacheKey)!
|
||||
// 检查缓存是否过期
|
||||
if (Date.now() - cache.timestamp < CACHE_EXPIRY) {
|
||||
console.log(`[GitHub] Returning cached tags for ${owner}/${repo}`)
|
||||
return cache.data
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`[GitHub] Fetching tags for ${owner}/${repo}`)
|
||||
const response = await axios.get<GitHubTag[]>(
|
||||
`${GITHUB_API_CONFIG.BASE_URL}/repos/${owner}/${repo}/tags?per_page=${GITHUB_API_CONFIG.TAGS_PER_PAGE}`,
|
||||
{
|
||||
headers: {
|
||||
Accept: 'application/vnd.github+json',
|
||||
'X-GitHub-Api-Version': GITHUB_API_CONFIG.API_VERSION
|
||||
},
|
||||
timeout: 10000
|
||||
}
|
||||
)
|
||||
|
||||
// 更新缓存
|
||||
versionCache.set(cacheKey, {
|
||||
data: response.data,
|
||||
timestamp: Date.now()
|
||||
})
|
||||
|
||||
console.log(`[GitHub] Successfully fetched ${response.data.length} tags for ${owner}/${repo}`)
|
||||
return response.data
|
||||
} catch (error) {
|
||||
console.error(`[GitHub] Failed to fetch tags for ${owner}/${repo}:`, error)
|
||||
if (axios.isAxiosError(error)) {
|
||||
throw new Error(`GitHub API error: ${error.response?.status} - ${error.response?.statusText}`)
|
||||
}
|
||||
throw new Error('Failed to fetch version list')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除版本缓存
|
||||
* @param owner 仓库所有者
|
||||
* @param repo 仓库名称
|
||||
*/
|
||||
export function clearVersionCache(owner: string, repo: string): void {
|
||||
const cacheKey = `${owner}/${repo}`
|
||||
const hasCache = versionCache.has(cacheKey)
|
||||
versionCache.delete(cacheKey)
|
||||
console.log(`[GitHub] Cache ${hasCache ? 'cleared' : 'not found'} for ${owner}/${repo}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载GitHub Release资产
|
||||
* @param url 下载URL
|
||||
* @param outputPath 输出路径
|
||||
*/
|
||||
async function downloadGitHubAsset(url: string, outputPath: string): Promise<void> {
|
||||
try {
|
||||
console.log(`[GitHub] Downloading asset from ${url}`)
|
||||
const writer = createWriteStream(outputPath)
|
||||
const response = await axios({
|
||||
url,
|
||||
method: 'GET',
|
||||
responseType: 'stream',
|
||||
timeout: 30000
|
||||
})
|
||||
|
||||
response.data.pipe(writer)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
writer.on('finish', () => {
|
||||
console.log(`[GitHub] Successfully downloaded asset to ${outputPath}`)
|
||||
resolve()
|
||||
})
|
||||
writer.on('error', (error) => {
|
||||
console.error(`[GitHub] Failed to write asset to ${outputPath}:`, error)
|
||||
reject(new Error(`Failed to download core file: ${error.message}`))
|
||||
})
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(`[GitHub] Failed to download asset from ${url}:`, error)
|
||||
if (axios.isAxiosError(error)) {
|
||||
throw new Error(`Download error: ${error.response?.status} - ${error.response?.statusText}`)
|
||||
}
|
||||
throw new Error('Failed to download core file')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安装特定版本的mihomo核心
|
||||
* @param version 版本号
|
||||
*/
|
||||
export async function installMihomoCore(version: string): Promise<void> {
|
||||
try {
|
||||
console.log(`[GitHub] Installing mihomo core version ${version}`)
|
||||
|
||||
const plat = platform()
|
||||
let arch = process.arch
|
||||
|
||||
// 映射平台和架构到GitHub Release文件名
|
||||
const key = `${plat}-${arch}`
|
||||
const name = PLATFORM_MAP[key]
|
||||
|
||||
if (!name) {
|
||||
throw new Error(`Unsupported platform "${plat}-${arch}"`)
|
||||
}
|
||||
|
||||
const isWin = plat === 'win32'
|
||||
const urlExt = isWin ? 'zip' : 'gz'
|
||||
const downloadURL = `https://github.com/MetaCubeX/mihomo/releases/download/${version}/${name}-${version}.${urlExt}`
|
||||
|
||||
const coreDir = mihomoCoreDir()
|
||||
const tempZip = join(coreDir, `temp-core.${urlExt}`)
|
||||
const exeFile = `${name}${isWin ? '.exe' : ''}`
|
||||
const targetFile = `mihomo-specific${isWin ? '.exe' : ''}`
|
||||
const targetPath = join(coreDir, targetFile)
|
||||
|
||||
// 如果目标文件已存在,先停止核心
|
||||
if (existsSync(targetPath)) {
|
||||
console.log('[GitHub] Stopping core before extracting new core file')
|
||||
// 先停止核心
|
||||
await stopCore(true)
|
||||
}
|
||||
|
||||
// 下载文件
|
||||
await downloadGitHubAsset(downloadURL, tempZip)
|
||||
|
||||
// 解压文件
|
||||
if (urlExt === 'zip') {
|
||||
console.log(`[GitHub] Extracting ZIP file ${tempZip}`)
|
||||
const zip = new AdmZip(tempZip)
|
||||
const entries = zip.getEntries()
|
||||
const entry = entries.find(e => e.entryName.includes(exeFile))
|
||||
|
||||
if (entry) {
|
||||
zip.extractEntryTo(entry, coreDir, false, true, false, targetFile)
|
||||
console.log(`[GitHub] Successfully extracted ${exeFile} to ${targetPath}`)
|
||||
} else {
|
||||
throw new Error(`Executable file not found in zip: ${exeFile}`)
|
||||
}
|
||||
} else {
|
||||
// 处理.gz文件
|
||||
console.log(`[GitHub] Extracting GZ file ${tempZip}`)
|
||||
const readStream = createReadStream(tempZip)
|
||||
const writeStream = createWriteStream(targetPath)
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const onError = (error: Error) => {
|
||||
console.error('[GitHub] Gzip decompression failed:', error.message)
|
||||
reject(new Error(`Gzip decompression failed: ${error.message}`))
|
||||
}
|
||||
|
||||
readStream
|
||||
.pipe(createGunzip().on('error', onError))
|
||||
.pipe(writeStream)
|
||||
.on('finish', () => {
|
||||
console.log('[GitHub] Gunzip finished')
|
||||
try {
|
||||
execSync(`chmod 755 ${targetPath}`)
|
||||
console.log('[GitHub] Chmod binary finished')
|
||||
} catch (chmodError) {
|
||||
console.warn('[GitHub] Failed to chmod binary:', chmodError)
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
.on('error', onError)
|
||||
})
|
||||
}
|
||||
|
||||
// 清理临时文件
|
||||
console.log(`[GitHub] Cleaning up temporary file ${tempZip}`)
|
||||
rmSync(tempZip)
|
||||
|
||||
console.log(`[GitHub] Successfully installed mihomo core version ${version}`)
|
||||
} catch (error) {
|
||||
console.error('[GitHub] Failed to install mihomo core:', error)
|
||||
throw new Error(`Failed to install core: ${error instanceof Error ? error.message : String(error)}`)
|
||||
}
|
||||
}
|
||||
@ -21,6 +21,7 @@ import {
|
||||
mihomoSmartFlushCache
|
||||
} from '../core/mihomoApi'
|
||||
import { checkAutoRun, disableAutoRun, enableAutoRun } from '../sys/autoRun'
|
||||
import { installMihomoCore, getGitHubTags, clearVersionCache } from './github'
|
||||
import {
|
||||
getAppConfig,
|
||||
patchAppConfig,
|
||||
@ -128,6 +129,22 @@ function ipcErrorWrapper<T>( // eslint-disable-next-line @typescript-eslint/no-e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GitHub版本管理相关IPC处理程序
|
||||
export async function fetchMihomoTags(forceRefresh = false): Promise<{name: string, zipball_url: string, tarball_url: string}[]> {
|
||||
return await getGitHubTags('MetaCubeX', 'mihomo', forceRefresh)
|
||||
}
|
||||
|
||||
export async function installSpecificMihomoCore(version: string): Promise<void> {
|
||||
// 安装完成后清除缓存,以便下次获取最新的标签列表
|
||||
clearVersionCache('MetaCubeX', 'mihomo')
|
||||
return await installMihomoCore(version)
|
||||
}
|
||||
|
||||
export async function clearMihomoVersionCache(): Promise<void> {
|
||||
clearVersionCache('MetaCubeX', 'mihomo')
|
||||
}
|
||||
|
||||
export function registerIpcMainHandlers(): void {
|
||||
ipcMain.handle('mihomoVersion', ipcErrorWrapper(mihomoVersion))
|
||||
ipcMain.handle('mihomoCloseConnection', (_e, id) => ipcErrorWrapper(mihomoCloseConnection)(id))
|
||||
@ -314,4 +331,13 @@ export function registerIpcMainHandlers(): void {
|
||||
// 触发托盘菜单更新
|
||||
ipcMain.emit('updateTrayMenu')
|
||||
})
|
||||
}
|
||||
|
||||
// 注册获取Mihomo标签的IPC处理程序
|
||||
ipcMain.handle('fetchMihomoTags', (_e, forceRefresh) => ipcErrorWrapper(fetchMihomoTags)(forceRefresh))
|
||||
|
||||
// 注册安装特定版本Mihomo核心的IPC处理程序
|
||||
ipcMain.handle('installSpecificMihomoCore', (_e, version) => ipcErrorWrapper(installSpecificMihomoCore)(version))
|
||||
|
||||
// 注册清除版本缓存的IPC处理程序
|
||||
ipcMain.handle('clearMihomoVersionCache', () => ipcErrorWrapper(clearMihomoVersionCache)())
|
||||
}
|
||||
@ -43,6 +43,7 @@
|
||||
"common.updater.versionReady": "v{{version}} Version Ready",
|
||||
"common.updater.goToDownload": "Go to Download",
|
||||
"common.updater.update": "Update",
|
||||
"common.refresh": "Refresh",
|
||||
"settings.general": "General Settings",
|
||||
"settings.mihomo": "Mihomo Settings",
|
||||
"settings.language": "Language",
|
||||
@ -128,6 +129,13 @@
|
||||
"mihomo.stableVersion": "Stable",
|
||||
"mihomo.alphaVersion": "Alpha",
|
||||
"mihomo.smartVersion": "Smart",
|
||||
"mihomo.specificVersion": "Specific Version",
|
||||
"mihomo.selectSpecificVersion": "Select Specific Version",
|
||||
"mihomo.searchVersion": "Search version...",
|
||||
"mihomo.installVersion": "Install Version",
|
||||
"mihomo.noVersionsFound": "No versions found",
|
||||
"mihomo.error.fetchTagsFailed": "Failed to fetch version list",
|
||||
"mihomo.error.installCoreFailed": "Failed to install core",
|
||||
"mihomo.enableSmartCore": "Enable Smart Core",
|
||||
"mihomo.enableSmartOverride": "Use Auto Smart Rule Override",
|
||||
"mihomo.smartOverrideTooltip": "Use the built-in smart override script in Party to replace url-test and load-balance with the Smart rule group; if the above rule groups are not present, extract all nodes from the subscription and replace the default rules, suitable for users who don’t want to hassle, with the feature taking effect with one click; if using global mode, please select the node named 'Smart Group.'",
|
||||
|
||||
@ -125,6 +125,13 @@
|
||||
"mihomo.stableVersion": "نسخه پایدار",
|
||||
"mihomo.alphaVersion": "نسخه آلفا",
|
||||
"mihomo.smartVersion": "Smart",
|
||||
"mihomo.specificVersion": "نسخه خاص",
|
||||
"mihomo.selectSpecificVersion": "انتخاب نسخه خاص",
|
||||
"mihomo.searchVersion": "جستجوی نسخه...",
|
||||
"mihomo.installVersion": "نصب نسخه",
|
||||
"mihomo.noVersionsFound": "نسخهای یافت نشد",
|
||||
"mihomo.error.fetchTagsFailed": "دریافت لیست نسخهها با شکست مواجه شد",
|
||||
"mihomo.error.installCoreFailed": "نصب هسته با شکست مواجه شد",
|
||||
"mihomo.enableSmartCore": "فعالسازی Smart Core",
|
||||
"mihomo.enableSmartOverride": "استفاده از بازنویسی خودکار Smart",
|
||||
"mihomo.smartOverrideTooltip": "از اسکریپت بازنویسی هوشمند داخلی Party برای جایگزینی url-test و load-balance با گروه قواعد Smart استفاده کنید؛ اگر گروههای قواعد فوق وجود ندارند، تمام گرهها را از اشتراک استخراج کرده و قواعد پیشفرض را جایگزین کنید، برای کاربرانی که نمیخواهند دردسر باشند مناسب است، قابلیت با یک کلیک فعال میشود؛ اگر از حالت جهانی استفاده میکنید، لطفاً گره با نام 'Smart Group' را انتخاب کنید",
|
||||
|
||||
@ -125,6 +125,13 @@
|
||||
"mihomo.stableVersion": "Стабильная",
|
||||
"mihomo.alphaVersion": "Альфа",
|
||||
"mihomo.smartVersion": "Smart",
|
||||
"mihomo.specificVersion": "Определённая версия",
|
||||
"mihomo.selectSpecificVersion": "Выбрать определённую версию",
|
||||
"mihomo.searchVersion": "Поиск версии...",
|
||||
"mihomo.installVersion": "Установить версию",
|
||||
"mihomo.noVersionsFound": "Версии не найдены",
|
||||
"mihomo.error.fetchTagsFailed": "Не удалось получить список версий",
|
||||
"mihomo.error.installCoreFailed": "Не удалось установить ядро",
|
||||
"mihomo.enableSmartCore": "Включить Smart ядро",
|
||||
"mihomo.enableSmartOverride": "Использовать автоматическое Smart переопределение правил",
|
||||
"mihomo.smartOverrideTooltip": "Использовать встроенный скрипт умного переопределения в Party для замены url-test и load-balance на Smart группы правил; если указанные группы правил отсутствуют, извлечь все узлы из подписки и заменить правила по умолчанию, подходит для пользователей, которые не хотят заморачиваться, функция действует одним кликом; если используется глобальный режим, выберите узел с именем 'Smart Group'",
|
||||
|
||||
@ -43,6 +43,7 @@
|
||||
"common.updater.versionReady": "v{{version}} 版本就绪",
|
||||
"common.updater.goToDownload": "前往下载",
|
||||
"common.updater.update": "更新",
|
||||
"common.refresh": "刷新",
|
||||
"settings.general": "通用设置",
|
||||
"settings.mihomo": "Mihomo 设置",
|
||||
"settings.language": "语言",
|
||||
@ -128,6 +129,13 @@
|
||||
"mihomo.stableVersion": "稳定版",
|
||||
"mihomo.alphaVersion": "预览版",
|
||||
"mihomo.smartVersion": "Smart 版",
|
||||
"mihomo.specificVersion": "特定版本",
|
||||
"mihomo.selectSpecificVersion": "选择特定版本",
|
||||
"mihomo.searchVersion": "搜索版本...",
|
||||
"mihomo.installVersion": "安装版本",
|
||||
"mihomo.noVersionsFound": "未找到版本",
|
||||
"mihomo.error.fetchTagsFailed": "获取版本列表失败",
|
||||
"mihomo.error.installCoreFailed": "安装内核失败",
|
||||
"mihomo.enableSmartCore": "启用 Smart 内核",
|
||||
"mihomo.enableSmartOverride": "使用自动 Smart 规则覆写",
|
||||
"mihomo.smartOverrideTooltip": "使用 Party 自带的智能覆写脚本,替换 url-test 和 load-balance 为 Smart 规则组;如果没有上述规则组,则提取订阅中的所有节点,并替换默认规则,适合不想折腾的用户,功能一键生效;如果使用全局模式,请选择名称为“Smart Group 的节点",
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Button, Divider, Input, Select, SelectItem, Switch, Tooltip } from '@heroui/react'
|
||||
import { Button, Divider, Input, Select, SelectItem, Switch, Tooltip, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, useDisclosure, Spinner, Chip } from '@heroui/react'
|
||||
import BasePage from '@renderer/components/base/base-page'
|
||||
import SettingCard from '@renderer/components/base/base-setting-card'
|
||||
import SettingItem from '@renderer/components/base/base-setting-item'
|
||||
@ -6,16 +6,19 @@ import { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
|
||||
import { platform } from '@renderer/utils/init'
|
||||
import { FaNetworkWired } from 'react-icons/fa'
|
||||
import { IoMdCloudDownload, IoMdInformationCircleOutline } from 'react-icons/io'
|
||||
import { IoMdCloudDownload, IoMdInformationCircleOutline, IoMdRefresh } from 'react-icons/io'
|
||||
import PubSub from 'pubsub-js'
|
||||
import {
|
||||
mihomoUpgrade,
|
||||
restartCore,
|
||||
startSubStoreBackendServer,
|
||||
triggerSysProxy,
|
||||
showDetailedError
|
||||
showDetailedError,
|
||||
fetchMihomoTags,
|
||||
installSpecificMihomoCore,
|
||||
clearMihomoVersionCache
|
||||
} from '@renderer/utils/ipc'
|
||||
import React, { useState } from 'react'
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import InterfaceModal from '@renderer/components/mihomo/interface-modal'
|
||||
import { MdDeleteForever } from 'react-icons/md'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -23,7 +26,8 @@ import { useTranslation } from 'react-i18next'
|
||||
const CoreMap = {
|
||||
mihomo: 'mihomo.stableVersion',
|
||||
'mihomo-alpha': 'mihomo.alphaVersion',
|
||||
'mihomo-smart': 'mihomo.smartVersion'
|
||||
'mihomo-smart': 'mihomo.smartVersion',
|
||||
'mihomo-specific': 'mihomo.specificVersion'
|
||||
}
|
||||
|
||||
const Mihomo: React.FC = () => {
|
||||
@ -31,6 +35,7 @@ const Mihomo: React.FC = () => {
|
||||
const { appConfig, patchAppConfig } = useAppConfig()
|
||||
const {
|
||||
core = 'mihomo',
|
||||
specificVersion,
|
||||
enableSmartCore = true,
|
||||
enableSmartOverride = true,
|
||||
smartCoreUseLightGBM = false,
|
||||
@ -78,7 +83,14 @@ const Mihomo: React.FC = () => {
|
||||
const [skipAuthPrefixesInput, setSkipAuthPrefixesInput] = useState(skipAuthPrefixes)
|
||||
const [upgrading, setUpgrading] = useState(false)
|
||||
const [lanOpen, setLanOpen] = useState(false)
|
||||
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
const [tags, setTags] = useState<{name: string, zipball_url: string, tarball_url: string}[]>([])
|
||||
const [loadingTags, setLoadingTags] = useState(false)
|
||||
const [selectedTag, setSelectedTag] = useState(specificVersion || '')
|
||||
const [installing, setInstalling] = useState(false)
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [refreshing, setRefreshing] = useState(false)
|
||||
|
||||
const onChangeNeedRestart = async (patch: Partial<IMihomoConfig>): Promise<void> => {
|
||||
await patchControledMihomoConfig(patch)
|
||||
await restartCore()
|
||||
@ -101,7 +113,90 @@ const Mihomo: React.FC = () => {
|
||||
PubSub.publish('mihomo-core-changed')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 获取GitHub标签列表(带缓存)
|
||||
const fetchTags = async (forceRefresh = false) => {
|
||||
setLoadingTags(true)
|
||||
try {
|
||||
const data = await fetchMihomoTags(forceRefresh)
|
||||
setTags(data)
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch tags:', error)
|
||||
alert(t('mihomo.error.fetchTagsFailed'))
|
||||
} finally {
|
||||
setLoadingTags(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 安装特定版本的核心
|
||||
const installSpecificCore = async () => {
|
||||
if (!selectedTag) return
|
||||
|
||||
setInstalling(true)
|
||||
try {
|
||||
// 下载并安装特定版本的核心
|
||||
await installSpecificMihomoCore(selectedTag)
|
||||
|
||||
// 更新应用配置
|
||||
await patchAppConfig({
|
||||
core: 'mihomo-specific',
|
||||
specificVersion: selectedTag
|
||||
})
|
||||
|
||||
// 重启核心
|
||||
await restartCore()
|
||||
|
||||
// 关闭模态框
|
||||
onClose()
|
||||
|
||||
// 通知用户
|
||||
new Notification(t('mihomo.coreUpgradeSuccess'))
|
||||
} catch (error) {
|
||||
console.error('Failed to install specific core:', error)
|
||||
alert(t('mihomo.error.installCoreFailed'))
|
||||
} finally {
|
||||
setInstalling(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新标签列表
|
||||
const refreshTags = async () => {
|
||||
setRefreshing(true)
|
||||
try {
|
||||
// 清除缓存并强制刷新
|
||||
await clearMihomoVersionCache()
|
||||
await fetchTags(true)
|
||||
} finally {
|
||||
setRefreshing(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 打开模态框时获取标签
|
||||
const handleOpenModal = async () => {
|
||||
onOpen()
|
||||
// 先显示缓存的标签(如果有)
|
||||
if (tags.length === 0) {
|
||||
await fetchTags(false) // 使用缓存
|
||||
}
|
||||
|
||||
// 在后台检查更新
|
||||
setTimeout(() => {
|
||||
fetchTags(true) // 强制刷新
|
||||
}, 100)
|
||||
}
|
||||
|
||||
// 过滤标签
|
||||
const filteredTags = tags.filter(tag =>
|
||||
tag.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
)
|
||||
|
||||
// 当模态框打开时,确保选中当前版本
|
||||
useEffect(() => {
|
||||
if (isOpen && specificVersion) {
|
||||
setSelectedTag(specificVersion)
|
||||
}
|
||||
}, [isOpen, specificVersion])
|
||||
|
||||
return (
|
||||
<>
|
||||
{lanOpen && <InterfaceModal onClose={() => setLanOpen(false)} />}
|
||||
@ -162,39 +257,57 @@ const Mihomo: React.FC = () => {
|
||||
)}
|
||||
|
||||
<SettingItem
|
||||
title={t('mihomo.coreVersion')}
|
||||
title={
|
||||
<div className="flex items-center gap-2">
|
||||
<span>{t('mihomo.coreVersion')}</span>
|
||||
{core === 'mihomo-specific' && specificVersion && (
|
||||
<Chip size="sm" variant="flat" color="primary">
|
||||
{specificVersion}
|
||||
</Chip>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
actions={
|
||||
<Button
|
||||
size="sm"
|
||||
isIconOnly
|
||||
title={t('mihomo.upgradeCore')}
|
||||
variant="light"
|
||||
isLoading={upgrading}
|
||||
onPress={async () => {
|
||||
try {
|
||||
setUpgrading(true)
|
||||
await mihomoUpgrade()
|
||||
setTimeout(() => {
|
||||
PubSub.publish('mihomo-core-changed')
|
||||
}, 2000)
|
||||
if (platform !== 'win32') {
|
||||
new Notification(t('mihomo.coreAuthLost'), {
|
||||
body: t('mihomo.coreUpgradeSuccess')
|
||||
})
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
isIconOnly
|
||||
title={t('mihomo.upgradeCore')}
|
||||
variant="light"
|
||||
isLoading={upgrading}
|
||||
onPress={async () => {
|
||||
try {
|
||||
setUpgrading(true)
|
||||
await mihomoUpgrade()
|
||||
setTimeout(() => {
|
||||
PubSub.publish('mihomo-core-changed')
|
||||
}, 2000)
|
||||
if (platform !== 'win32') {
|
||||
new Notification(t('mihomo.coreAuthLost'), {
|
||||
body: t('mihomo.coreUpgradeSuccess')
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
if (typeof e === 'string' && e.includes('already using latest version')) {
|
||||
new Notification(t('mihomo.alreadyLatestVersion'))
|
||||
} else {
|
||||
alert(e)
|
||||
}
|
||||
} finally {
|
||||
setUpgrading(false)
|
||||
}
|
||||
} catch (e) {
|
||||
if (typeof e === 'string' && e.includes('already using latest version')) {
|
||||
new Notification(t('mihomo.alreadyLatestVersion'))
|
||||
} else {
|
||||
alert(e)
|
||||
}
|
||||
} finally {
|
||||
setUpgrading(false)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<IoMdCloudDownload className="text-lg" />
|
||||
</Button>
|
||||
}}
|
||||
>
|
||||
<IoMdCloudDownload className="text-lg" />
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="light"
|
||||
onPress={handleOpenModal}
|
||||
>
|
||||
{t('mihomo.selectSpecificVersion')}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
divider={enableSmartCore && core === 'mihomo-smart'}
|
||||
>
|
||||
@ -204,27 +317,29 @@ const Mihomo: React.FC = () => {
|
||||
? 'data-[hover=true]:bg-blue-100 dark:data-[hover=true]:bg-blue-900/50'
|
||||
: 'data-[hover=true]:bg-default-200'
|
||||
}}
|
||||
className="w-[100px]"
|
||||
className="w-[150px]"
|
||||
size="sm"
|
||||
aria-label={t('mihomo.selectCoreVersion')}
|
||||
selectedKeys={new Set([
|
||||
enableSmartCore
|
||||
? 'mihomo-smart'
|
||||
: (core === 'mihomo-smart' ? 'mihomo' : core)
|
||||
core
|
||||
])}
|
||||
disallowEmptySelection={true}
|
||||
onSelectionChange={async (v) => {
|
||||
handleConfigChangeWithRestart('core', v.currentKey as 'mihomo' | 'mihomo-alpha' | 'mihomo-smart')
|
||||
const selectedCore = v.currentKey as 'mihomo' | 'mihomo-alpha' | 'mihomo-smart' | 'mihomo-specific'
|
||||
// 如果切换到特定版本但没有设置specificVersion,则打开选择模态框
|
||||
if (selectedCore === 'mihomo-specific' && !specificVersion) {
|
||||
handleOpenModal()
|
||||
} else {
|
||||
handleConfigChangeWithRestart('core', selectedCore)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SelectItem key="mihomo">{t(CoreMap['mihomo'])}</SelectItem>
|
||||
<SelectItem key="mihomo-alpha">{t(CoreMap['mihomo-alpha'])}</SelectItem>
|
||||
{enableSmartCore ? (
|
||||
<SelectItem key="mihomo-smart">{t(CoreMap['mihomo-smart'])}</SelectItem>
|
||||
) : (
|
||||
<>
|
||||
<SelectItem key="mihomo">{t(CoreMap['mihomo'])}</SelectItem>
|
||||
<SelectItem key="mihomo-alpha">{t(CoreMap['mihomo-alpha'])}</SelectItem>
|
||||
</>
|
||||
)}
|
||||
) : null}
|
||||
<SelectItem key="mihomo-specific">{t(CoreMap['mihomo-specific'])}</SelectItem>
|
||||
</Select>
|
||||
</SettingItem>
|
||||
|
||||
@ -899,6 +1014,82 @@ const Mihomo: React.FC = () => {
|
||||
</SettingItem>
|
||||
</SettingCard>
|
||||
</BasePage>
|
||||
{/* 自定义版本选择模态框 */}
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
size="5xl"
|
||||
backdrop="blur"
|
||||
classNames={{ backdrop: 'top-[48px]' }}
|
||||
hideCloseButton
|
||||
scrollBehavior="inside"
|
||||
>
|
||||
<ModalContent className="h-full w-[calc(100%-100px)]">
|
||||
<ModalHeader className="flex app-drag">{t('mihomo.selectSpecificVersion')}</ModalHeader>
|
||||
<ModalBody>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
placeholder={t('mihomo.searchVersion')}
|
||||
value={searchTerm}
|
||||
onValueChange={setSearchTerm}
|
||||
className="flex-1"
|
||||
/>
|
||||
<Button
|
||||
isIconOnly
|
||||
variant="light"
|
||||
onPress={refreshTags}
|
||||
isLoading={refreshing}
|
||||
title={t('common.refresh')}
|
||||
>
|
||||
<IoMdRefresh className="text-lg" />
|
||||
</Button>
|
||||
</div>
|
||||
{loadingTags ? (
|
||||
<div className="flex justify-center items-center h-40">
|
||||
<Spinner size="lg" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-full overflow-y-auto">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||
{filteredTags.map((tag) => (
|
||||
<div
|
||||
key={tag.name}
|
||||
className={`p-3 rounded-lg cursor-pointer transition-colors ${
|
||||
selectedTag === tag.name
|
||||
? 'bg-primary/20 border-2 border-primary'
|
||||
: 'bg-default-100 hover:bg-default-200'
|
||||
}`}
|
||||
onClick={() => setSelectedTag(tag.name)}
|
||||
>
|
||||
<div className="font-medium">{tag.name}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{filteredTags.length === 0 && (
|
||||
<div className="text-center py-8 text-default-500">
|
||||
{t('mihomo.noVersionsFound')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button variant="light" onPress={onClose}>
|
||||
{t('common.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
color="primary"
|
||||
isLoading={installing}
|
||||
isDisabled={!selectedTag || installing}
|
||||
onPress={installSpecificCore}
|
||||
>
|
||||
{t('mihomo.installVersion')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -9,6 +9,19 @@ function ipcErrorWrapper(response: any): any {
|
||||
}
|
||||
}
|
||||
|
||||
// GitHub版本管理相关IPC调用
|
||||
export async function fetchMihomoTags(forceRefresh = false): Promise<{name: string, zipball_url: string, tarball_url: string}[]> {
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('fetchMihomoTags', forceRefresh))
|
||||
}
|
||||
|
||||
export async function installSpecificMihomoCore(version: string): Promise<void> {
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('installSpecificMihomoCore', version))
|
||||
}
|
||||
|
||||
export async function clearMihomoVersionCache(): Promise<void> {
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('clearMihomoVersionCache'))
|
||||
}
|
||||
|
||||
export async function mihomoVersion(): Promise<IMihomoVersion> {
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoVersion'))
|
||||
}
|
||||
@ -231,8 +244,6 @@ export async function triggerSysProxy(enable: boolean): Promise<void> {
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('triggerSysProxy', enable))
|
||||
}
|
||||
|
||||
|
||||
|
||||
export async function checkTunPermissions(): Promise<boolean> {
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('checkTunPermissions'))
|
||||
}
|
||||
@ -519,4 +530,4 @@ async function alert<T>(msg: T): Promise<void> {
|
||||
return await window.electron.ipcRenderer.invoke('alert', msgStr)
|
||||
}
|
||||
|
||||
window.alert = alert
|
||||
window.alert = alert
|
||||
3
src/shared/types.d.ts
vendored
3
src/shared/types.d.ts
vendored
@ -216,7 +216,8 @@ interface ISysProxyConfig {
|
||||
}
|
||||
|
||||
interface IAppConfig {
|
||||
core: 'mihomo' | 'mihomo-alpha' | 'mihomo-smart'
|
||||
core: 'mihomo' | 'mihomo-alpha' | 'mihomo-smart' | 'mihomo-specific'
|
||||
specificVersion?: string
|
||||
enableSmartCore: boolean
|
||||
enableSmartOverride: boolean
|
||||
smartCoreUseLightGBM: boolean
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user