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