refactor(settings): route settings-domain commands through the mutate funnel

Ports port saving, webdav/local backup operations, core change/restart/
upgrade, service install/uninstall/repair flows, the system-proxy toggle
queue, the DNS override toggle, and app lifecycle commands onto the single
funnel. Bespoke flows keep their own error handling via errorNotice:false;
simple flows gain the funnel's default error toast and revalidation.
This commit is contained in:
Tunglies
2026-10-01 04:20:26 +08:00
parent 24a3a18758
commit 4e62db44f8
15 changed files with 232 additions and 80 deletions
@@ -12,6 +12,7 @@ import {
restartCore, restartCore,
type ServiceInstallOutcome, type ServiceInstallOutcome,
} from '@/services/cmds' } from '@/services/cmds'
import { mutate } from '@/services/mutate'
import { showNotice } from '@/services/notice-service' import { showNotice } from '@/services/notice-service'
import { useAppDispatch, useRunState } from '@/store/app-store-context' import { useAppDispatch, useRunState } from '@/store/app-store-context'
@@ -74,11 +75,23 @@ export const ServiceMigrationDialog = () => {
let outcome: ServiceInstallOutcome | undefined let outcome: ServiceInstallOutcome | undefined
try { try {
if (remedy === 'install') { if (remedy === 'install') {
outcome = await installService() const result = await mutate(() => installService(), {
id: 'install-service',
errorNotice: false,
})
if (result.ok) outcome = result.value
} else if (remedy === 'repair') { } else if (remedy === 'repair') {
outcome = await repairService() const result = await mutate(() => repairService(), {
id: 'repair-service',
errorNotice: false,
})
if (result.ok) outcome = result.value
} else { } else {
outcome = await reinstallService() const result = await mutate(() => reinstallService(), {
id: 'reinstall-service',
errorNotice: false,
})
if (result.ok) outcome = result.value
} }
} catch (error) { } catch (error) {
showNotice.error( showNotice.error(
@@ -114,8 +127,11 @@ export const ServiceMigrationDialog = () => {
let restartSucceeded = false let restartSucceeded = false
try { try {
await restartCore() const result = await mutate(() => restartCore(), {
restartSucceeded = true id: 'restart-core',
errorNotice: false,
})
restartSucceeded = result.ok
} catch (error) { } catch (error) {
showNotice.error( showNotice.error(
'layout.components.serviceMigration.errors.restartFailed', 'layout.components.serviceMigration.errors.restartFailed',
@@ -145,7 +161,10 @@ export const ServiceMigrationDialog = () => {
setWorkflowIncomplete(true) setWorkflowIncomplete(true)
let startupError: unknown let startupError: unknown
try { try {
await continueWithSidecar() await mutate(() => continueWithSidecar(), {
id: 'continue-with-sidecar',
errorNotice: false,
})
} catch (error) { } catch (error) {
startupError = error startupError = error
} }
@@ -14,6 +14,7 @@ import {
type PendingFailure, type PendingFailure,
type ServiceInstallOutcome, type ServiceInstallOutcome,
} from '@/services/cmds' } from '@/services/cmds'
import { mutate } from '@/services/mutate'
import { showNotice } from '@/services/notice-service' import { showNotice } from '@/services/notice-service'
import { import {
clearServiceRequest, clearServiceRequest,
@@ -103,10 +104,18 @@ export const SysproxyPrivilegeDialog = () => {
let outcome: ServiceInstallOutcome | undefined let outcome: ServiceInstallOutcome | undefined
if (remedy === 'reinstallAndRestart') { if (remedy === 'reinstallAndRestart') {
setStep('installing') setStep('installing')
outcome = await reinstallService() const result = await mutate(() => reinstallService(), {
id: 'reinstall-service',
errorNotice: false,
})
if (result.ok) outcome = result.value
} else if (remedy === 'installAndRestart') { } else if (remedy === 'installAndRestart') {
setStep('installing') setStep('installing')
outcome = await installService() const result = await mutate(() => installService(), {
id: 'install-service',
errorNotice: false,
})
if (result.ok) outcome = result.value
} }
if (outcome?.status === 'sidecar') { if (outcome?.status === 'sidecar') {
showNotice.warning( showNotice.warning(
@@ -118,7 +127,10 @@ export const SysproxyPrivilegeDialog = () => {
return return
} }
setStep('restarting') setStep('restarting')
await restartCore() await mutate(() => restartCore(), {
id: 'restart-core',
errorNotice: false,
})
const runState = await getRuntimeState() const runState = await getRuntimeState()
const usingAdminFallback = const usingAdminFallback =
@@ -131,7 +143,10 @@ export const SysproxyPrivilegeDialog = () => {
if (runState.mode === 'Service' || usingAdminFallback) { if (runState.mode === 'Service' || usingAdminFallback) {
if (restoring !== undefined) { if (restoring !== undefined) {
setStep('applying') setStep('applying')
await patchVergeConfig(restoring) await mutate(() => patchVergeConfig(restoring), {
id: 'sysproxy-privilege-restore',
errorNotice: false,
})
} }
if (!usingAdminFallback) { if (!usingAdminFallback) {
showNotice.success( showNotice.success(
+4 -4
View File
@@ -7,7 +7,7 @@ import { useNavigate } from 'react-router'
import { useSystemState } from '@/hooks/use-system-state' import { useSystemState } from '@/hooks/use-system-state'
import { useAppRefreshers } from '@/providers/app-data-context' import { useAppRefreshers } from '@/providers/app-data-context'
import { openLogsDir, restartCore } from '@/services/cmds' import { openLogsDir, restartCore } from '@/services/cmds'
import { showNotice } from '@/services/notice-service' import { mutate } from '@/services/mutate'
import type { ProxyEmptyStateReason } from './proxy-empty-state-model' import type { ProxyEmptyStateReason } from './proxy-empty-state-model'
@@ -25,10 +25,10 @@ export const ProxyEmptyState = ({ reason }: Props) => {
const handleRestart = useLockFn(async () => { const handleRestart = useLockFn(async () => {
setIsRestarting(true) setIsRestarting(true)
try { try {
await restartCore() await mutate(() => restartCore(), { id: 'restart-core' })
await Promise.all([refreshProxy(), mutateSystemState()]) await Promise.all([refreshProxy(), mutateSystemState()])
} catch (error) { } catch {
showNotice.error(error) // the funnel already notified
} finally { } finally {
setIsRestarting(false) setIsRestarting(false)
} }
@@ -15,6 +15,7 @@ import { useTranslation } from 'react-i18next'
import { useVerge } from '@/hooks/use-verge' import { useVerge } from '@/hooks/use-verge'
import { saveWebdavConfig, createWebdavBackup } from '@/services/cmds' import { saveWebdavConfig, createWebdavBackup } from '@/services/cmds'
import { mutate } from '@/services/mutate'
import { errorDetail, showNotice } from '@/services/notice-service' import { errorDetail, showNotice } from '@/services/notice-service'
import { import {
buildWebdavSignature, buildWebdavSignature,
@@ -119,7 +120,13 @@ export const BackupConfigViewer = memo(
try { try {
setLoading(true) setLoading(true)
await saveWebdavConfig(trimmedUrl, trimmedUsername, data.password) await mutate(
() => saveWebdavConfig(trimmedUrl, trimmedUsername, data.password),
{
id: 'save-webdav-config',
errorNotice: false,
},
)
await mutateVerge( await mutateVerge(
(current) => (current) =>
current current
@@ -156,7 +163,10 @@ export const BackupConfigViewer = memo(
try { try {
setLoading(true) setLoading(true)
await createWebdavBackup().then(async () => { await mutate(() => createWebdavBackup(), {
id: 'create-webdav-backup',
errorNotice: false,
}).then(async () => {
showNotice.success('settings.modals.backup.messages.backupCreated') showNotice.success('settings.modals.backup.messages.backupCreated')
await onBackupSuccess() await onBackupSuccess()
}) })
@@ -35,6 +35,7 @@ import {
restoreLocalBackup, restoreLocalBackup,
restoreWebDavBackup, restoreWebDavBackup,
} from '@/services/cmds' } from '@/services/cmds'
import { mutate } from '@/services/mutate'
import { showNotice } from '@/services/notice-service' import { showNotice } from '@/services/notice-service'
import { import {
buildWebdavSignature, buildWebdavSignature,
@@ -228,17 +229,29 @@ export const BackupHistoryViewer = ({
try { try {
if (action === 'delete') { if (action === 'delete') {
if (actionIsLocal) { if (actionIsLocal) {
await deleteLocalBackup(filename) await mutate(() => deleteLocalBackup(filename), {
id: 'delete-local-backup',
errorNotice: false,
})
} else { } else {
await deleteWebdavBackup(filename) await mutate(() => deleteWebdavBackup(filename), {
id: 'delete-webdav-backup',
errorNotice: false,
})
} }
setPendingConfirmation(null) setPendingConfirmation(null)
await fetchRows() await fetchRows()
} else { } else {
if (actionIsLocal) { if (actionIsLocal) {
await restoreLocalBackup(filename) await mutate(() => restoreLocalBackup(filename), {
id: 'restore-local-backup',
errorNotice: false,
})
} else { } else {
await restoreWebDavBackup(filename) await mutate(() => restoreWebDavBackup(filename), {
id: 'restore-webdav-backup',
errorNotice: false,
})
} }
setPendingConfirmation(null) setPendingConfirmation(null)
showNotice.success('settings.modals.backup.messages.restoreSuccess') showNotice.success('settings.modals.backup.messages.restoreSuccess')
@@ -265,8 +278,11 @@ export const BackupHistoryViewer = ({
const savePath = await save({ defaultPath: filename }) const savePath = await save({ defaultPath: filename })
if (!savePath || Array.isArray(savePath)) return if (!savePath || Array.isArray(savePath)) return
try { try {
await exportLocalBackup(filename, savePath) await mutate(() => exportLocalBackup(filename, savePath), {
showNotice.success('settings.modals.backup.messages.localBackupExported') id: 'export-local-backup',
errorNotice: false,
successNotice: 'settings.modals.backup.messages.localBackupExported',
})
} catch (ignoreError: unknown) { } catch (ignoreError: unknown) {
showNotice.error( showNotice.error(
'settings.modals.backup.messages.localBackupExportFailed', 'settings.modals.backup.messages.localBackupExportFailed',
+17 -7
View File
@@ -19,6 +19,7 @@ import {
createWebdavBackup, createWebdavBackup,
importLocalBackup, importLocalBackup,
} from '@/services/cmds' } from '@/services/cmds'
import { mutate } from '@/services/mutate'
import { errorDetail, showNotice } from '@/services/notice-service' import { errorDetail, showNotice } from '@/services/notice-service'
import { buildWebdavSignature, setWebdavStatus } from '@/services/webdav-status' import { buildWebdavSignature, setWebdavStatus } from '@/services/webdav-status'
@@ -55,12 +56,18 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
try { try {
setBusyAction(target) setBusyAction(target)
if (target === 'local') { if (target === 'local') {
await createLocalBackup() await mutate(() => createLocalBackup(), {
showNotice.success('settings.modals.backup.messages.localBackupCreated') id: 'create-local-backup',
errorNotice: false,
successNotice: 'settings.modals.backup.messages.localBackupCreated',
})
} else { } else {
await createWebdavBackup() await mutate(() => createWebdavBackup(), {
showNotice.success('settings.modals.backup.messages.backupCreated') id: 'create-webdav-backup',
setWebdavStatus(webdavSignature, 'ready') errorNotice: false,
successNotice: 'settings.modals.backup.messages.backupCreated',
onFulfilled: () => setWebdavStatus(webdavSignature, 'ready'),
})
} }
} catch (error) { } catch (error) {
console.error(error) console.error(error)
@@ -86,8 +93,11 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
if (!selected || Array.isArray(selected)) return if (!selected || Array.isArray(selected)) return
try { try {
setLocalImporting(true) setLocalImporting(true)
await importLocalBackup(selected) await mutate(() => importLocalBackup(selected), {
showNotice.success('settings.modals.backup.messages.localBackupImported') id: 'import-local-backup',
errorNotice: false,
successNotice: 'settings.modals.backup.messages.localBackupImported',
})
openHistory('local') openHistory('local')
} catch (error) { } catch (error) {
console.error(error) console.error(error)
@@ -21,6 +21,7 @@ import { BaseDialog, DialogRef } from '@/components/base'
import { useClash, useClashInfo } from '@/hooks/use-clash' import { useClash, useClashInfo } from '@/hooks/use-clash'
import { useVerge } from '@/hooks/use-verge' import { useVerge } from '@/hooks/use-verge'
import { changeClashCore, restartCore, upgradeClashCore } from '@/services/cmds' import { changeClashCore, restartCore, upgradeClashCore } from '@/services/cmds'
import { mutate } from '@/services/mutate'
import { showNotice } from '@/services/notice-service' import { showNotice } from '@/services/notice-service'
const VALID_CORE = [ const VALID_CORE = [
@@ -60,8 +61,16 @@ export function ClashCoreViewer({ ref }: { ref?: Ref<DialogRef> }) {
try { try {
setChangingCore(core) setChangingCore(core)
closeAllConnections() void mutate(() => closeAllConnections(), {
const errorMsg = await changeClashCore(core) id: 'close-all-connections',
errorNotice: false,
})
const result = await mutate(() => changeClashCore(core), {
id: 'change-clash-core',
errorNotice: false,
})
if (!result.ok) return
const errorMsg = result.value
if (errorMsg) { if (errorMsg) {
showNotice.error(errorMsg) showNotice.error(errorMsg)
@@ -83,28 +92,33 @@ export function ClashCoreViewer({ ref }: { ref?: Ref<DialogRef> }) {
const onRestart = useLockFn(async () => { const onRestart = useLockFn(async () => {
try { try {
setRestarting(true) setRestarting(true)
await restartCore() await mutate(() => restartCore(), {
showNotice.success( id: 'restart-core',
t('settings.feedback.notifications.clash.restartSuccess'), successNotice: t(
) 'settings.feedback.notifications.clash.restartSuccess',
),
})
setRestarting(false) setRestarting(false)
} catch (err) { } catch {
setRestarting(false) setRestarting(false)
showNotice.error(err)
} }
}) })
const onUpgrade = useLockFn(async () => { const onUpgrade = useLockFn(async () => {
try { try {
setUpgrading(true) setUpgrading(true)
const report = await upgradeClashCore() await mutate(() => upgradeClashCore(), {
showNotice.success( id: 'upgrade-clash-core',
report.upgraded onFulfilled: (report) => {
? t('settings.feedback.notifications.clash.versionUpdated') showNotice.success(
: t('settings.feedback.notifications.clash.alreadyLatestVersion'), report.upgraded
) ? t('settings.feedback.notifications.clash.versionUpdated')
} catch (err) { : t('settings.feedback.notifications.clash.alreadyLatestVersion'),
showNotice.error(err) )
},
})
} catch {
// the funnel already notified
} finally { } finally {
setUpgrading(false) setUpgrading(false)
} }
@@ -15,7 +15,8 @@ import { useTranslation } from 'react-i18next'
import { BaseDialog, Switch } from '@/components/base' import { BaseDialog, Switch } from '@/components/base'
import { useDisplayedMixedPort } from '@/hooks/use-displayed-mixed-port' import { useDisplayedMixedPort } from '@/hooks/use-displayed-mixed-port'
import { useVerge } from '@/hooks/use-verge' import { useVerge } from '@/hooks/use-verge'
import { saveProxyPorts } from '@/services/cmds' import { saveProxyPorts, type ProxyPortSettings } from '@/services/cmds'
import { mutate } from '@/services/mutate'
import { showNotice } from '@/services/notice-service' import { showNotice } from '@/services/notice-service'
import getSystem from '@/utils/get-system' import getSystem from '@/utils/get-system'
@@ -57,22 +58,31 @@ export const ClashPortViewer = forwardRef<ClashPortViewerRef>((_, ref) => {
) )
// 添加保存请求,防止GUI卡死 // 添加保存请求,防止GUI卡死
const { loading, runAsync: saveSettings } = useRequest(saveProxyPorts, { const { loading, runAsync: saveSettings } = useRequest(
manual: true, async (settings: ProxyPortSettings) =>
onSuccess: (outcome) => { mutate(() => saveProxyPorts(settings), {
if (outcome.status === 'conflict') { id: 'save-proxy-ports',
showNotice.error('settings.modals.clashPort.messages.portInUse', { errorNotice: false,
port: outcome.port, }),
}) {
return manual: true,
} onSuccess: (result) => {
setOpen(false) if (!result.ok) return
showNotice.success('settings.modals.clashPort.messages.saved') const outcome = result.value
if (outcome.status === 'conflict') {
showNotice.error('settings.modals.clashPort.messages.portInUse', {
port: outcome.port,
})
return
}
setOpen(false)
showNotice.success('settings.modals.clashPort.messages.saved')
},
onError: (error) => {
showNotice.error('settings.modals.clashPort.messages.saveFailed', error)
},
}, },
onError: (error) => { )
showNotice.error('settings.modals.clashPort.messages.saveFailed', error)
},
})
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
open: () => { open: () => {
@@ -7,6 +7,7 @@ import { useTranslation } from 'react-i18next'
import { BaseDialog, Switch } from '@/components/base' import { BaseDialog, Switch } from '@/components/base'
import { useClash } from '@/hooks/use-clash' import { useClash } from '@/hooks/use-clash'
import { restartCore } from '@/services/cmds' import { restartCore } from '@/services/cmds'
import { mutate } from '@/services/mutate'
import { showNotice } from '@/services/notice-service' import { showNotice } from '@/services/notice-service'
// Development origins must never be persisted into production configuration. // Development origins must never be persisted into production configuration.
@@ -135,7 +136,10 @@ export const HeaderConfiguration = forwardRef<ClashHeaderConfigingRef>(
), ),
}, },
}) })
await restartCore() await mutate(() => restartCore(), {
id: 'restart-core',
errorNotice: false,
})
await mutateClash() await mutateClash()
}, },
{ {
@@ -16,6 +16,7 @@ import type { Options as ReactMarkdownOptions } from 'react-markdown'
import { BaseDialog, DialogRef } from '@/components/base' import { BaseDialog, DialogRef } from '@/components/base'
import { useUpdate } from '@/hooks/use-update' import { useUpdate } from '@/hooks/use-update'
import { restartApp } from '@/services/cmds' import { restartApp } from '@/services/cmds'
import { mutate } from '@/services/mutate'
import { showNotice } from '@/services/notice-service' import { showNotice } from '@/services/notice-service'
import { useSetUpdateState, useUpdateState } from '@/services/states' import { useSetUpdateState, useUpdateState } from '@/services/states'
import { openExternalUrl } from '@/utils/open-external-url' import { openExternalUrl } from '@/utils/open-external-url'
@@ -270,8 +271,14 @@ export function UpdateViewer({ ref }: { ref?: Ref<DialogRef> }) {
} }
try { try {
await updateInfo.downloadAndInstall(onDownloadEvent) await mutate(() => updateInfo.downloadAndInstall(onDownloadEvent), {
await restartApp() id: 'install-app-update',
errorNotice: false,
})
await mutate(() => restartApp(), {
id: 'restart-app',
errorNotice: false,
})
} catch (err: any) { } catch (err: any) {
showNotice.error(err) showNotice.error(err)
} finally { } finally {
+14 -2
View File
@@ -12,6 +12,7 @@ import { useDisplayedMixedPort } from '@/hooks/use-displayed-mixed-port'
import { useProfiles } from '@/hooks/use-profiles' import { useProfiles } from '@/hooks/use-profiles'
import { useVerge } from '@/hooks/use-verge' import { useVerge } from '@/hooks/use-verge'
import { invoke_uwp_tool, setDnsOverride } from '@/services/cmds' import { invoke_uwp_tool, setDnsOverride } from '@/services/cmds'
import { mutate } from '@/services/mutate'
import { showNotice } from '@/services/notice-service' import { showNotice } from '@/services/notice-service'
import getSystem from '@/utils/get-system' import getSystem from '@/utils/get-system'
@@ -74,7 +75,10 @@ const SettingClash = ({ onError }: Props) => {
} }
const onUpdateGeo = async () => { const onUpdateGeo = async () => {
try { try {
await updateGeo() await mutate(() => updateGeo(), {
id: 'update-geo',
errorNotice: false,
})
showNotice.success('settings.feedback.notifications.clash.geoDataUpdated') showNotice.success('settings.feedback.notifications.clash.geoDataUpdated')
} catch (err: any) { } catch (err: any) {
showNotice.error(err) showNotice.error(err)
@@ -90,7 +94,15 @@ const SettingClash = ({ onError }: Props) => {
if (!profileUid) return if (!profileUid) return
setDnsUpdating(true) setDnsUpdating(true)
try { try {
const outcome = await setDnsOverride(profileUid, enable, confirmation) const result = await mutate(
() => setDnsOverride(profileUid, enable, confirmation),
{
id: 'set-dns-override',
errorNotice: false,
},
)
if (!result.ok) return
const outcome = result.value
if (outcome.status === 'confirmation_required') { if (outcome.status === 'confirmation_required') {
setDnsConfirmation({ profileUid, source: outcome.source }) setDnsConfirmation({ profileUid, source: outcome.source })
} else { } else {
@@ -13,6 +13,7 @@ import {
openDevTools, openDevTools,
openLogsDir, openLogsDir,
} from '@/services/cmds' } from '@/services/cmds'
import { mutate } from '@/services/mutate'
import { showNotice } from '@/services/notice-service' import { showNotice } from '@/services/notice-service'
import { checkUpdateSafe as checkUpdate } from '@/services/update' import { checkUpdateSafe as checkUpdate } from '@/services/update'
import { version } from '@root/package.json' import { version } from '@root/package.json'
@@ -61,7 +62,10 @@ const SettingVergeAdvanced = ({ onError: _ }: Props) => {
} }
const onExportDiagnosticInfo = useCallback(async () => { const onExportDiagnosticInfo = useCallback(async () => {
await exportDiagnosticInfo() await mutate(() => exportDiagnosticInfo(), {
id: 'export-diagnostic-info',
errorNotice: false,
})
showNotice.success('shared.feedback.notifications.common.copySuccess', 1000) showNotice.success('shared.feedback.notifications.common.copySuccess', 1000)
}, []) }, [])
@@ -128,7 +132,12 @@ const SettingVergeAdvanced = ({ onError: _ }: Props) => {
/> />
<SettingItem <SettingItem
onClick={openDevTools} onClick={() => {
void mutate(() => openDevTools(), {
id: 'open-devtools',
errorNotice: false,
}).catch(() => {})
}}
label={t('settings.components.verge.advanced.fields.openDevTools')} label={t('settings.components.verge.advanced.fields.openDevTools')}
/> />
@@ -145,7 +154,10 @@ const SettingVergeAdvanced = ({ onError: _ }: Props) => {
<SettingItem <SettingItem
onClick={() => { onClick={() => {
exitApp() void mutate(() => exitApp(), {
id: 'exit-app',
errorNotice: false,
}).catch(() => {})
}} }}
label={t('settings.components.verge.advanced.fields.exit')} label={t('settings.components.verge.advanced.fields.exit')}
/> />
+15 -5
View File
@@ -1,6 +1,7 @@
import { useCallback } from 'react' import { useCallback } from 'react'
import { installService, restartCore } from '@/services/cmds' import { installService, restartCore } from '@/services/cmds'
import { mutate } from '@/services/mutate'
import { showNotice } from '@/services/notice-service' import { showNotice } from '@/services/notice-service'
const executeWithErrorHandling = async <T>( const executeWithErrorHandling = async <T>(
@@ -23,14 +24,19 @@ const executeWithErrorHandling = async <T>(
export const useServiceInstaller = () => { export const useServiceInstaller = () => {
const installServiceAndRestartCore = useCallback(async () => { const installServiceAndRestartCore = useCallback(async () => {
const outcome = await executeWithErrorHandling( const installResult = await executeWithErrorHandling(
() => installService(), () =>
mutate(() => installService(), {
id: 'install-service',
errorNotice: false,
}),
'settings.statuses.clashService.installing', 'settings.statuses.clashService.installing',
) )
if (outcome.status === 'sidecar') { if (!installResult.ok) return
if (installResult.value.status === 'sidecar') {
showNotice.warning( showNotice.warning(
'settings.feedback.notifications.clashService.permissionFallback', 'settings.feedback.notifications.clashService.permissionFallback',
{ reason: outcome.reason }, { reason: installResult.value.reason },
0, 0,
) )
return return
@@ -40,7 +46,11 @@ export const useServiceInstaller = () => {
) )
await executeWithErrorHandling( await executeWithErrorHandling(
() => restartCore(), () =>
mutate(() => restartCore(), {
id: 'restart-core',
errorNotice: false,
}),
'settings.statuses.clash.restarting', 'settings.statuses.clash.restarting',
'settings.feedback.notifications.clash.restartSuccess', 'settings.feedback.notifications.clash.restartSuccess',
) )
+10 -4
View File
@@ -1,6 +1,7 @@
import { useCallback } from 'react' import { useCallback } from 'react'
import { uninstallService } from '@/services/cmds' import { uninstallService } from '@/services/cmds'
import { mutate } from '@/services/mutate'
import { showNotice } from '@/services/notice-service' import { showNotice } from '@/services/notice-service'
import { useSystemState } from './use-system-state' import { useSystemState } from './use-system-state'
@@ -12,10 +13,15 @@ export const useServiceUninstaller = () => {
let uninstallError: unknown let uninstallError: unknown
showNotice.info('settings.statuses.clashService.uninstalling') showNotice.info('settings.statuses.clashService.uninstalling')
try { try {
await uninstallService() const result = await mutate(() => uninstallService(), {
showNotice.success( id: 'uninstall-service',
'settings.feedback.notifications.clashService.uninstallSuccess', errorNotice: false,
) })
if (result.ok) {
showNotice.success(
'settings.feedback.notifications.clashService.uninstallSuccess',
)
}
} catch (error) { } catch (error) {
uninstallError = error uninstallError = error
} }
+9 -2
View File
@@ -9,6 +9,7 @@ import {
getEmbeddedServerPort, getEmbeddedServerPort,
patchVergeConfig, patchVergeConfig,
} from '@/services/cmds' } from '@/services/cmds'
import { mutate } from '@/services/mutate'
import { import {
removeCacheData, removeCacheData,
revalidateQueries, revalidateQueries,
@@ -64,10 +65,16 @@ export const useSystemProxyState = () => {
while (pendingRef.current !== null) { while (pendingRef.current !== null) {
const target = pendingRef.current const target = pendingRef.current
pendingRef.current = null pendingRef.current = null
await patchVergeConfig({ enable_system_proxy: target }) await mutate(() => patchVergeConfig({ enable_system_proxy: target }), {
id: 'toggle-system-proxy',
errorNotice: false,
})
confirmed = target confirmed = target
if (!target && verge?.auto_close_connection) { if (!target && verge?.auto_close_connection) {
await closeAllConnections().catch(() => {}) await mutate(() => closeAllConnections(), {
id: 'close-all-connections',
errorNotice: false,
}).catch(() => {})
} }
} }
} catch (error) { } catch (error) {