mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2025-12-27 05:00:30 +08:00
Compare commits
3 Commits
6542be8490
...
041a81cfd4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
041a81cfd4 | ||
|
|
d811f76bb4 | ||
|
|
393a32bcfe |
@ -1,6 +1,7 @@
|
|||||||
import { getAppConfig } from '../config'
|
import { getAppConfig } from '../config'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import AdmZip from 'adm-zip'
|
import AdmZip from 'adm-zip'
|
||||||
|
import https from 'https'
|
||||||
import {
|
import {
|
||||||
appConfigPath,
|
appConfigPath,
|
||||||
controledMihomoConfigPath,
|
controledMihomoConfigPath,
|
||||||
@ -34,13 +35,22 @@ async function getWebDAVClient(): Promise<WebDAVContext> {
|
|||||||
webdavUsername = '',
|
webdavUsername = '',
|
||||||
webdavPassword = '',
|
webdavPassword = '',
|
||||||
webdavDir = 'clash-party',
|
webdavDir = 'clash-party',
|
||||||
webdavMaxBackups = 0
|
webdavMaxBackups = 0,
|
||||||
|
webdavIgnoreCert = false
|
||||||
} = await getAppConfig()
|
} = await getAppConfig()
|
||||||
|
|
||||||
const client = createClient(webdavUrl, {
|
const clientOptions: Parameters<typeof createClient>[1] = {
|
||||||
username: webdavUsername,
|
username: webdavUsername,
|
||||||
password: webdavPassword
|
password: webdavPassword
|
||||||
|
}
|
||||||
|
|
||||||
|
if (webdavIgnoreCert) {
|
||||||
|
clientOptions.httpsAgent = new https.Agent({
|
||||||
|
rejectUnauthorized: false
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = createClient(webdavUrl, clientOptions)
|
||||||
|
|
||||||
return { client, webdavDir, webdavMaxBackups }
|
return { client, webdavDir, webdavMaxBackups }
|
||||||
}
|
}
|
||||||
@ -49,15 +59,33 @@ export async function webdavBackup(): Promise<boolean> {
|
|||||||
const { client, webdavDir, webdavMaxBackups } = await getWebDAVClient()
|
const { client, webdavDir, webdavMaxBackups } = await getWebDAVClient()
|
||||||
const zip = new AdmZip()
|
const zip = new AdmZip()
|
||||||
|
|
||||||
|
if (existsSync(appConfigPath())) {
|
||||||
zip.addLocalFile(appConfigPath())
|
zip.addLocalFile(appConfigPath())
|
||||||
|
}
|
||||||
|
if (existsSync(controledMihomoConfigPath())) {
|
||||||
zip.addLocalFile(controledMihomoConfigPath())
|
zip.addLocalFile(controledMihomoConfigPath())
|
||||||
|
}
|
||||||
|
if (existsSync(profileConfigPath())) {
|
||||||
zip.addLocalFile(profileConfigPath())
|
zip.addLocalFile(profileConfigPath())
|
||||||
|
}
|
||||||
|
if (existsSync(overrideConfigPath())) {
|
||||||
zip.addLocalFile(overrideConfigPath())
|
zip.addLocalFile(overrideConfigPath())
|
||||||
|
}
|
||||||
|
if (existsSync(themesDir())) {
|
||||||
zip.addLocalFolder(themesDir(), 'themes')
|
zip.addLocalFolder(themesDir(), 'themes')
|
||||||
|
}
|
||||||
|
if (existsSync(profilesDir())) {
|
||||||
zip.addLocalFolder(profilesDir(), 'profiles')
|
zip.addLocalFolder(profilesDir(), 'profiles')
|
||||||
|
}
|
||||||
|
if (existsSync(overrideDir())) {
|
||||||
zip.addLocalFolder(overrideDir(), 'override')
|
zip.addLocalFolder(overrideDir(), 'override')
|
||||||
|
}
|
||||||
|
if (existsSync(rulesDir())) {
|
||||||
zip.addLocalFolder(rulesDir(), 'rules')
|
zip.addLocalFolder(rulesDir(), 'rules')
|
||||||
|
}
|
||||||
|
if (existsSync(subStoreDir())) {
|
||||||
zip.addLocalFolder(subStoreDir(), 'substore')
|
zip.addLocalFolder(subStoreDir(), 'substore')
|
||||||
|
}
|
||||||
const date = new Date()
|
const date = new Date()
|
||||||
const zipFileName = `${process.platform}_${dayjs(date).format('YYYY-MM-DD_HH-mm-ss')}.zip`
|
const zipFileName = `${process.platform}_${dayjs(date).format('YYYY-MM-DD_HH-mm-ss')}.zip`
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import React, { useState } from 'react'
|
|||||||
import SettingCard from '../base/base-setting-card'
|
import SettingCard from '../base/base-setting-card'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
import { toast } from '@renderer/components/base/toast'
|
||||||
import SettingItem from '../base/base-setting-item'
|
import SettingItem from '../base/base-setting-item'
|
||||||
import { Button, Input, Select, SelectItem } from '@heroui/react'
|
import { Button, Input, Select, SelectItem, Switch } from '@heroui/react'
|
||||||
import { listWebdavBackups, webdavBackup, reinitWebdavBackupScheduler } from '@renderer/utils/ipc'
|
import { listWebdavBackups, webdavBackup, reinitWebdavBackupScheduler } from '@renderer/utils/ipc'
|
||||||
import WebdavRestoreModal from './webdav-restore-modal'
|
import WebdavRestoreModal from './webdav-restore-modal'
|
||||||
import debounce from '@renderer/utils/debounce'
|
import debounce from '@renderer/utils/debounce'
|
||||||
@ -19,7 +19,8 @@ const WebdavConfig: React.FC = () => {
|
|||||||
webdavPassword,
|
webdavPassword,
|
||||||
webdavDir = 'clash-party',
|
webdavDir = 'clash-party',
|
||||||
webdavMaxBackups = 0,
|
webdavMaxBackups = 0,
|
||||||
webdavBackupCron
|
webdavBackupCron,
|
||||||
|
webdavIgnoreCert = false
|
||||||
} = appConfig || {}
|
} = appConfig || {}
|
||||||
const [backuping, setBackuping] = useState(false)
|
const [backuping, setBackuping] = useState(false)
|
||||||
const [restoring, setRestoring] = useState(false)
|
const [restoring, setRestoring] = useState(false)
|
||||||
@ -32,7 +33,8 @@ const WebdavConfig: React.FC = () => {
|
|||||||
webdavPassword,
|
webdavPassword,
|
||||||
webdavDir,
|
webdavDir,
|
||||||
webdavMaxBackups,
|
webdavMaxBackups,
|
||||||
webdavBackupCron
|
webdavBackupCron,
|
||||||
|
webdavIgnoreCert
|
||||||
})
|
})
|
||||||
const setWebdavDebounce = debounce(
|
const setWebdavDebounce = debounce(
|
||||||
({
|
({
|
||||||
@ -153,6 +155,16 @@ const WebdavConfig: React.FC = () => {
|
|||||||
<SelectItem key="20">20</SelectItem>
|
<SelectItem key="20">20</SelectItem>
|
||||||
</Select>
|
</Select>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
<SettingItem title={t('webdav.ignoreCert')} divider>
|
||||||
|
<Switch
|
||||||
|
size="sm"
|
||||||
|
isSelected={webdav.webdavIgnoreCert}
|
||||||
|
onValueChange={(v) => {
|
||||||
|
setWebdav({ ...webdav, webdavIgnoreCert: v })
|
||||||
|
patchAppConfig({ webdavIgnoreCert: v })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SettingItem>
|
||||||
<SettingItem title={t('webdav.backup.cron.title')} divider>
|
<SettingItem title={t('webdav.backup.cron.title')} divider>
|
||||||
<div className="flex w-[60%] gap-2">
|
<div className="flex w-[60%] gap-2">
|
||||||
{webdavBackupCron !== webdav.webdavBackupCron && (
|
{webdavBackupCron !== webdav.webdavBackupCron && (
|
||||||
@ -188,7 +200,7 @@ const WebdavConfig: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
<div className="flex justify0between">
|
<div className="flex justify-between">
|
||||||
<Button isLoading={backuping} fullWidth size="sm" className="mr-1" onPress={handleBackup}>
|
<Button isLoading={backuping} fullWidth size="sm" className="mr-1" onPress={handleBackup}>
|
||||||
{t('webdav.backup')}
|
{t('webdav.backup')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -25,11 +25,12 @@ export const AppConfigProvider: React.FC<{ children: ReactNode }> = ({ children
|
|||||||
}
|
}
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
window.electron.ipcRenderer.on('appConfigUpdated', () => {
|
const handler = (): void => {
|
||||||
mutateAppConfig()
|
mutateAppConfig()
|
||||||
})
|
}
|
||||||
|
window.electron.ipcRenderer.on('appConfigUpdated', handler)
|
||||||
return (): void => {
|
return (): void => {
|
||||||
window.electron.ipcRenderer.removeAllListeners('appConfigUpdated')
|
window.electron.ipcRenderer.removeListener('appConfigUpdated', handler)
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|||||||
@ -30,11 +30,12 @@ export const ControledMihomoConfigProvider: React.FC<{ children: ReactNode }> =
|
|||||||
}
|
}
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
window.electron.ipcRenderer.on('controledMihomoConfigUpdated', () => {
|
const handler = (): void => {
|
||||||
mutateControledMihomoConfig()
|
mutateControledMihomoConfig()
|
||||||
})
|
}
|
||||||
|
window.electron.ipcRenderer.on('controledMihomoConfigUpdated', handler)
|
||||||
return (): void => {
|
return (): void => {
|
||||||
window.electron.ipcRenderer.removeAllListeners('controledMihomoConfigUpdated')
|
window.electron.ipcRenderer.removeListener('controledMihomoConfigUpdated', handler)
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|||||||
@ -20,11 +20,12 @@ export const GroupsProvider: React.FC<{ children: ReactNode }> = ({ children })
|
|||||||
})
|
})
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
window.electron.ipcRenderer.on('groupsUpdated', () => {
|
const handler = (): void => {
|
||||||
mutate()
|
mutate()
|
||||||
})
|
}
|
||||||
|
window.electron.ipcRenderer.on('groupsUpdated', handler)
|
||||||
return (): void => {
|
return (): void => {
|
||||||
window.electron.ipcRenderer.removeAllListeners('groupsUpdated')
|
window.electron.ipcRenderer.removeListener('groupsUpdated', handler)
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|||||||
@ -119,11 +119,12 @@ export const ProfileConfigProvider: React.FC<{ children: ReactNode }> = ({ child
|
|||||||
}
|
}
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
window.electron.ipcRenderer.on('profileConfigUpdated', () => {
|
const handler = (): void => {
|
||||||
mutateProfileConfig()
|
mutateProfileConfig()
|
||||||
})
|
}
|
||||||
|
window.electron.ipcRenderer.on('profileConfigUpdated', handler)
|
||||||
return (): void => {
|
return (): void => {
|
||||||
window.electron.ipcRenderer.removeAllListeners('profileConfigUpdated')
|
window.electron.ipcRenderer.removeListener('profileConfigUpdated', handler)
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|||||||
@ -16,11 +16,12 @@ export const RulesProvider: React.FC<{ children: ReactNode }> = ({ children }) =
|
|||||||
})
|
})
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
window.electron.ipcRenderer.on('rulesUpdated', () => {
|
const handler = (): void => {
|
||||||
mutate()
|
mutate()
|
||||||
})
|
}
|
||||||
|
window.electron.ipcRenderer.on('rulesUpdated', handler)
|
||||||
return (): void => {
|
return (): void => {
|
||||||
window.electron.ipcRenderer.removeAllListeners('rulesUpdated')
|
window.electron.ipcRenderer.removeListener('rulesUpdated', handler)
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|||||||
@ -236,6 +236,7 @@
|
|||||||
"webdav.username": "WebDAV Username",
|
"webdav.username": "WebDAV Username",
|
||||||
"webdav.password": "WebDAV Password",
|
"webdav.password": "WebDAV Password",
|
||||||
"webdav.maxBackups": "Max Backups",
|
"webdav.maxBackups": "Max Backups",
|
||||||
|
"webdav.ignoreCert": "Ignore Certificate",
|
||||||
"webdav.noLimit": "No Limit",
|
"webdav.noLimit": "No Limit",
|
||||||
"webdav.backup": "Backup",
|
"webdav.backup": "Backup",
|
||||||
"webdav.backup.cron.title": "Schedule Config Backup",
|
"webdav.backup.cron.title": "Schedule Config Backup",
|
||||||
|
|||||||
@ -211,6 +211,7 @@
|
|||||||
"webdav.username": "نام کاربری WebDAV",
|
"webdav.username": "نام کاربری WebDAV",
|
||||||
"webdav.password": "رمز عبور WebDAV",
|
"webdav.password": "رمز عبور WebDAV",
|
||||||
"webdav.maxBackups": "حداکثر نسخه پشتیبان",
|
"webdav.maxBackups": "حداکثر نسخه پشتیبان",
|
||||||
|
"webdav.ignoreCert": "نادیده گرفتن گواهی",
|
||||||
"webdav.noLimit": "بدون محدودیت",
|
"webdav.noLimit": "بدون محدودیت",
|
||||||
"webdav.backup": "پشتیبانگیری",
|
"webdav.backup": "پشتیبانگیری",
|
||||||
"webdav.backup.cron.title": "زمانبندی پشتیبانگیری پیکربندی",
|
"webdav.backup.cron.title": "زمانبندی پشتیبانگیری پیکربندی",
|
||||||
|
|||||||
@ -211,6 +211,7 @@
|
|||||||
"webdav.username": "Имя пользователя WebDAV",
|
"webdav.username": "Имя пользователя WebDAV",
|
||||||
"webdav.password": "Пароль WebDAV",
|
"webdav.password": "Пароль WebDAV",
|
||||||
"webdav.maxBackups": "Максимум резервных копий",
|
"webdav.maxBackups": "Максимум резервных копий",
|
||||||
|
"webdav.ignoreCert": "Игнорировать сертификат",
|
||||||
"webdav.noLimit": "Без ограничений",
|
"webdav.noLimit": "Без ограничений",
|
||||||
"webdav.backup": "Резервное копирование",
|
"webdav.backup": "Резервное копирование",
|
||||||
"webdav.backup.cron.title": "Расписание резервного копирования",
|
"webdav.backup.cron.title": "Расписание резервного копирования",
|
||||||
|
|||||||
@ -236,6 +236,7 @@
|
|||||||
"webdav.username": "WebDAV 用户名",
|
"webdav.username": "WebDAV 用户名",
|
||||||
"webdav.password": "WebDAV 密码",
|
"webdav.password": "WebDAV 密码",
|
||||||
"webdav.maxBackups": "最大备份数",
|
"webdav.maxBackups": "最大备份数",
|
||||||
|
"webdav.ignoreCert": "忽略证书验证",
|
||||||
"webdav.noLimit": "不限制",
|
"webdav.noLimit": "不限制",
|
||||||
"webdav.backup": "备份",
|
"webdav.backup": "备份",
|
||||||
"webdav.backup.cron.title": "定时备份配置",
|
"webdav.backup.cron.title": "定时备份配置",
|
||||||
|
|||||||
@ -236,6 +236,7 @@
|
|||||||
"webdav.username": "WebDAV 用戶名",
|
"webdav.username": "WebDAV 用戶名",
|
||||||
"webdav.password": "WebDAV 密碼",
|
"webdav.password": "WebDAV 密碼",
|
||||||
"webdav.maxBackups": "最大備份數",
|
"webdav.maxBackups": "最大備份數",
|
||||||
|
"webdav.ignoreCert": "忽略證書驗證",
|
||||||
"webdav.noLimit": "不限制",
|
"webdav.noLimit": "不限制",
|
||||||
"webdav.backup": "備份",
|
"webdav.backup": "備份",
|
||||||
"webdav.backup.cron.title": "定時備份配置",
|
"webdav.backup.cron.title": "定時備份配置",
|
||||||
|
|||||||
@ -632,8 +632,10 @@ const Mihomo: React.FC = () => {
|
|||||||
type="number"
|
type="number"
|
||||||
value={smartCollectorSize.toString()}
|
value={smartCollectorSize.toString()}
|
||||||
onValueChange={async (v: string) => {
|
onValueChange={async (v: string) => {
|
||||||
let num = parseInt(v)
|
const num = parseInt(v)
|
||||||
|
if (!isNaN(num)) {
|
||||||
await patchAppConfig({ smartCollectorSize: num })
|
await patchAppConfig({ smartCollectorSize: num })
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
onBlur={async (e) => {
|
onBlur={async (e) => {
|
||||||
let num = parseInt(e.target.value)
|
let num = parseInt(e.target.value)
|
||||||
@ -706,9 +708,11 @@ const Mihomo: React.FC = () => {
|
|||||||
min={0}
|
min={0}
|
||||||
onValueChange={(v) => {
|
onValueChange={(v) => {
|
||||||
const port = parseInt(v)
|
const port = parseInt(v)
|
||||||
|
if (!isNaN(port)) {
|
||||||
setMixedPortInput(port)
|
setMixedPortInput(port)
|
||||||
patchAppConfig({ showMixedPort: port })
|
patchAppConfig({ showMixedPort: port })
|
||||||
setIsManualPortChange(true)
|
setIsManualPortChange(true)
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
@ -765,9 +769,11 @@ const Mihomo: React.FC = () => {
|
|||||||
min={0}
|
min={0}
|
||||||
onValueChange={(v) => {
|
onValueChange={(v) => {
|
||||||
const port = parseInt(v)
|
const port = parseInt(v)
|
||||||
|
if (!isNaN(port)) {
|
||||||
setSocksPortInput(port)
|
setSocksPortInput(port)
|
||||||
patchAppConfig({ showSocksPort: port })
|
patchAppConfig({ showSocksPort: port })
|
||||||
setIsManualPortChange(true)
|
setIsManualPortChange(true)
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
@ -824,9 +830,11 @@ const Mihomo: React.FC = () => {
|
|||||||
min={0}
|
min={0}
|
||||||
onValueChange={(v) => {
|
onValueChange={(v) => {
|
||||||
const port = parseInt(v)
|
const port = parseInt(v)
|
||||||
|
if (!isNaN(port)) {
|
||||||
setHttpPortInput(port)
|
setHttpPortInput(port)
|
||||||
patchAppConfig({ showHttpPort: port })
|
patchAppConfig({ showHttpPort: port })
|
||||||
setIsManualPortChange(true)
|
setIsManualPortChange(true)
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
@ -884,9 +892,11 @@ const Mihomo: React.FC = () => {
|
|||||||
min={0}
|
min={0}
|
||||||
onValueChange={(v) => {
|
onValueChange={(v) => {
|
||||||
const port = parseInt(v)
|
const port = parseInt(v)
|
||||||
|
if (!isNaN(port)) {
|
||||||
setRedirPortInput(port)
|
setRedirPortInput(port)
|
||||||
patchAppConfig({ showRedirPort: port })
|
patchAppConfig({ showRedirPort: port })
|
||||||
setIsManualPortChange(true)
|
setIsManualPortChange(true)
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
@ -945,9 +955,11 @@ const Mihomo: React.FC = () => {
|
|||||||
min={0}
|
min={0}
|
||||||
onValueChange={(v) => {
|
onValueChange={(v) => {
|
||||||
const port = parseInt(v)
|
const port = parseInt(v)
|
||||||
|
if (!isNaN(port)) {
|
||||||
setTproxyPortInput(port)
|
setTproxyPortInput(port)
|
||||||
patchAppConfig({ showTproxyPort: port })
|
patchAppConfig({ showTproxyPort: port })
|
||||||
setIsManualPortChange(true)
|
setIsManualPortChange(true)
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
@ -1082,7 +1094,7 @@ const Mihomo: React.FC = () => {
|
|||||||
{allowLan && (
|
{allowLan && (
|
||||||
<>
|
<>
|
||||||
<SettingItem title={t('mihomo.allowedIpSegments')}>
|
<SettingItem title={t('mihomo.allowedIpSegments')}>
|
||||||
{lanAllowedIpsInput.join('') !== lanAllowedIps.join('') && (
|
{JSON.stringify(lanAllowedIpsInput) !== JSON.stringify(lanAllowedIps) && (
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
color="primary"
|
color="primary"
|
||||||
@ -1132,7 +1144,7 @@ const Mihomo: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
<Divider className="mb-2" />
|
<Divider className="mb-2" />
|
||||||
<SettingItem title={t('mihomo.disallowedIpSegments')}>
|
<SettingItem title={t('mihomo.disallowedIpSegments')}>
|
||||||
{lanDisallowedIpsInput.join('') !== lanDisallowedIps.join('') && (
|
{JSON.stringify(lanDisallowedIpsInput) !== JSON.stringify(lanDisallowedIps) && (
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
color="primary"
|
color="primary"
|
||||||
@ -1186,7 +1198,7 @@ const Mihomo: React.FC = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<SettingItem title={t('mihomo.userVerification')}>
|
<SettingItem title={t('mihomo.userVerification')}>
|
||||||
{authenticationInput.join('') !== authentication.join('') && (
|
{JSON.stringify(authenticationInput) !== JSON.stringify(authentication) && (
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
color="primary"
|
color="primary"
|
||||||
@ -1261,7 +1273,7 @@ const Mihomo: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
<Divider className="mb-2" />
|
<Divider className="mb-2" />
|
||||||
<SettingItem title={t('mihomo.skipAuthPrefixes')}>
|
<SettingItem title={t('mihomo.skipAuthPrefixes')}>
|
||||||
{skipAuthPrefixesInput.join('') !== skipAuthPrefixes.join('') && (
|
{JSON.stringify(skipAuthPrefixesInput) !== JSON.stringify(skipAuthPrefixes) && (
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
color="primary"
|
color="primary"
|
||||||
@ -1357,7 +1369,10 @@ const Mihomo: React.FC = () => {
|
|||||||
className="w-[100px]"
|
className="w-[100px]"
|
||||||
value={maxLogDays.toString()}
|
value={maxLogDays.toString()}
|
||||||
onValueChange={(v) => {
|
onValueChange={(v) => {
|
||||||
patchAppConfig({ maxLogDays: parseInt(v) })
|
const num = parseInt(v)
|
||||||
|
if (!isNaN(num)) {
|
||||||
|
patchAppConfig({ maxLogDays: num })
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|||||||
@ -149,8 +149,8 @@ const Profiles: React.FC = () => {
|
|||||||
const newOrder = sortedItems.slice()
|
const newOrder = sortedItems.slice()
|
||||||
const activeIndex = newOrder.findIndex((item) => item.id === active.id)
|
const activeIndex = newOrder.findIndex((item) => item.id === active.id)
|
||||||
const overIndex = newOrder.findIndex((item) => item.id === over.id)
|
const overIndex = newOrder.findIndex((item) => item.id === over.id)
|
||||||
newOrder.splice(activeIndex, 1)
|
const [movedItem] = newOrder.splice(activeIndex, 1)
|
||||||
newOrder.splice(overIndex, 0, items[activeIndex])
|
newOrder.splice(overIndex, 0, movedItem)
|
||||||
setSortedItems(newOrder)
|
setSortedItems(newOrder)
|
||||||
await setProfileConfig({ current, items: newOrder })
|
await setProfileConfig({ current, items: newOrder })
|
||||||
}
|
}
|
||||||
|
|||||||
@ -63,20 +63,39 @@ const useProxyState = (
|
|||||||
const [isOpen, setIsOpen] = useState<boolean[]>(() => {
|
const [isOpen, setIsOpen] = useState<boolean[]>(() => {
|
||||||
try {
|
try {
|
||||||
const savedState = localStorage.getItem(GROUP_EXPAND_STATE_KEY)
|
const savedState = localStorage.getItem(GROUP_EXPAND_STATE_KEY)
|
||||||
return savedState ? JSON.parse(savedState) : Array(groups.length).fill(false)
|
if (savedState) {
|
||||||
|
const parsed = JSON.parse(savedState)
|
||||||
|
if (Array.isArray(parsed)) {
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load group expand state:', error)
|
console.error('Failed to load group expand state:', error)
|
||||||
return Array(groups.length).fill(false)
|
|
||||||
}
|
}
|
||||||
|
return []
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 同步展开状态数组长度与 groups 长度
|
||||||
|
useEffect(() => {
|
||||||
|
if (groups.length !== isOpen.length) {
|
||||||
|
setIsOpen((prev) => {
|
||||||
|
if (groups.length > prev.length) {
|
||||||
|
return [...prev, ...Array(groups.length - prev.length).fill(false)]
|
||||||
|
}
|
||||||
|
return prev.slice(0, groups.length)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [groups.length])
|
||||||
|
|
||||||
// 保存展开状态
|
// 保存展开状态
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (isOpen.length > 0) {
|
||||||
try {
|
try {
|
||||||
localStorage.setItem(GROUP_EXPAND_STATE_KEY, JSON.stringify(isOpen))
|
localStorage.setItem(GROUP_EXPAND_STATE_KEY, JSON.stringify(isOpen))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to save group expand state:', error)
|
console.error('Failed to save group expand state:', error)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}, [isOpen])
|
}, [isOpen])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -223,7 +223,8 @@ const Tun: React.FC = () => {
|
|||||||
className="w-[100px]"
|
className="w-[100px]"
|
||||||
value={values.mtu.toString()}
|
value={values.mtu.toString()}
|
||||||
onValueChange={(v) => {
|
onValueChange={(v) => {
|
||||||
setValues({ ...values, mtu: parseInt(v) })
|
const num = parseInt(v)
|
||||||
|
setValues({ ...values, mtu: isNaN(num) ? 1500 : num })
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|||||||
1
src/shared/types.d.ts
vendored
1
src/shared/types.d.ts
vendored
@ -302,6 +302,7 @@ interface IAppConfig {
|
|||||||
webdavPassword?: string
|
webdavPassword?: string
|
||||||
webdavMaxBackups?: number
|
webdavMaxBackups?: number
|
||||||
webdavBackupCron?: string
|
webdavBackupCron?: string
|
||||||
|
webdavIgnoreCert?: boolean
|
||||||
useNameserverPolicy: boolean
|
useNameserverPolicy: boolean
|
||||||
nameserverPolicy: { [key: string]: string | string[] }
|
nameserverPolicy: { [key: string]: string | string[] }
|
||||||
showWindowShortcut?: string
|
showWindowShortcut?: string
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user