Feature: switch group can close its connections

This commit is contained in:
Dreamacro 2020-03-06 21:55:48 +08:00
parent 027e54bf1f
commit 48668b9312
13 changed files with 83 additions and 60 deletions

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react'
import React from 'react'
import classnames from 'classnames'
import { BaseComponentProps } from '@models/BaseProps'
import { Spinner } from './Spinner'
@ -19,17 +19,3 @@ export function Loading (props: LoadingProps) {
)
: null
}
export function useLoading (initial: boolean) {
const [visible, setVisible] = useState(initial)
function hide () {
setVisible(false)
}
function show () {
setVisible(true)
}
return { visible, hide, show }
}

View File

@ -1,8 +1,9 @@
import React, { useState, useLayoutEffect } from 'react'
import React, { useLayoutEffect } from 'react'
import classnames from 'classnames'
import { unmountComponentAtNode, render } from 'react-dom'
import { Icon } from '@components'
import { noop } from '@lib/helper'
import { useVisible } from '@lib/hook'
import './style.scss'
const TYPE_ICON_MAP = {
@ -40,13 +41,13 @@ export function Message (props: MessageProps) {
duration = 1500
} = props
const [visible, setVisible] = useState(false)
const { visible, show, hide } = useVisible()
useLayoutEffect(() => {
window.setTimeout(() => setVisible(true), 0)
window.setTimeout(() => show(), 0)
const id = window.setTimeout(() => {
setVisible(false)
hide()
onClose()
}, duration)
return () => window.clearTimeout(id)

View File

@ -1,4 +1,4 @@
import React, { useRef, useLayoutEffect, MouseEvent, useState } from 'react'
import React, { useRef, useLayoutEffect, MouseEvent } from 'react'
import classnames from 'classnames'
import { createPortal } from 'react-dom'
import { BaseComponentProps } from '@models'
@ -90,17 +90,3 @@ export function Modal (props: ModalProps) {
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 }
}

View File

@ -1,11 +1,11 @@
import React, { useEffect, useMemo } from 'react'
import { useBlockLayout, useResizeColumns, useTable } from 'react-table'
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 * as API from '@lib/request'
import { StreamReader } from '@lib/streamer'
import { useObject } from '@lib/hook'
import { useObject, useVisible } from '@lib/hook'
import { noop } from '@lib/helper'
import { fromNow } from '@lib/date'
import { useConnections } from './store'
@ -87,7 +87,7 @@ export default function Connections () {
}
// close all connections
const { visible, show, hide } = useModal()
const { visible, show, hide } = useVisible()
function handleCloseConnections () {
API.closeAllConnections().finally(() => hide())
}

View File

@ -8,7 +8,7 @@ export default function ExternalController () {
const { useTranslation } = containers.useI18n()
const { t } = useTranslation('Settings')
const { data: info, update, fetch } = containers.useAPIInfo()
const { unauthorized: { hidden, visible } } = containers.useData()
const { unauthorized: { hide, visible } } = containers.useData()
const [value, set] = useObject({
hostname: '',
port: '',
@ -33,7 +33,7 @@ export default function ExternalController () {
show={visible}
title={t('externalControllerSetting.title')}
bodyClassName="external-controller"
onClose={hidden}
onClose={hide}
onOk={handleOk}
>
<Alert type="info" inside={true}>

View File

@ -1,6 +1,6 @@
import * as React from 'react'
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 './style.scss'
@ -10,11 +10,25 @@ interface GroupProps {
export function Group (props: GroupProps) {
const { fetch } = containers.useData()
const { data: Config } = containers.useConfig()
const { config } = props
async function handleChangeProxySelected (name: string) {
await changeProxySelected(props.config.name, name)
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'

View File

@ -1,9 +1,10 @@
import * as React 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 { fromNow } from '@lib/date'
import { Provider as IProvider, Proxy as IProxy, updateProvider, healthCheckProvider } from '@lib/request'
import { useVisible } from '@lib/hook'
import { Proxy } from '../Proxy'
import { compareDesc } from '../../'
import './style.scss'
@ -19,7 +20,7 @@ export function Provider (props: ProvidersProps) {
const { t } = useTranslation('Proxies')
const { visible, hide, show } = useLoading(false)
const { visible, hide, show } = useVisible()
function handleHealthChech () {
show()

View File

@ -2,7 +2,7 @@ import React, { useMemo } from 'react'
import useSWR from 'swr'
import EE from '@lib/event'
import { useRound } from '@lib/hook'
import { Card, Header, Icon } from '@components'
import { Card, Header, Icon, Checkbox } from '@components'
import { containers } from '@stores'
import * as API from '@lib/request'
@ -30,6 +30,7 @@ export function compareDesc (a: API.Proxy, b: API.Proxy) {
export default function Proxies () {
const { data, fetch } = containers.useData()
const { useTranslation } = containers.useI18n()
const { data: config, set: setConfig } = containers.useConfig()
const { t } = useTranslation('Proxies')
useSWR('data', fetch)
@ -57,7 +58,14 @@ export default function Proxies () {
{
data.proxyGroup.length !== 0 &&
<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">
<ul className="proxies-group-list">
{

View File

@ -88,6 +88,7 @@ export default {
providerUpdateTime: 'Last updated at',
expandText: 'Expand',
collapseText: 'Collapse',
speedTestText: 'Speed Test'
speedTestText: 'Speed Test',
breakConnectionsText: 'Close connections which include the group'
}
}

View File

@ -88,6 +88,7 @@ export default {
providerUpdateTime: '最后更新于',
expandText: '展开',
collapseText: '收起',
speedTestText: '测速'
speedTestText: '测速',
breakConnectionsText: '切换时打断包含策略组的连接'
}
}

View File

@ -89,3 +89,16 @@ export function useRound<T> (list: T[], defidx = 0) {
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 }
}

View File

@ -199,6 +199,16 @@ export async function closeAllConnections () {
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) {
const req = await getInstance()
return req.put<void>(`proxies/${name}`, { name: select })

View File

@ -1,10 +1,10 @@
import { useState } from 'react'
import * as Models from '@models'
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 { setLocalStorageItem, partition, to } from '@lib/helper'
import { useI18n } from '@i18n'
import { AxiosError } from 'axios'
function useData () {
const [data, set] = useObject<Models.Data>({
@ -16,19 +16,12 @@ function useData () {
rules: []
})
const [visible, setVisible] = useState(false)
function show () {
setVisible(true)
}
function hidden () {
setVisible(false)
}
const { visible, show, hide } = useVisible()
async function fetch () {
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()
}
@ -82,7 +75,7 @@ function useData () {
})
}
return { data, fetch, unauthorized: { visible, show, hidden }, updateDelay }
return { data, fetch, unauthorized: { visible, show, hide }, updateDelay }
}
function useAPIInfo () {
@ -108,6 +101,14 @@ function useAPIInfo () {
return { data, fetch, update }
}
function useConfig () {
const [data, set] = useObject({
breakConnections: false
})
return { data, set }
}
function useClashXData () {
const [data, set] = useObject<Models.ClashXData>({
startAtLogin: false,
@ -128,7 +129,8 @@ const { Provider, containers } = composeContainer({
useData,
useAPIInfo,
useClashXData,
useI18n
useI18n,
useConfig
})
export { Provider, containers }