From 1ff165d61b9e24834109d5c4d82de04c390d2a03 Mon Sep 17 00:00:00 2001
From: Memory <134070804+Memory2314@users.noreply.github.com>
Date: Mon, 6 Oct 2025 12:40:01 +0800
Subject: [PATCH] migrate: move WebUI from global settings to mihomo page
---
.../src/components/settings/webui-config.tsx | 356 ------------------
src/renderer/src/locales/en-US.json | 1 -
src/renderer/src/locales/zh-CN.json | 1 -
src/renderer/src/pages/mihomo.tsx | 337 ++++++++++++++++-
src/renderer/src/pages/settings.tsx | 2 -
5 files changed, 334 insertions(+), 363 deletions(-)
delete mode 100644 src/renderer/src/components/settings/webui-config.tsx
diff --git a/src/renderer/src/components/settings/webui-config.tsx b/src/renderer/src/components/settings/webui-config.tsx
deleted file mode 100644
index 37ad16d..0000000
--- a/src/renderer/src/components/settings/webui-config.tsx
+++ /dev/null
@@ -1,356 +0,0 @@
-import React, { useState, useEffect, useRef } from 'react'
-import SettingCard from '../base/base-setting-card'
-import SettingItem from '../base/base-setting-item'
-import { Button, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Input } from '@heroui/react'
-import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
-import { useTranslation } from 'react-i18next'
-import { MdEdit, MdDelete, MdOpenInNew } from 'react-icons/md'
-
-interface WebUIPanel {
- id: string
- name: string
- url: string
- isDefault?: boolean
-}
-
-// 用于高亮显示URL中的变量
-const HighlightedUrl: React.FC<{ url: string }> = ({ url }) => {
- const parts = url.split(/(%host|%port|%secret)/g)
-
- return (
-
- {parts.map((part, index) => {
- if (part === '%host' || part === '%port' || part === '%secret') {
- return (
-
- {part}
-
- )
- }
- return part
- })}
-
- )
-}
-
-// 可点击的变量标签组件
-const ClickableVariableTag: React.FC<{
- variable: string;
- onClick: (variable: string) => void
-}> = ({ variable, onClick }) => {
- return (
- onClick(variable)}
- >
- {variable}
-
- )
-}
-
-const WebUIConfig: React.FC = () => {
- const { t } = useTranslation()
- const { controledMihomoConfig } = useControledMihomoConfig()
-
- const externalController = controledMihomoConfig?.['external-controller'] || ''
- const secret = controledMihomoConfig?.secret || ''
-
- // 解析主机和端口
- const parseController = () => {
- if (externalController) {
- const [host, port] = externalController.split(':')
- return { host: host.replace('0.0.0.0', '127.0.0.1'), port }
- }
- return { host: '127.0.0.1', port: '9090' }
- }
-
- const { host, port } = parseController()
-
- // 默认WebUI面板选项
- const defaultWebUIPanels: WebUIPanel[] = [
- {
- id: 'metacubexd',
- name: 'MetaCubeXD',
- url: 'https://metacubex.github.io/metacubexd/#/setup?http=true&hostname=%host&port=%port&secret=%secret',
- isDefault: true
- },
- {
- id: 'yacd',
- name: 'YACD',
- url: 'https://yacd.metacubex.one/?hostname=%host&port=%port&secret=%secret',
- isDefault: true
- },
- {
- id: 'zashboard',
- name: 'Zashboard',
- url: 'https://board.zash.run.place/#/setup?http=true&hostname=%host&port=%port&secret=%secret',
- isDefault: true
- }
- ]
-
- const [isModalOpen, setIsModalOpen] = useState(false)
- const [allPanels, setAllPanels] = useState([])
- const [editingPanel, setEditingPanel] = useState(null)
- const [newPanelName, setNewPanelName] = useState('')
- const [newPanelUrl, setNewPanelUrl] = useState('')
-
-
- const urlInputRef = useRef(null)
-
- // 初始化面板列表
- useEffect(() => {
- const savedPanels = localStorage.getItem('webui-panels')
- if (savedPanels) {
- setAllPanels(JSON.parse(savedPanels))
- } else {
- setAllPanels(defaultWebUIPanels)
- }
- }, [])
-
- // 保存面板列表到localStorage
- useEffect(() => {
- if (allPanels.length > 0) {
- localStorage.setItem('webui-panels', JSON.stringify(allPanels))
- }
- }, [allPanels])
-
- // 在URL输入框光标处插入或替换变量
- const insertVariableAtCursor = (variable: string) => {
- if (!urlInputRef.current) return
-
- const input = urlInputRef.current
- const start = input.selectionStart || 0
- const end = input.selectionEnd || 0
- const currentValue = newPanelUrl || ''
-
- // 如果有选中文本,则替换选中的文本
- const newValue = currentValue.substring(0, start) + variable + currentValue.substring(end)
-
- setNewPanelUrl(newValue)
-
- // 设置光标位置到插入变量之后
- setTimeout(() => {
- if (urlInputRef.current) {
- const newCursorPos = start + variable.length
- urlInputRef.current.setSelectionRange(newCursorPos, newCursorPos)
- urlInputRef.current.focus()
- }
- }, 0)
- }
-
- // 打开WebUI面板
- const openWebUI = (panel: WebUIPanel) => {
- const url = panel.url
- .replace('%host', host)
- .replace('%port', port)
- .replace('%secret', secret)
- window.open(url, '_blank')
- }
-
- // 添加新面板
- const addNewPanel = () => {
- if (newPanelName && newPanelUrl) {
- const newPanel: WebUIPanel = {
- id: Date.now().toString(),
- name: newPanelName,
- url: newPanelUrl
- }
- setAllPanels([...allPanels, newPanel])
- setNewPanelName('')
- setNewPanelUrl('')
- setEditingPanel(null)
- }
- }
-
- // 更新面板
- const updatePanel = () => {
- if (editingPanel && newPanelName && newPanelUrl) {
- const updatedPanels = allPanels.map(panel =>
- panel.id === editingPanel.id
- ? { ...panel, name: newPanelName, url: newPanelUrl }
- : panel
- )
- setAllPanels(updatedPanels)
- setEditingPanel(null)
- setNewPanelName('')
- setNewPanelUrl('')
- }
- }
-
- // 删除面板
- const deletePanel = (id: string) => {
- setAllPanels(allPanels.filter(panel => panel.id !== id))
- }
-
- // 开始编辑面板
- const startEditing = (panel: WebUIPanel) => {
- setEditingPanel(panel)
- setNewPanelName(panel.name)
- setNewPanelUrl(panel.url)
- }
-
- // 取消编辑
- const cancelEditing = () => {
- setEditingPanel(null)
- setNewPanelName('')
- setNewPanelUrl('')
- }
-
- // 恢复默认面板
- const restoreDefaultPanels = () => {
- setAllPanels(defaultWebUIPanels)
- }
-
- return (
-
-
-
-
-
-
-
-
-
{t('settings.webui.host')}: {host}
-
{t('settings.webui.port')}: {port}
-
-
-
- {/* 面板管理模态框 */}
-
-
-
- {t('settings.webui.manage')}
-
-
-
- {/* 添加/编辑面板表单 */}
-
-
-
-
- {t('settings.webui.variableHint')}:
-
-
-
-
-
- {editingPanel ? (
- <>
-
-
- >
- ) : (
-
- )}
-
-
-
-
- {/* 面板列表 */}
-
-
{t('settings.webui.panels')}
- {allPanels.map(panel => (
-
-
-
-
-
-
-
-
- ))}
-
-
-
-
-
-
-
-
-
- )
-}
-
-export default WebUIConfig
\ No newline at end of file
diff --git a/src/renderer/src/locales/en-US.json b/src/renderer/src/locales/en-US.json
index e0e458c..31488af 100644
--- a/src/renderer/src/locales/en-US.json
+++ b/src/renderer/src/locales/en-US.json
@@ -93,7 +93,6 @@
"settings.title": "Application Settings",
"settings.webui.title": "WebUI Management Panel",
"settings.webui.manage": "Manage Panels",
- "settings.webui.currentConfig": "Current Configuration",
"settings.webui.host": "Host",
"settings.webui.port": "Port",
"settings.webui.secret": "Secret",
diff --git a/src/renderer/src/locales/zh-CN.json b/src/renderer/src/locales/zh-CN.json
index 9949373..3c2237e 100644
--- a/src/renderer/src/locales/zh-CN.json
+++ b/src/renderer/src/locales/zh-CN.json
@@ -93,7 +93,6 @@
"settings.title": "应用设置",
"settings.webui.title": "WebUI 管理面板",
"settings.webui.manage": "管理面板",
- "settings.webui.currentConfig": "当前配置信息",
"settings.webui.host": "主机",
"settings.webui.port": "端口",
"settings.webui.secret": "密钥",
diff --git a/src/renderer/src/pages/mihomo.tsx b/src/renderer/src/pages/mihomo.tsx
index 9da2d23..c405254 100644
--- a/src/renderer/src/pages/mihomo.tsx
+++ b/src/renderer/src/pages/mihomo.tsx
@@ -18,9 +18,9 @@ import {
installSpecificMihomoCore,
clearMihomoVersionCache
} from '@renderer/utils/ipc'
-import React, { useState, useEffect } from 'react'
+import React, { useState, useEffect, useRef } from 'react'
import InterfaceModal from '@renderer/components/mihomo/interface-modal'
-import { MdDeleteForever } from 'react-icons/md'
+import { MdDeleteForever, MdEdit, MdDelete, MdOpenInNew } from 'react-icons/md'
import { useTranslation } from 'react-i18next'
const CoreMap = {
@@ -48,10 +48,18 @@ const Mihomo: React.FC = () => {
disableSystemCA,
skipSafePathCheck } = appConfig || {}
const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig()
+
+ interface WebUIPanel {
+ id: string
+ name: string
+ url: string
+ isDefault?: boolean
+ }
+
const {
ipv6,
'external-controller': externalController = '',
- secret,
+ secret = '',
authentication = [],
'skip-auth-prefixes': skipAuthPrefixes = ['127.0.0.1/32', '::1/128'],
'log-level': logLevel = 'info',
@@ -91,6 +99,187 @@ const Mihomo: React.FC = () => {
const [searchTerm, setSearchTerm] = useState('')
const [refreshing, setRefreshing] = useState(false)
+ // WebUI管理状态
+ const [isWebUIModalOpen, setIsWebUIModalOpen] = useState(false)
+ const [allPanels, setAllPanels] = useState([])
+ const [editingPanel, setEditingPanel] = useState(null)
+ const [newPanelName, setNewPanelName] = useState('')
+ const [newPanelUrl, setNewPanelUrl] = useState('')
+
+ const urlInputRef = useRef(null)
+
+ // 解析主机和端口
+ const parseController = () => {
+ if (externalController) {
+ const [host, port] = externalController.split(':')
+ return { host: host.replace('0.0.0.0', '127.0.0.1'), port }
+ }
+ return { host: '127.0.0.1', port: '9090' }
+ }
+
+ const { host, port } = parseController()
+
+ // 默认WebUI面板选项
+ const defaultWebUIPanels: WebUIPanel[] = [
+ {
+ id: 'metacubexd',
+ name: 'MetaCubeXD',
+ url: 'https://metacubex.github.io/metacubexd/#/setup?http=true&hostname=%host&port=%port&secret=%secret',
+ isDefault: true
+ },
+ {
+ id: 'yacd',
+ name: 'YACD',
+ url: 'https://yacd.metacubex.one/?hostname=%host&port=%port&secret=%secret',
+ isDefault: true
+ },
+ {
+ id: 'zashboard',
+ name: 'Zashboard',
+ url: 'https://board.zash.run.place/#/setup?http=true&hostname=%host&port=%port&secret=%secret',
+ isDefault: true
+ }
+ ]
+
+ // 初始化面板列表
+ useEffect(() => {
+ const savedPanels = localStorage.getItem('webui-panels')
+ if (savedPanels) {
+ setAllPanels(JSON.parse(savedPanels))
+ } else {
+ setAllPanels(defaultWebUIPanels)
+ }
+ }, [])
+
+ // 保存面板列表到localStorage
+ useEffect(() => {
+ if (allPanels.length > 0) {
+ localStorage.setItem('webui-panels', JSON.stringify(allPanels))
+ }
+ }, [allPanels])
+
+ // 在URL输入框光标处插入或替换变量
+ const insertVariableAtCursor = (variable: string) => {
+ if (!urlInputRef.current) return
+
+ const input = urlInputRef.current
+ const start = input.selectionStart || 0
+ const end = input.selectionEnd || 0
+ const currentValue = newPanelUrl || ''
+
+ // 如果有选中文本,则替换选中的文本
+ const newValue = currentValue.substring(0, start) + variable + currentValue.substring(end)
+
+ setNewPanelUrl(newValue)
+
+ // 设置光标位置到插入变量之后
+ setTimeout(() => {
+ if (urlInputRef.current) {
+ const newCursorPos = start + variable.length
+ urlInputRef.current.setSelectionRange(newCursorPos, newCursorPos)
+ urlInputRef.current.focus()
+ }
+ }, 0)
+ }
+
+ // 打开WebUI面板
+ const openWebUI = (panel: WebUIPanel) => {
+ const url = panel.url
+ .replace('%host', host)
+ .replace('%port', port)
+ .replace('%secret', secret)
+ window.open(url, '_blank')
+ }
+
+ // 添加新面板
+ const addNewPanel = () => {
+ if (newPanelName && newPanelUrl) {
+ const newPanel: WebUIPanel = {
+ id: Date.now().toString(),
+ name: newPanelName,
+ url: newPanelUrl
+ }
+ setAllPanels([...allPanels, newPanel])
+ setNewPanelName('')
+ setNewPanelUrl('')
+ setEditingPanel(null)
+ }
+ }
+
+ // 更新面板
+ const updatePanel = () => {
+ if (editingPanel && newPanelName && newPanelUrl) {
+ const updatedPanels = allPanels.map(panel =>
+ panel.id === editingPanel.id
+ ? { ...panel, name: newPanelName, url: newPanelUrl }
+ : panel
+ )
+ setAllPanels(updatedPanels)
+ setEditingPanel(null)
+ setNewPanelName('')
+ setNewPanelUrl('')
+ }
+ }
+
+ // 删除面板
+ const deletePanel = (id: string) => {
+ setAllPanels(allPanels.filter(panel => panel.id !== id))
+ }
+
+ // 开始编辑面板
+ const startEditing = (panel: WebUIPanel) => {
+ setEditingPanel(panel)
+ setNewPanelName(panel.name)
+ setNewPanelUrl(panel.url)
+ }
+
+ // 取消编辑
+ const cancelEditing = () => {
+ setEditingPanel(null)
+ setNewPanelName('')
+ setNewPanelUrl('')
+ }
+
+ // 恢复默认面板
+ const restoreDefaultPanels = () => {
+ setAllPanels(defaultWebUIPanels)
+ }
+
+ // 用于高亮显示URL中的变量
+ const HighlightedUrl: React.FC<{ url: string }> = ({ url }) => {
+ const parts = url.split(/(%host|%port|%secret)/g)
+
+ return (
+
+ {parts.map((part, index) => {
+ if (part === '%host' || part === '%port' || part === '%secret') {
+ return (
+
+ {part}
+
+ )
+ }
+ return part
+ })}
+
+ )
+ }
+
+ // 可点击的变量标签组件
+ const ClickableVariableTag: React.FC<{
+ variable: string;
+ onClick: (variable: string) => void
+ }> = ({ variable, onClick }) => {
+ return (
+ onClick(variable)}
+ >
+ {variable}
+
+ )
+ }
+
const onChangeNeedRestart = async (patch: Partial): Promise => {
await patchControledMihomoConfig(patch)
await restartCore()
@@ -626,6 +815,18 @@ const Mihomo: React.FC = () => {
/>
+
+
+
+
+
{
+
+ {/* WebUI 管理模态框 */}
+
+
+
+ {t('settings.webui.manage')}
+
+
+
+ {/* 添加/编辑面板表单 */}
+
+
+
+
+ {t('settings.webui.variableHint')}:
+
+
+
+
+
+ {editingPanel ? (
+ <>
+
+
+ >
+ ) : (
+
+ )}
+
+
+
+
+ {/* 面板列表 */}
+
+
{t('settings.webui.panels')}
+ {allPanels.map(panel => (
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+
{/* 自定义版本选择模态框 */}
{
@@ -61,7 +60,6 @@ const Settings: React.FC = () => {
}
>
-