Compare commits

...

4 Commits

Author SHA1 Message Date
Memory
a7de9b2588
refactor: Redesign and migrate WebUI to global settings 2025-10-03 22:49:41 +08:00
ezequielnick
f34cc976b4 style: update macos dock icon 2025-10-03 22:43:05 +08:00
ezequielnick
814112f541 style: update logo & changelog 2025-10-03 21:01:41 +08:00
Jiawen Geng
717239f56b
feat: add placeholder for subscription URL in profile management across multiple languages (#1244) 2025-10-03 18:48:32 +08:00
27 changed files with 422 additions and 129 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 211 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 229 KiB

After

Width:  |  Height:  |  Size: 204 KiB

View File

@ -1,8 +1,31 @@
## 1.8.8
### 新功能 (Feat)
- 增加内核版本选择
- 记住日志页面的筛选关键字
- Webdav增加Cron定时备份
- 连接卡片纯数字显示样式
- 支持修改窗口触发行为
### 修复 (Fix)
- MacOS 首次启动时的 ENOENT: no such file or directory(config.yaml)
- 自动更新获取老的文件名称
- 修复 mihomo.yaml 文件缺失的问题
- Smart 配置文件验证出错的问题
- 开发环境的 electron 问题
### 优化 (Optimize)
- 加快以管理员模式重启速度
- 优化仅用户滚动滚轮时触发自动滚动
- 改进俄语翻译
- 使用重载替换不必要的重启
# 其他 (chore)
- 更新依赖
### 样式调整 (Sytle)
- 改进 logo 设计
- 卡片尺寸
## 1.8.7

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -4,12 +4,12 @@ import { GenIcon } from 'react-icons'
function MihomoIcon(props: any): React.JSX.Element {
return GenIcon({
tag: 'svg',
attr: { viewBox: '0 0 120 150' },
attr: { viewBox: '0 0 58 61.53' },
child: [
{
tag: 'path',
attr: {
d: 'M123,105.41c0,15.99-13.44,20.46-20.54,20.6h-29.1c5.33-1.74,8.97-5.24,9.18-5.44.05-.05.28-.27.55-.56h19.31c1.44-.04,14.6-.78,14.6-14.6,0-9.22-3.85-21.88-34.64-29.67l-1.47-6.53c28.67,6.6,42.11,18.22,42.11,36.2Z',
d: 'M57.99,14.06c-.26-4.98-4.34-9.05-9.31-9.31-5.69-.3-10.39,4.22-10.39,9.84h0c0,.39-.24.73-.6.86l-1.08.38c-.49.17-.77.68-.65,1.18l.2.9c.13.6.76.95,1.34.74l.7-.25c.42-.15.88.04,1.07.45,1.59,3.31,4.96,5.59,8.88,5.59,5.62,0,10.14-4.7,9.84-10.39Z',
fill: 'currentColor'
},
child: []
@ -17,27 +17,7 @@ function MihomoIcon(props: any): React.JSX.Element {
{
tag: 'path',
attr: {
d: 'M81.1,37.6c-2.42.76-4.68,1.55-6.78,2.37l-1.33-5.92c2.05-.77,4.21-1.51,6.47-2.21.27,2.03.83,3.96,1.64,5.76Z',
fill: 'currentColor'
},
child: []
},
{
tag: 'circle',
attr: {
cx: '53.83',
cy: '33.01',
r: '6',
fill: 'currentColor'
},
child: []
},
{
tag: 'circle',
attr: {
cx: '33.83',
cy: '33.01',
r: '6',
d: 'M40.49,33.96l-1.01-.08.84,3.69h.03s.14,0,.14,0c6.11,0,12.43,4.21,12.43,11.26,0,5.02-3.54,9.1-7.89,9.1h-3.81c-.33.57-.71,1.13-1.13,1.66-.58.72-1.22,1.37-1.91,1.95h6.85c6.35,0,11.49-5.69,11.49-12.71h0c0-9-7.9-14.87-16.04-14.87Z',
fill: 'currentColor'
},
child: []
@ -45,23 +25,7 @@ function MihomoIcon(props: any): React.JSX.Element {
{
tag: 'path',
attr: {
d: 'M67.53,126.01h-1.49c-1.83,0-3.4-1.54-3.19-3.36.17-1.52,1.4-2.64,2.98-2.64h1.7c4.3,0,8.3-1.9,11-5.3,2.7-3.3,3.6-7.6,2.7-11.8L60.13,8.91l-.4.4c-4.3,4.4-9.9,6.7-15.9,6.7s-11.6-2.3-15.8-6.6l-.5-.4L6.33,103.01c-.9,4.1,0,8.4,2.7,11.8s6.7,5.3,11,5.3h.59c1.83,0,3.4,1.54,3.19,3.36-.17,1.52-1.4,2.64-2.98,2.64h-.8c-6.1,0-11.8-2.7-15.7-7.5-3.8-4.9-5.2-11-3.8-17L22.93,2.31c.2-1.1,1-1.9,2.1-2.2,1-.3,2.2,0,3,.8l4.3,4.3c3,3.1,7.1,4.8,11.5,4.8s8.5-1.7,11.6-4.8l4.24-4.24c.37-.37.81-.68,1.31-.83,1.95-.58,3.56.68,3.86,2.16l22.3,99.3c1.3,5.9-.1,12.1-3.9,16.8-.4.5-1.4,1.5-1.4,1.5,0,0-6.26,6.1-14.3,6.1h0Z',
fill: 'currentColor'
},
child: []
},
{
tag: 'path',
attr: {
d: 'M22.23,70.11c1.6-.3,3.2.7,3.5,2.4l9,45.1c.3,1.5,1.5,2.5,3,2.5,1.7,0,3-1.3,3-3v-34.1c0-1.7,1.3-3,3-3s3,1.3,3,3v34c0,1.6,1.4,3,3.1,3h1c1.5,0,2.7-1,3-2.5l9.1-45.1c.3-1.6,1.9-2.7,3.5-2.3,1.6.3,2.7,1.9,2.3,3.5l-9.1,45c-.7,4.3-4.5,7.4-8.9,7.4h-1c-2.3,0-4.5-.9-6.1-2.3-1.6,1.4-3.7,2.3-6,2.3-4.3,0-7.9-3-8.8-7.3l-9-45.1c-.3-1.6.8-3.2,2.4-3.5Z',
fill: 'currentColor'
},
child: []
},
{
tag: 'path',
attr: {
d: 'M100.75,7.39c-11.85,0-21.5,9.64-21.5,21.5,0,1,.07,1.99.21,2.95.27,2.03.83,3.96,1.64,5.76,3.35,7.53,10.9,12.79,19.65,12.79,11.86,0,21.5-9.65,21.5-21.5s-9.64-21.5-21.5-21.5ZM100.75,45.39c-9.1,0-16.5-7.4-16.5-16.5s7.4-16.5,16.5-16.5,16.5,7.4,16.5,16.5-7.4,16.5-16.5,16.5Z',
d: 'M14.79,13.24c1.93,0,3.49,1.56,3.49,3.49s-1.56,3.49-3.49,3.49-3.49-1.56-3.49-3.49,1.56-3.49,3.49-3.49ZM25.6,13.24c1.93,0,3.49,1.56,3.49,3.49s-1.56,3.49-3.49,3.49-3.49-1.56-3.49-3.49,1.56-3.49,3.49-3.49ZM40.12,48.91L29.04,0h-.02c0,.07.02.13.02.2,0,3.3-3.96,5.98-8.84,5.98S11.36,3.51,11.36.2c0-.07.01-.13.02-.2h-.04L.26,48.91c-1.47,6.47,3.53,12.62,10.26,12.62h19.34c6.73,0,11.73-6.15,10.26-12.62ZM16.61,58.42c-.78,0-1.4-.63-1.4-1.4,0-14.27-6.25-14.65-6.52-14.66-.78,0-1.39-.63-1.39-1.4s.64-1.4,1.42-1.4c.38,0,9.3.2,9.3,17.47,0,.78-.63,1.4-1.4,1.4ZM31.7,42.36c-.31.01-6.52.45-6.52,14.66,0,.78-.63,1.4-1.4,1.4s-1.4-.63-1.4-1.4c0-17.27,8.92-17.47,9.3-17.47.78,0,1.4.63,1.4,1.4s-.62,1.39-1.38,1.4Z',
fill: 'currentColor'
},
child: []

View File

@ -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 (
<p className="text-sm text-default-500 break-all">
{parts.map((part, index) => {
if (part === '%host' || part === '%port' || part === '%secret') {
return (
<span key={index} className="bg-warning-200 text-warning-800 px-1 rounded">
{part}
</span>
)
}
return part
})}
</p>
)
}
// 可点击的变量标签组件
const ClickableVariableTag: React.FC<{
variable: string;
onClick: (variable: string) => void
}> = ({ variable, onClick }) => {
return (
<span
className="bg-warning-200 text-warning-800 px-1 rounded ml-1 cursor-pointer hover:bg-warning-300"
onClick={() => onClick(variable)}
>
{variable}
</span>
)
}
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<WebUIPanel[]>([])
const [editingPanel, setEditingPanel] = useState<WebUIPanel | null>(null)
const [newPanelName, setNewPanelName] = useState('')
const [newPanelUrl, setNewPanelUrl] = useState('')
const urlInputRef = useRef<HTMLInputElement>(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 (
<SettingCard>
<SettingItem title={t('settings.webui.title')} divider>
<div className="flex gap-2">
<Button
size="sm"
color="primary"
onPress={() => setIsModalOpen(true)}
>
{t('settings.webui.manage')}
</Button>
</div>
</SettingItem>
<SettingItem title={t('settings.webui.currentConfig')}>
<div className="text-sm text-default-500">
<p>{t('settings.webui.host')}: {host}</p>
<p>{t('settings.webui.port')}: {port}</p>
</div>
</SettingItem>
{/* 面板管理模态框 */}
<Modal
isOpen={isModalOpen}
onOpenChange={setIsModalOpen}
size="5xl"
scrollBehavior="inside"
backdrop="blur"
classNames={{ backdrop: 'top-[48px]' }}
hideCloseButton
>
<ModalContent className="h-full w-[calc(100%-100px)]">
<ModalHeader className="flex pb-0 app-drag">
{t('settings.webui.manage')}
</ModalHeader>
<ModalBody className="flex flex-col h-full">
<div className="flex flex-col h-full">
{/* 添加/编辑面板表单 */}
<div className="flex flex-col gap-2 p-3 bg-default-100 rounded-lg flex-shrink-0">
<Input
label={t('settings.webui.panelName')}
placeholder={t('settings.webui.panelNamePlaceholder')}
value={newPanelName}
onValueChange={setNewPanelName}
/>
<Input
ref={urlInputRef}
label={t('settings.webui.panelUrl')}
placeholder={t('settings.webui.panelUrlPlaceholder')}
value={newPanelUrl}
onValueChange={setNewPanelUrl}
/>
<div className="text-xs text-default-500">
{t('settings.webui.variableHint')}:
<ClickableVariableTag variable="%host" onClick={insertVariableAtCursor} />
<ClickableVariableTag variable="%port" onClick={insertVariableAtCursor} />
<ClickableVariableTag variable="%secret" onClick={insertVariableAtCursor} />
</div>
<div className="flex gap-2">
{editingPanel ? (
<>
<Button
size="sm"
color="primary"
onPress={updatePanel}
isDisabled={!newPanelName || !newPanelUrl}
>
{t('common.save')}
</Button>
<Button
size="sm"
color="default"
variant="bordered"
onPress={cancelEditing}
>
{t('common.cancel')}
</Button>
</>
) : (
<Button
size="sm"
color="primary"
onPress={addNewPanel}
isDisabled={!newPanelName || !newPanelUrl}
>
{t('settings.webui.addPanel')}
</Button>
)}
<Button
size="sm"
color="warning"
variant="bordered"
onPress={restoreDefaultPanels}
>
{t('settings.webui.restoreDefaults')}
</Button>
</div>
</div>
{/* 面板列表 */}
<div className="flex flex-col gap-2 mt-2 overflow-y-auto flex-grow">
<h3 className="text-lg font-semibold">{t('settings.webui.panels')}</h3>
{allPanels.map(panel => (
<div key={panel.id} className="flex items-start justify-between p-3 bg-default-50 rounded-lg flex-shrink-0">
<div className="flex-1 mr-2">
<p className="font-medium">{panel.name}</p>
<HighlightedUrl url={panel.url} />
</div>
<div className="flex gap-2">
<Button
isIconOnly
size="sm"
color="primary"
onPress={() => openWebUI(panel)}
>
<MdOpenInNew />
</Button>
<Button
isIconOnly
size="sm"
color="warning"
onPress={() => startEditing(panel)}
>
<MdEdit />
</Button>
<Button
isIconOnly
size="sm"
color="danger"
onPress={() => deletePanel(panel.id)}
>
<MdDelete />
</Button>
</div>
</div>
))}
</div>
</div>
</ModalBody>
<ModalFooter className="pt-0">
<Button
color="primary"
onPress={() => setIsModalOpen(false)}
>
{t('common.close')}
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</SettingCard>
)
}
export default WebUIConfig

View File

@ -296,4 +296,4 @@ const loadImage = (url: string): Promise<string> => {
})
}
const trayIconBase64 = `data:image/png;base64,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`
const trayIconBase64 = `data:image/png;base64,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`

View File

@ -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",
@ -388,6 +403,7 @@
"dns.customHosts.valuePlaceholder": "Domain or IP",
"dns.saveOnly": "Save Only",
"profiles.title": "Profile Management",
"profiles.input.placeholder": "Enter your subscription URL",
"profiles.updateAll": "Update All Profiles",
"profiles.useProxy": "Proxy",
"profiles.import": "Import",
@ -404,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",

View File

@ -377,6 +377,7 @@
"dns.customHosts.valuePlaceholder": "دامنه یا IP",
"dns.saveOnly": "فقط ذخیره",
"profiles.title": "مدیریت پروفایل",
"profiles.input.placeholder": "آدرس اشتراک خود را وارد کنید",
"profiles.updateAll": "به‌روزرسانی همه پروفایل‌ها",
"profiles.useProxy": "پراکسی",
"profiles.import": "وارد کردن",

View File

@ -377,6 +377,7 @@
"dns.customHosts.valuePlaceholder": "Домен или IP",
"dns.saveOnly": "Только сохранить",
"profiles.title": "Управление профилями",
"profiles.input.placeholder": "Введите URL вашей подписки",
"profiles.updateAll": "Обновить все профили",
"profiles.useProxy": "Прокси",
"profiles.import": "Импорт",

View File

@ -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": "内存使用",
@ -388,6 +403,7 @@
"dns.fallbackFilter.domainPlaceholder": "例:+.google.com",
"dns.saveOnly": "仅保存",
"profiles.title": "订阅管理",
"profiles.input.placeholder": "请输入您的订阅网址",
"profiles.updateAll": "更新全部订阅",
"profiles.useProxy": "代理",
"profiles.import": "导入",
@ -409,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": "订阅地址",

View File

@ -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 (
<BasePage
ref={pageRef}
@ -256,6 +228,7 @@ const Profiles: React.FC = () => {
<div className="flex p-2">
<Input
size="sm"
placeholder={t('profiles.input.placeholder')}
value={url}
onValueChange={setUrl}
onKeyUp={handleInputKeyUp}
@ -406,53 +379,6 @@ 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

@ -10,6 +10,7 @@ import ShortcutConfig from '@renderer/components/settings/shortcut-config'
import { FaTelegramPlane } from 'react-icons/fa'
import SiderConfig from '@renderer/components/settings/sider-config'
import SubStoreConfig from '@renderer/components/settings/substore-config'
import WebUIConfig from '@renderer/components/settings/webui-config'
import { useTranslation } from 'react-i18next'
const Settings: React.FC = () => {
@ -60,6 +61,7 @@ const Settings: React.FC = () => {
}
>
<GeneralConfig />
<WebUIConfig />
<SubStoreConfig />
<SiderConfig />
<WebdavConfig />