feat: launch WebUI directly from subscription manager

This commit is contained in:
Memory 2025-09-27 10:00:21 +08:00 committed by GitHub
parent 76a849e376
commit 99a5505d16
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 99 additions and 1 deletions

View File

@ -183,6 +183,11 @@ export const mihomoUpgrade = async (): Promise<void> => {
return await instance.post('/upgrade')
}
export const mihomoUpgradeUI = async (): Promise<void> => {
const instance = await getAxios()
return await instance.post('/upgrade/ui')
}
// Smart 内核 API
export const mihomoSmartGroupWeights = async (
groupName: string

View File

@ -15,6 +15,7 @@ import {
mihomoUpdateRuleProviders,
mihomoUpgrade,
mihomoUpgradeGeo,
mihomoUpgradeUI,
mihomoVersion,
patchMihomoConfig,
mihomoSmartGroupWeights,
@ -167,6 +168,7 @@ export function registerIpcMainHandlers(): void {
ipcMain.handle('mihomoUnfixedProxy', (_e, group) => ipcErrorWrapper(mihomoUnfixedProxy)(group))
ipcMain.handle('mihomoUpgradeGeo', ipcErrorWrapper(mihomoUpgradeGeo))
ipcMain.handle('mihomoUpgrade', ipcErrorWrapper(mihomoUpgrade))
ipcMain.handle('mihomoUpgradeUI', ipcErrorWrapper(mihomoUpgradeUI))
ipcMain.handle('mihomoProxyDelay', (_e, proxy, url) =>
ipcErrorWrapper(mihomoProxyDelay)(proxy, url)
)

View File

@ -400,6 +400,12 @@
"profiles.remote": "Remote",
"profiles.local": "Local",
"profiles.trafficUsage": "Traffic Usage Progress",
"profiles.openWebUI.title": "WebUI Management Panel",
"profiles.openWebUI.description": "Select a WebUI panel to open",
"profiles.openWebUI.local": "Local WebUI",
"profiles.updateWebUI.button": "Update Panel",
"profiles.updateWebUI.success": "WebUI panel updated successfully",
"profiles.updateWebUI.failed": "WebUI panel update failed: {{error}}",
"profiles.editInfo.title": "Edit Information",
"profiles.editInfo.name": "Name",
"profiles.editInfo.url": "Subscription URL",

View File

@ -405,6 +405,12 @@
"profiles.traffic.expired": "已过期",
"profiles.traffic.remainingDays": "剩余 {{days}} 天",
"profiles.traffic.lastUpdate": "最后更新:{{time}}",
"profiles.openWebUI.title": "WebUI 管理面板",
"profiles.openWebUI.description": "选择一个 WebUI 面板打开",
"profiles.openWebUI.local": "本地 WebUI",
"profiles.updateWebUI.button": "更新面板",
"profiles.updateWebUI.success": "WebUI 面板更新成功",
"profiles.updateWebUI.failed": "WebUI 面板更新失败: {{error}}",
"profiles.editInfo.title": "编辑信息",
"profiles.editInfo.name": "名称",
"profiles.editInfo.url": "订阅地址",

View File

@ -7,12 +7,16 @@ import {
DropdownItem,
DropdownMenu,
DropdownTrigger,
Input
Input,
Card,
CardBody,
CardHeader
} from '@heroui/react'
import BasePage from '@renderer/components/base/base-page'
import ProfileItem from '@renderer/components/profiles/profile-item'
import { useProfileConfig } from '@renderer/hooks/use-profile-config'
import { useAppConfig } from '@renderer/hooks/use-app-config'
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
import { getFilePath, readTextFile, subStoreCollections, subStoreSubs } from '@renderer/utils/ipc'
import type { KeyboardEvent } from 'react'
import { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'
@ -32,6 +36,7 @@ import SubStoreIcon from '@renderer/components/base/substore-icon'
import useSWR from 'swr'
import { useNavigate } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { mihomoUpgradeUI } from '@renderer/utils/ipc'
const Profiles: React.FC = () => {
const { t } = useTranslation()
@ -45,6 +50,9 @@ const Profiles: React.FC = () => {
mutateProfileConfig
} = useProfileConfig()
const { appConfig } = useAppConfig()
const { controledMihomoConfig } = useControledMihomoConfig()
const externalController = controledMihomoConfig?.['external-controller'] || ''
const externalUI = (controledMihomoConfig as any)?.['external-ui']
const { useSubStore = true, useCustomSubStore = false, customSubStoreUrl = '' } = appConfig || {}
const { current, items = [] } = profileConfig || {}
const navigate = useNavigate()
@ -195,6 +203,26 @@ const Profiles: React.FC = () => {
setSortedItems(items)
}, [items])
// 获取本地WebUI的URL
const getLocalWebUIUrl = (): string => {
if (externalController) {
// 将地址转换为WebUI URL
// 例如: 127.0.0.1:9090 -> http://127.0.0.1:9090/ui
const controller = externalController.replace('0.0.0.0', '127.0.0.1')
// 如果配置了external-ui使用/ui路径否则可能需要使用不同的路径
const uiPath = externalUI ? '/ui' : '/ui' // 默认使用/ui路径
return `http://${controller}${uiPath}`
}
// 默认URL
return 'http://127.0.0.1:9090/ui'
}
// 检查本地WebUI是否可用
const isLocalWebUIAvailable = (): boolean => {
// 如果有配置的external-controller则认为本地WebUI可用
return !!externalController
}
return (
<BasePage
ref={pageRef}
@ -378,6 +406,53 @@ const Profiles: React.FC = () => {
</div>
<Divider />
</div>
{/* WebUI Card with Multiple Options */}
<div className="m-2">
<Card>
<CardHeader className="flex gap-3">
<div className="flex flex-col">
<p className="text-md">{t('profiles.openWebUI.title')}</p>
<p className="text-small text-default-500">{t('profiles.openWebUI.description')}</p>
</div>
</CardHeader>
<CardBody>
<div className="flex gap-2 flex-wrap">
<Button
onPress={() => window.open('https://metacubexd.pages.dev/', '_blank')}
>
MetaCubeXD
</Button>
<Button
onPress={() => window.open('https://zashboard.pages.dev/', '_blank')}
>
Zashboard
</Button>
{isLocalWebUIAvailable() && (
<>
<Button
onPress={() => window.open(getLocalWebUIUrl(), '_blank')}
>
{t('profiles.openWebUI.local')}
</Button>
<Button
color="success"
onPress={async () => {
try {
await mihomoUpgradeUI()
new Notification(t('profiles.updateWebUI.success'))
} catch (e) {
new Notification(t('profiles.updateWebUI.failed', { error: String(e) }))
}
}}
>
{t('profiles.updateWebUI.button')}
</Button>
</>
)}
</div>
</CardBody>
</Card>
</div>
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={onDragEnd}>
<div
className={`${fileOver ? 'blur-sm' : ''} grid sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-2 m-2`}

View File

@ -84,6 +84,10 @@ export async function mihomoUpgrade(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoUpgrade'))
}
export async function mihomoUpgradeUI(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoUpgradeUI'))
}
export async function mihomoProxyDelay(proxy: string, url?: string): Promise<IMihomoDelay> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoProxyDelay', proxy, url))
}