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 => ( -
-
-

{panel.name}

- -
-
- - - -
-
- ))} -
-
-
- - - -
-
-
- ) -} - -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 => ( +
+
+

{panel.name}

+ +
+
+ + + +
+
+ ))} +
+
+
+ + + +
+
+ {/* 自定义版本选择模态框 */} { @@ -61,7 +60,6 @@ const Settings: React.FC = () => { } > -