mirror of
https://github.com/woodchen-ink/clash-and-dashboard.git
synced 2025-07-18 14:01:56 +08:00
Feature: switch group can close its connections
This commit is contained in:
parent
027e54bf1f
commit
48668b9312
@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react'
|
import React from 'react'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import { BaseComponentProps } from '@models/BaseProps'
|
import { BaseComponentProps } from '@models/BaseProps'
|
||||||
import { Spinner } from './Spinner'
|
import { Spinner } from './Spinner'
|
||||||
@ -19,17 +19,3 @@ export function Loading (props: LoadingProps) {
|
|||||||
)
|
)
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useLoading (initial: boolean) {
|
|
||||||
const [visible, setVisible] = useState(initial)
|
|
||||||
|
|
||||||
function hide () {
|
|
||||||
setVisible(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
function show () {
|
|
||||||
setVisible(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
return { visible, hide, show }
|
|
||||||
}
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import React, { useState, useLayoutEffect } from 'react'
|
import React, { useLayoutEffect } from 'react'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import { unmountComponentAtNode, render } from 'react-dom'
|
import { unmountComponentAtNode, render } from 'react-dom'
|
||||||
import { Icon } from '@components'
|
import { Icon } from '@components'
|
||||||
import { noop } from '@lib/helper'
|
import { noop } from '@lib/helper'
|
||||||
|
import { useVisible } from '@lib/hook'
|
||||||
import './style.scss'
|
import './style.scss'
|
||||||
|
|
||||||
const TYPE_ICON_MAP = {
|
const TYPE_ICON_MAP = {
|
||||||
@ -40,13 +41,13 @@ export function Message (props: MessageProps) {
|
|||||||
duration = 1500
|
duration = 1500
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
const [visible, setVisible] = useState(false)
|
const { visible, show, hide } = useVisible()
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
window.setTimeout(() => setVisible(true), 0)
|
window.setTimeout(() => show(), 0)
|
||||||
|
|
||||||
const id = window.setTimeout(() => {
|
const id = window.setTimeout(() => {
|
||||||
setVisible(false)
|
hide()
|
||||||
onClose()
|
onClose()
|
||||||
}, duration)
|
}, duration)
|
||||||
return () => window.clearTimeout(id)
|
return () => window.clearTimeout(id)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useRef, useLayoutEffect, MouseEvent, useState } from 'react'
|
import React, { useRef, useLayoutEffect, MouseEvent } from 'react'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import { createPortal } from 'react-dom'
|
import { createPortal } from 'react-dom'
|
||||||
import { BaseComponentProps } from '@models'
|
import { BaseComponentProps } from '@models'
|
||||||
@ -90,17 +90,3 @@ export function Modal (props: ModalProps) {
|
|||||||
|
|
||||||
return createPortal(modal, portalRef.current)
|
return createPortal(modal, portalRef.current)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useModal () {
|
|
||||||
const [visible, setVisible] = useState(false)
|
|
||||||
|
|
||||||
function show () {
|
|
||||||
setVisible(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
function hide () {
|
|
||||||
setVisible(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
return { visible, show, hide }
|
|
||||||
}
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import React, { useEffect, useMemo } from 'react'
|
import React, { useEffect, useMemo } from 'react'
|
||||||
import { useBlockLayout, useResizeColumns, useTable } from 'react-table'
|
import { useBlockLayout, useResizeColumns, useTable } from 'react-table'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import { Header, Card, Checkbox, Modal, useModal, Icon } from '@components'
|
import { Header, Card, Checkbox, Modal, Icon } from '@components'
|
||||||
import { containers } from '@stores'
|
import { containers } from '@stores'
|
||||||
import * as API from '@lib/request'
|
import * as API from '@lib/request'
|
||||||
import { StreamReader } from '@lib/streamer'
|
import { StreamReader } from '@lib/streamer'
|
||||||
import { useObject } from '@lib/hook'
|
import { useObject, useVisible } from '@lib/hook'
|
||||||
import { noop } from '@lib/helper'
|
import { noop } from '@lib/helper'
|
||||||
import { fromNow } from '@lib/date'
|
import { fromNow } from '@lib/date'
|
||||||
import { useConnections } from './store'
|
import { useConnections } from './store'
|
||||||
@ -87,7 +87,7 @@ export default function Connections () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// close all connections
|
// close all connections
|
||||||
const { visible, show, hide } = useModal()
|
const { visible, show, hide } = useVisible()
|
||||||
function handleCloseConnections () {
|
function handleCloseConnections () {
|
||||||
API.closeAllConnections().finally(() => hide())
|
API.closeAllConnections().finally(() => hide())
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ export default function ExternalController () {
|
|||||||
const { useTranslation } = containers.useI18n()
|
const { useTranslation } = containers.useI18n()
|
||||||
const { t } = useTranslation('Settings')
|
const { t } = useTranslation('Settings')
|
||||||
const { data: info, update, fetch } = containers.useAPIInfo()
|
const { data: info, update, fetch } = containers.useAPIInfo()
|
||||||
const { unauthorized: { hidden, visible } } = containers.useData()
|
const { unauthorized: { hide, visible } } = containers.useData()
|
||||||
const [value, set] = useObject({
|
const [value, set] = useObject({
|
||||||
hostname: '',
|
hostname: '',
|
||||||
port: '',
|
port: '',
|
||||||
@ -33,7 +33,7 @@ export default function ExternalController () {
|
|||||||
show={visible}
|
show={visible}
|
||||||
title={t('externalControllerSetting.title')}
|
title={t('externalControllerSetting.title')}
|
||||||
bodyClassName="external-controller"
|
bodyClassName="external-controller"
|
||||||
onClose={hidden}
|
onClose={hide}
|
||||||
onOk={handleOk}
|
onOk={handleOk}
|
||||||
>
|
>
|
||||||
<Alert type="info" inside={true}>
|
<Alert type="info" inside={true}>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { containers } from '@stores'
|
import { containers } from '@stores'
|
||||||
import { changeProxySelected, Group as IGroup } from '@lib/request'
|
import { changeProxySelected, Group as IGroup, getConnections, closeConnection } from '@lib/request'
|
||||||
import { Tags, Tag } from '@components'
|
import { Tags, Tag } from '@components'
|
||||||
import './style.scss'
|
import './style.scss'
|
||||||
|
|
||||||
@ -10,11 +10,25 @@ interface GroupProps {
|
|||||||
|
|
||||||
export function Group (props: GroupProps) {
|
export function Group (props: GroupProps) {
|
||||||
const { fetch } = containers.useData()
|
const { fetch } = containers.useData()
|
||||||
|
const { data: Config } = containers.useConfig()
|
||||||
const { config } = props
|
const { config } = props
|
||||||
|
|
||||||
async function handleChangeProxySelected (name: string) {
|
async function handleChangeProxySelected (name: string) {
|
||||||
await changeProxySelected(props.config.name, name)
|
await changeProxySelected(props.config.name, name)
|
||||||
await fetch()
|
await fetch()
|
||||||
|
if (Config.breakConnections) {
|
||||||
|
const list: string[] = []
|
||||||
|
const snapshot = await getConnections()
|
||||||
|
for (const connection of snapshot.data.connections) {
|
||||||
|
if (connection.chains.includes(name)) {
|
||||||
|
list.push(connection.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const id of list) {
|
||||||
|
closeConnection(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const canClick = config.type === 'Selector'
|
const canClick = config.type === 'Selector'
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { Card, Tag, Icon, Loading, useLoading } from '@components'
|
import { Card, Tag, Icon, Loading } from '@components'
|
||||||
import { containers } from '@stores'
|
import { containers } from '@stores'
|
||||||
import { fromNow } from '@lib/date'
|
import { fromNow } from '@lib/date'
|
||||||
import { Provider as IProvider, Proxy as IProxy, updateProvider, healthCheckProvider } from '@lib/request'
|
import { Provider as IProvider, Proxy as IProxy, updateProvider, healthCheckProvider } from '@lib/request'
|
||||||
|
import { useVisible } from '@lib/hook'
|
||||||
import { Proxy } from '../Proxy'
|
import { Proxy } from '../Proxy'
|
||||||
import { compareDesc } from '../../'
|
import { compareDesc } from '../../'
|
||||||
import './style.scss'
|
import './style.scss'
|
||||||
@ -19,7 +20,7 @@ export function Provider (props: ProvidersProps) {
|
|||||||
|
|
||||||
const { t } = useTranslation('Proxies')
|
const { t } = useTranslation('Proxies')
|
||||||
|
|
||||||
const { visible, hide, show } = useLoading(false)
|
const { visible, hide, show } = useVisible()
|
||||||
|
|
||||||
function handleHealthChech () {
|
function handleHealthChech () {
|
||||||
show()
|
show()
|
||||||
|
@ -2,7 +2,7 @@ import React, { useMemo } from 'react'
|
|||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import EE from '@lib/event'
|
import EE from '@lib/event'
|
||||||
import { useRound } from '@lib/hook'
|
import { useRound } from '@lib/hook'
|
||||||
import { Card, Header, Icon } from '@components'
|
import { Card, Header, Icon, Checkbox } from '@components'
|
||||||
import { containers } from '@stores'
|
import { containers } from '@stores'
|
||||||
import * as API from '@lib/request'
|
import * as API from '@lib/request'
|
||||||
|
|
||||||
@ -30,6 +30,7 @@ export function compareDesc (a: API.Proxy, b: API.Proxy) {
|
|||||||
export default function Proxies () {
|
export default function Proxies () {
|
||||||
const { data, fetch } = containers.useData()
|
const { data, fetch } = containers.useData()
|
||||||
const { useTranslation } = containers.useI18n()
|
const { useTranslation } = containers.useI18n()
|
||||||
|
const { data: config, set: setConfig } = containers.useConfig()
|
||||||
const { t } = useTranslation('Proxies')
|
const { t } = useTranslation('Proxies')
|
||||||
useSWR('data', fetch)
|
useSWR('data', fetch)
|
||||||
|
|
||||||
@ -57,7 +58,14 @@ export default function Proxies () {
|
|||||||
{
|
{
|
||||||
data.proxyGroup.length !== 0 &&
|
data.proxyGroup.length !== 0 &&
|
||||||
<div className="proxies-container">
|
<div className="proxies-container">
|
||||||
<Header title={t('groupTitle')} />
|
<Header title={t('groupTitle')}>
|
||||||
|
<Checkbox
|
||||||
|
className="connections-filter"
|
||||||
|
checked={config.breakConnections}
|
||||||
|
onChange={value => setConfig('breakConnections', value)}>
|
||||||
|
{t('breakConnectionsText')}
|
||||||
|
</Checkbox>
|
||||||
|
</Header>
|
||||||
<Card className="proxies-group-card">
|
<Card className="proxies-group-card">
|
||||||
<ul className="proxies-group-list">
|
<ul className="proxies-group-list">
|
||||||
{
|
{
|
||||||
|
@ -88,6 +88,7 @@ export default {
|
|||||||
providerUpdateTime: 'Last updated at',
|
providerUpdateTime: 'Last updated at',
|
||||||
expandText: 'Expand',
|
expandText: 'Expand',
|
||||||
collapseText: 'Collapse',
|
collapseText: 'Collapse',
|
||||||
speedTestText: 'Speed Test'
|
speedTestText: 'Speed Test',
|
||||||
|
breakConnectionsText: 'Close connections which include the group'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,6 +88,7 @@ export default {
|
|||||||
providerUpdateTime: '最后更新于',
|
providerUpdateTime: '最后更新于',
|
||||||
expandText: '展开',
|
expandText: '展开',
|
||||||
collapseText: '收起',
|
collapseText: '收起',
|
||||||
speedTestText: '测速'
|
speedTestText: '测速',
|
||||||
|
breakConnectionsText: '切换时打断包含策略组的连接'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,3 +89,16 @@ export function useRound<T> (list: T[], defidx = 0) {
|
|||||||
|
|
||||||
return { current, next }
|
return { current, next }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useVisible (initial = false) {
|
||||||
|
const [visible, setVisible] = useState(initial)
|
||||||
|
|
||||||
|
function hide () {
|
||||||
|
setVisible(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
function show () {
|
||||||
|
setVisible(true)
|
||||||
|
}
|
||||||
|
return { visible, hide, show }
|
||||||
|
}
|
||||||
|
@ -199,6 +199,16 @@ export async function closeAllConnections () {
|
|||||||
return req.delete('connections')
|
return req.delete('connections')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function closeConnection (id: string) {
|
||||||
|
const req = await getInstance()
|
||||||
|
return req.delete(`connections/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getConnections () {
|
||||||
|
const req = await getInstance()
|
||||||
|
return req.get<Snapshot>('connections')
|
||||||
|
}
|
||||||
|
|
||||||
export async function changeProxySelected (name: string, select: string) {
|
export async function changeProxySelected (name: string, select: string) {
|
||||||
const req = await getInstance()
|
const req = await getInstance()
|
||||||
return req.put<void>(`proxies/${name}`, { name: select })
|
return req.put<void>(`proxies/${name}`, { name: select })
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { useState } from 'react'
|
|
||||||
import * as Models from '@models'
|
import * as Models from '@models'
|
||||||
import * as API from '@lib/request'
|
import * as API from '@lib/request'
|
||||||
import { useObject, composeContainer } from '@lib/hook'
|
import { useObject, composeContainer, useVisible } from '@lib/hook'
|
||||||
import { jsBridge } from '@lib/jsBridge'
|
import { jsBridge } from '@lib/jsBridge'
|
||||||
import { setLocalStorageItem, partition, to } from '@lib/helper'
|
import { setLocalStorageItem, partition, to } from '@lib/helper'
|
||||||
import { useI18n } from '@i18n'
|
import { useI18n } from '@i18n'
|
||||||
|
import { AxiosError } from 'axios'
|
||||||
|
|
||||||
function useData () {
|
function useData () {
|
||||||
const [data, set] = useObject<Models.Data>({
|
const [data, set] = useObject<Models.Data>({
|
||||||
@ -16,19 +16,12 @@ function useData () {
|
|||||||
rules: []
|
rules: []
|
||||||
})
|
})
|
||||||
|
|
||||||
const [visible, setVisible] = useState(false)
|
const { visible, show, hide } = useVisible()
|
||||||
|
|
||||||
function show () {
|
|
||||||
setVisible(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
function hidden () {
|
|
||||||
setVisible(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetch () {
|
async function fetch () {
|
||||||
const [resp, err] = await to(Promise.all([API.getConfig(), API.getProxies(), API.getRules(), API.getProxyProviders()]))
|
const [resp, err] = await to(Promise.all([API.getConfig(), API.getProxies(), API.getRules(), API.getProxyProviders()]))
|
||||||
if (err && (!err.response || err.response.status === 401)) {
|
const rErr = err as AxiosError
|
||||||
|
if (rErr && (!rErr.response || rErr.response.status === 401)) {
|
||||||
show()
|
show()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +75,7 @@ function useData () {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return { data, fetch, unauthorized: { visible, show, hidden }, updateDelay }
|
return { data, fetch, unauthorized: { visible, show, hide }, updateDelay }
|
||||||
}
|
}
|
||||||
|
|
||||||
function useAPIInfo () {
|
function useAPIInfo () {
|
||||||
@ -108,6 +101,14 @@ function useAPIInfo () {
|
|||||||
return { data, fetch, update }
|
return { data, fetch, update }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function useConfig () {
|
||||||
|
const [data, set] = useObject({
|
||||||
|
breakConnections: false
|
||||||
|
})
|
||||||
|
|
||||||
|
return { data, set }
|
||||||
|
}
|
||||||
|
|
||||||
function useClashXData () {
|
function useClashXData () {
|
||||||
const [data, set] = useObject<Models.ClashXData>({
|
const [data, set] = useObject<Models.ClashXData>({
|
||||||
startAtLogin: false,
|
startAtLogin: false,
|
||||||
@ -128,7 +129,8 @@ const { Provider, containers } = composeContainer({
|
|||||||
useData,
|
useData,
|
||||||
useAPIInfo,
|
useAPIInfo,
|
||||||
useClashXData,
|
useClashXData,
|
||||||
useI18n
|
useI18n,
|
||||||
|
useConfig
|
||||||
})
|
})
|
||||||
|
|
||||||
export { Provider, containers }
|
export { Provider, containers }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user