From a7de9b2588e133de00a4ffd176f4b1b1915698c1 Mon Sep 17 00:00:00 2001 From: Memory <134070804+Memory2314@users.noreply.github.com> Date: Fri, 3 Oct 2025 22:49:41 +0800 Subject: [PATCH] refactor: Redesign and migrate WebUI to global settings --- .../src/components/settings/webui-config.tsx | 356 ++++++++++++++++++ src/renderer/src/locales/en-US.json | 21 +- src/renderer/src/locales/zh-CN.json | 21 +- src/renderer/src/pages/profiles.tsx | 77 +--- src/renderer/src/pages/settings.tsx | 2 + 5 files changed, 389 insertions(+), 88 deletions(-) create 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 new file mode 100644 index 0000000..37ad16d --- /dev/null +++ b/src/renderer/src/components/settings/webui-config.tsx @@ -0,0 +1,356 @@ +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 d6147cb..e0e458c 100644 --- a/src/renderer/src/locales/en-US.json +++ b/src/renderer/src/locales/en-US.json @@ -91,6 +91,21 @@ "settings.links.github": "GitHub Repository", "settings.links.telegram": "Telegram Group", "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", + "settings.webui.noSecret": "No Secret", + "settings.webui.panelName": "Panel Name", + "settings.webui.panelUrl": "Panel URL", + "settings.webui.panelNamePlaceholder": "Enter panel name", + "settings.webui.panelUrlPlaceholder": "Enter panel URL", + "settings.webui.variableHint": "Available Variables", + "settings.webui.addPanel": "Add Panel", + "settings.webui.panels": "Panel List", + "settings.webui.restoreDefaults": "Restore Defaults", "mihomo.userAgent": "Subscription User Agent", "mihomo.userAgentPlaceholder": "Default: mihomo.party/v{{version}} (clash.meta)", "mihomo.delayTest.url": "Delay Test URL", @@ -405,12 +420,6 @@ "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", diff --git a/src/renderer/src/locales/zh-CN.json b/src/renderer/src/locales/zh-CN.json index 40d6f47..9949373 100644 --- a/src/renderer/src/locales/zh-CN.json +++ b/src/renderer/src/locales/zh-CN.json @@ -91,6 +91,21 @@ "settings.links.github": "GitHub 仓库", "settings.links.telegram": "Telegram 群组", "settings.title": "应用设置", + "settings.webui.title": "WebUI 管理面板", + "settings.webui.manage": "管理面板", + "settings.webui.currentConfig": "当前配置信息", + "settings.webui.host": "主机", + "settings.webui.port": "端口", + "settings.webui.secret": "密钥", + "settings.webui.noSecret": "无密钥", + "settings.webui.panelName": "面板名称", + "settings.webui.panelUrl": "面板URL", + "settings.webui.panelNamePlaceholder": "输入面板名称", + "settings.webui.panelUrlPlaceholder": "输入面板URL", + "settings.webui.variableHint": "可用变量", + "settings.webui.addPanel": "添加面板", + "settings.webui.panels": "面板列表", + "settings.webui.restoreDefaults": "恢复默认", "mihomo.title": "内核设置", "mihomo.restart": "重启内核", "mihomo.memory": "内存使用", @@ -410,12 +425,6 @@ "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": "订阅地址", diff --git a/src/renderer/src/pages/profiles.tsx b/src/renderer/src/pages/profiles.tsx index b5be2e1..50e38cd 100644 --- a/src/renderer/src/pages/profiles.tsx +++ b/src/renderer/src/pages/profiles.tsx @@ -7,16 +7,12 @@ import { DropdownItem, DropdownMenu, DropdownTrigger, - Input, - Card, - CardBody, - CardHeader + Input } 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' @@ -36,7 +32,6 @@ 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() @@ -50,9 +45,6 @@ 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() @@ -203,26 +195,6 @@ 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 ( { - {/* WebUI Card with Multiple Options */} -
- - -
-

{t('profiles.openWebUI.title')}

-

{t('profiles.openWebUI.description')}

-
-
- -
- - - {isLocalWebUIAvailable() && ( - <> - - - - )} -
-
-
-
{ @@ -60,6 +61,7 @@ const Settings: React.FC = () => { } > +