mirror of
https://github.com/woodchen-ink/clash-and-dashboard.git
synced 2025-07-18 14:01:56 +08:00
Chore: clear request client
This commit is contained in:
parent
53f4aa4e32
commit
7e999dfdca
1
src/assets/index.d.ts
vendored
Normal file
1
src/assets/index.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
declare module '*.png'
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect } from 'react'
|
import React from 'react'
|
||||||
import { Route, Redirect, Switch } from 'react-router-dom'
|
import { Route, Redirect, Switch } from 'react-router-dom'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import { isClashX } from '@lib/jsBridge'
|
import { isClashX } from '@lib/jsBridge'
|
||||||
@ -11,15 +11,13 @@ import Settings from '@containers/Settings'
|
|||||||
import SlideBar from '@containers/Sidebar'
|
import SlideBar from '@containers/Sidebar'
|
||||||
import Connections from '@containers/Connections'
|
import Connections from '@containers/Connections'
|
||||||
import ExternalControllerModal from '@containers/ExternalControllerDrawer'
|
import ExternalControllerModal from '@containers/ExternalControllerDrawer'
|
||||||
import { getLogsStreamReader } from '@lib/request'
|
import { useLogsStreamReader } from '@stores'
|
||||||
|
|
||||||
import '../styles/common.scss'
|
import '../styles/common.scss'
|
||||||
import '../styles/iconfont.scss'
|
import '../styles/iconfont.scss'
|
||||||
|
|
||||||
export default function App () {
|
export default function App () {
|
||||||
useEffect(() => {
|
useLogsStreamReader()
|
||||||
getLogsStreamReader()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
// { path: '/', name: 'Overview', component: Overview, exact: true },
|
// { path: '/', name: 'Overview', component: Overview, exact: true },
|
||||||
|
@ -4,9 +4,8 @@ import classnames from 'classnames'
|
|||||||
import { useScroll } from 'react-use'
|
import { useScroll } from 'react-use'
|
||||||
import { groupBy } from 'lodash-es'
|
import { groupBy } from 'lodash-es'
|
||||||
import { Header, Card, Checkbox, Modal, Icon } from '@components'
|
import { Header, Card, Checkbox, Modal, Icon } from '@components'
|
||||||
import { useI18n } from '@stores'
|
import { useClient, useConnectionStreamReader, useI18n } from '@stores'
|
||||||
import * as API from '@lib/request'
|
import * as API from '@lib/request'
|
||||||
import { StreamReader } from '@lib/streamer'
|
|
||||||
import { useObject, useVisible } from '@lib/hook'
|
import { useObject, useVisible } from '@lib/hook'
|
||||||
import { fromNow } from '@lib/date'
|
import { fromNow } from '@lib/date'
|
||||||
import { RuleType } from '@models'
|
import { RuleType } from '@models'
|
||||||
@ -93,6 +92,8 @@ interface formatConnection {
|
|||||||
export default function Connections() {
|
export default function Connections() {
|
||||||
const { translation, lang } = useI18n()
|
const { translation, lang } = useI18n()
|
||||||
const t = useMemo(() => translation('Connections').t, [translation])
|
const t = useMemo(() => translation('Connections').t, [translation])
|
||||||
|
const connStreamReader = useConnectionStreamReader()
|
||||||
|
const client = useClient()
|
||||||
|
|
||||||
// total
|
// total
|
||||||
const [traffic, setTraffic] = useObject({
|
const [traffic, setTraffic] = useObject({
|
||||||
@ -103,7 +104,7 @@ export default function Connections() {
|
|||||||
// close all connections
|
// close all connections
|
||||||
const { visible, show, hide } = useVisible()
|
const { visible, show, hide } = useVisible()
|
||||||
function handleCloseConnections() {
|
function handleCloseConnections() {
|
||||||
API.closeAllConnections().finally(() => hide())
|
client.closeAllConnections().finally(() => hide())
|
||||||
}
|
}
|
||||||
|
|
||||||
// connections
|
// connections
|
||||||
@ -161,8 +162,6 @@ export default function Connections() {
|
|||||||
] as TableColumnOption<formatConnection>[], [t])
|
] as TableColumnOption<formatConnection>[], [t])
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
let streamReader: StreamReader<API.Snapshot> | null = null
|
|
||||||
|
|
||||||
function handleConnection(snapshots: API.Snapshot[]) {
|
function handleConnection(snapshots: API.Snapshot[]) {
|
||||||
for (const snapshot of snapshots) {
|
for (const snapshot of snapshots) {
|
||||||
setTraffic({
|
setTraffic({
|
||||||
@ -174,18 +173,12 @@ export default function Connections() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(async function () {
|
connStreamReader?.subscribe('data', handleConnection)
|
||||||
streamReader = await API.getConnectionStreamReader()
|
|
||||||
streamReader.subscribe('data', handleConnection)
|
|
||||||
}())
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (streamReader) {
|
connStreamReader?.unsubscribe('data', handleConnection)
|
||||||
streamReader.unsubscribe('data', handleConnection)
|
connStreamReader?.destory()
|
||||||
streamReader.destory()
|
|
||||||
}
|
}
|
||||||
}
|
}, [connStreamReader, feed, setTraffic])
|
||||||
}, [feed, setTraffic])
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getTableProps,
|
getTableProps,
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
import React, { useEffect } from 'react'
|
import React, { useEffect } from 'react'
|
||||||
|
import { useAtom } from 'jotai'
|
||||||
|
import { useUpdateAtom } from 'jotai/utils'
|
||||||
import { useObject } from '@lib/hook'
|
import { useObject } from '@lib/hook'
|
||||||
import { Modal, Input, Alert } from '@components'
|
import { Modal, Input, Alert } from '@components'
|
||||||
import { useI18n, useAPIInfo, useIdentity } from '@stores'
|
import { useI18n, useAPIInfo, identityAtom } from '@stores'
|
||||||
|
import { localStorageAtom } from '@stores/request'
|
||||||
import './style.scss'
|
import './style.scss'
|
||||||
|
|
||||||
export default function ExternalController () {
|
export default function ExternalController () {
|
||||||
const { translation } = useI18n()
|
const { translation } = useI18n()
|
||||||
const { t } = translation('Settings')
|
const { t } = translation('Settings')
|
||||||
const { data: info, update, fetch } = useAPIInfo()
|
const { hostname, port, secret } = useAPIInfo()
|
||||||
const { identity, set: setIdentity } = useIdentity()
|
const [identity, setIdentity] = useAtom(identityAtom)
|
||||||
const [value, set] = useObject({
|
const [value, set] = useObject({
|
||||||
hostname: '',
|
hostname: '',
|
||||||
port: '',
|
port: '',
|
||||||
@ -16,16 +19,14 @@ export default function ExternalController () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch()
|
set({ hostname, port, secret })
|
||||||
}, [fetch])
|
}, [hostname, port, secret, set])
|
||||||
|
|
||||||
useEffect(() => {
|
const setter = useUpdateAtom(localStorageAtom)
|
||||||
set({ hostname: info.hostname, port: info.port, secret: info.secret })
|
|
||||||
}, [info, set])
|
|
||||||
|
|
||||||
function handleOk () {
|
function handleOk () {
|
||||||
const { hostname, port, secret } = value
|
const { hostname, port, secret } = value
|
||||||
update({ hostname, port, secret })
|
setter([{ hostname, port, secret }])
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import React, { useLayoutEffect, useEffect, useRef, useState } from 'react'
|
import React, { useLayoutEffect, useEffect, useRef, useState } from 'react'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { useI18n } from '@stores'
|
import { useI18n, useLogsStreamReader } from '@stores'
|
||||||
import { Card, Header } from '@components'
|
import { Card, Header } from '@components'
|
||||||
import { getLogsStreamReader } from '@lib/request'
|
|
||||||
import { StreamReader } from '@lib/streamer'
|
|
||||||
import { Log } from '@models/Log'
|
import { Log } from '@models/Log'
|
||||||
import './style.scss'
|
import './style.scss'
|
||||||
|
|
||||||
@ -13,6 +11,7 @@ export default function Logs () {
|
|||||||
const [logs, setLogs] = useState<Log[]>([])
|
const [logs, setLogs] = useState<Log[]>([])
|
||||||
const { translation } = useI18n()
|
const { translation } = useI18n()
|
||||||
const { t } = translation('Logs')
|
const { t } = translation('Logs')
|
||||||
|
const logsStreamReader = useLogsStreamReader()
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const ul = listRef.current
|
const ul = listRef.current
|
||||||
@ -22,22 +21,19 @@ export default function Logs () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let streamReader: StreamReader<Log> | null = null
|
|
||||||
|
|
||||||
function handleLog (newLogs: Log[]) {
|
function handleLog (newLogs: Log[]) {
|
||||||
logsRef.current = logsRef.current.slice().concat(newLogs.map(d => ({ ...d, time: new Date() })))
|
logsRef.current = logsRef.current.slice().concat(newLogs.map(d => ({ ...d, time: new Date() })))
|
||||||
setLogs(logsRef.current)
|
setLogs(logsRef.current)
|
||||||
}
|
}
|
||||||
|
|
||||||
(async function () {
|
if (logsStreamReader) {
|
||||||
streamReader = await getLogsStreamReader()
|
logsStreamReader.subscribe('data', handleLog)
|
||||||
logsRef.current = streamReader.buffer()
|
logsRef.current = logsStreamReader.buffer()
|
||||||
setLogs(logsRef.current)
|
setLogs(logsRef.current)
|
||||||
streamReader.subscribe('data', handleLog)
|
}
|
||||||
}())
|
|
||||||
|
|
||||||
return () => streamReader?.unsubscribe('data', handleLog)
|
return () => logsStreamReader?.unsubscribe('data', handleLog)
|
||||||
}, [])
|
}, [logsStreamReader])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page">
|
<div className="page">
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import { useAtom } from 'jotai'
|
import { useAtom } from 'jotai'
|
||||||
import { useProxy, useConfig, proxyMapping } from '@stores'
|
import { useProxy, useConfig, proxyMapping, useClient } from '@stores'
|
||||||
import { changeProxySelected, Group as IGroup, getConnections, closeConnection } from '@lib/request'
|
import { Group as IGroup } from '@lib/request'
|
||||||
import { Tags, Tag } from '@components'
|
import { Tags, Tag } from '@components'
|
||||||
import './style.scss'
|
import './style.scss'
|
||||||
|
|
||||||
@ -13,14 +13,15 @@ export function Group (props: GroupProps) {
|
|||||||
const { markProxySelected } = useProxy()
|
const { markProxySelected } = useProxy()
|
||||||
const [proxyMap] = useAtom(proxyMapping)
|
const [proxyMap] = useAtom(proxyMapping)
|
||||||
const { data: Config } = useConfig()
|
const { data: Config } = useConfig()
|
||||||
|
const client = useClient()
|
||||||
const { config } = props
|
const { config } = props
|
||||||
|
|
||||||
async function handleChangeProxySelected (name: string) {
|
async function handleChangeProxySelected (name: string) {
|
||||||
await changeProxySelected(props.config.name, name)
|
await client.changeProxySelected(props.config.name, name)
|
||||||
markProxySelected(props.config.name, name)
|
markProxySelected(props.config.name, name)
|
||||||
if (Config.breakConnections) {
|
if (Config.breakConnections) {
|
||||||
const list: string[] = []
|
const list: string[] = []
|
||||||
const snapshot = await getConnections()
|
const snapshot = await client.getConnections()
|
||||||
for (const connection of snapshot.data.connections) {
|
for (const connection of snapshot.data.connections) {
|
||||||
if (connection.chains.includes(props.config.name)) {
|
if (connection.chains.includes(props.config.name)) {
|
||||||
list.push(connection.id)
|
list.push(connection.id)
|
||||||
@ -28,7 +29,7 @@ export function Group (props: GroupProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const id of list) {
|
for (const id of list) {
|
||||||
closeConnection(id)
|
client.closeConnection(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import { Card, Tag, Icon, Loading } from '@components'
|
import { Card, Tag, Icon, Loading } from '@components'
|
||||||
import { useI18n, useProxyProviders } from '@stores'
|
import { useClient, useI18n, useProxyProviders } 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 } from '@lib/request'
|
||||||
import { useVisible } from '@lib/hook'
|
import { useVisible } from '@lib/hook'
|
||||||
import { compareDesc } from '@containers/Proxies'
|
import { compareDesc } from '@containers/Proxies'
|
||||||
import { Proxy } from '@containers/Proxies/components/Proxy'
|
import { Proxy } from '@containers/Proxies/components/Proxy'
|
||||||
@ -15,6 +15,7 @@ interface ProvidersProps {
|
|||||||
export function Provider (props: ProvidersProps) {
|
export function Provider (props: ProvidersProps) {
|
||||||
const { update } = useProxyProviders()
|
const { update } = useProxyProviders()
|
||||||
const { translation, lang } = useI18n()
|
const { translation, lang } = useI18n()
|
||||||
|
const client = useClient()
|
||||||
|
|
||||||
const { provider } = props
|
const { provider } = props
|
||||||
const { t } = translation('Proxies')
|
const { t } = translation('Proxies')
|
||||||
@ -23,12 +24,12 @@ export function Provider (props: ProvidersProps) {
|
|||||||
|
|
||||||
function handleHealthChech () {
|
function handleHealthChech () {
|
||||||
show()
|
show()
|
||||||
healthCheckProvider(provider.name).then(() => update()).finally(() => hide())
|
client.healthCheckProvider(provider.name).then(() => update()).finally(() => hide())
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleUpdate () {
|
function handleUpdate () {
|
||||||
show()
|
show()
|
||||||
updateProvider(provider.name).then(() => update()).finally(() => hide())
|
client.updateProvider(provider.name).then(() => update()).finally(() => hide())
|
||||||
}
|
}
|
||||||
|
|
||||||
const proxies = useMemo(() => {
|
const proxies = useMemo(() => {
|
||||||
|
@ -3,8 +3,8 @@ import { ResultAsync } from 'neverthrow'
|
|||||||
import type{ AxiosError } from 'axios'
|
import type{ AxiosError } from 'axios'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import { BaseComponentProps } from '@models'
|
import { BaseComponentProps } from '@models'
|
||||||
import { useProxy } from '@stores'
|
import { useClient, useProxy } from '@stores'
|
||||||
import { getProxyDelay, Proxy as IProxy } from '@lib/request'
|
import { Proxy as IProxy } from '@lib/request'
|
||||||
import EE, { Action } from '@lib/event'
|
import EE, { Action } from '@lib/event'
|
||||||
import { isClashX, jsBridge } from '@lib/jsBridge'
|
import { isClashX, jsBridge } from '@lib/jsBridge'
|
||||||
|
|
||||||
@ -21,19 +21,20 @@ const TagColors = {
|
|||||||
'#ff3e5e': Infinity
|
'#ff3e5e': Infinity
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getDelay (name: string) {
|
export function Proxy (props: ProxyProps) {
|
||||||
|
const { config, className } = props
|
||||||
|
const { set } = useProxy()
|
||||||
|
const client = useClient()
|
||||||
|
|
||||||
|
const getDelay = useCallback(async (name: string) => {
|
||||||
if (isClashX()) {
|
if (isClashX()) {
|
||||||
const delay = await jsBridge?.getProxyDelay(name) ?? 0
|
const delay = await jsBridge?.getProxyDelay(name) ?? 0
|
||||||
return delay
|
return delay
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data: { delay } } = await getProxyDelay(name)
|
const { data: { delay } } = await client.getProxyDelay(name)
|
||||||
return delay
|
return delay
|
||||||
}
|
}, [client])
|
||||||
|
|
||||||
export function Proxy (props: ProxyProps) {
|
|
||||||
const { config, className } = props
|
|
||||||
const { set } = useProxy()
|
|
||||||
|
|
||||||
const speedTest = useCallback(async function () {
|
const speedTest = useCallback(async function () {
|
||||||
const result = await ResultAsync.fromPromise(getDelay(config.name), e => e as AxiosError)
|
const result = await ResultAsync.fromPromise(getDelay(config.name), e => e as AxiosError)
|
||||||
@ -45,7 +46,7 @@ export function Proxy (props: ProxyProps) {
|
|||||||
proxy.history.push({ time: Date.now().toString(), delay: validDelay })
|
proxy.history.push({ time: Date.now().toString(), delay: validDelay })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, [config.name, set])
|
}, [config.name, getDelay, set])
|
||||||
|
|
||||||
const delay = useMemo(
|
const delay = useMemo(
|
||||||
() => config.history?.length ? config.history.slice(-1)[0].delay : 0,
|
() => config.history?.length ? config.history.slice(-1)[0].delay : 0,
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import { Card, Tag, Icon } from '@components'
|
import { Card, Tag, Icon } from '@components'
|
||||||
import { useI18n, useRuleProviders } from '@stores'
|
import { useClient, useI18n, useRuleProviders } from '@stores'
|
||||||
import { fromNow } from '@lib/date'
|
import { fromNow } from '@lib/date'
|
||||||
import { RuleProvider, updateRuleProvider } from '@lib/request'
|
import { RuleProvider } from '@lib/request'
|
||||||
import { useVisible } from '@lib/hook'
|
import { useVisible } from '@lib/hook'
|
||||||
import './style.scss'
|
import './style.scss'
|
||||||
|
|
||||||
@ -14,6 +14,7 @@ interface ProvidersProps {
|
|||||||
export function Provider (props: ProvidersProps) {
|
export function Provider (props: ProvidersProps) {
|
||||||
const { update } = useRuleProviders()
|
const { update } = useRuleProviders()
|
||||||
const { translation, lang } = useI18n()
|
const { translation, lang } = useI18n()
|
||||||
|
const client = useClient()
|
||||||
|
|
||||||
const { provider } = props
|
const { provider } = props
|
||||||
const { t } = translation('Rules')
|
const { t } = translation('Rules')
|
||||||
@ -22,7 +23,7 @@ export function Provider (props: ProvidersProps) {
|
|||||||
|
|
||||||
function handleUpdate () {
|
function handleUpdate () {
|
||||||
show()
|
show()
|
||||||
updateRuleProvider(provider.name).then(() => update()).finally(() => hide())
|
client.updateRuleProvider(provider.name).then(() => update()).finally(() => hide())
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateClassnames = classnames('rule-provider-icon', { 'rule-provider-loading': visible })
|
const updateClassnames = classnames('rule-provider-icon', { 'rule-provider-loading': visible })
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import React, { useEffect, useMemo } from 'react'
|
import React, { useEffect, useMemo } from 'react'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
|
import { useUpdateAtom } from 'jotai/utils'
|
||||||
import { capitalize } from 'lodash-es'
|
import { capitalize } from 'lodash-es'
|
||||||
import { Header, Card, Switch, ButtonSelect, ButtonSelectOptions, Input } from '@components'
|
import { Header, Card, Switch, ButtonSelect, ButtonSelectOptions, Input } from '@components'
|
||||||
import { useI18n, useClashXData, useAPIInfo, useGeneral, useIdentity, useVersion } from '@stores'
|
import { useI18n, useClashXData, useAPIInfo, useGeneral, useVersion, useClient, identityAtom } from '@stores'
|
||||||
import { updateConfig } from '@lib/request'
|
|
||||||
import { useObject } from '@lib/hook'
|
import { useObject } from '@lib/hook'
|
||||||
import { jsBridge } from '@lib/jsBridge'
|
import { jsBridge } from '@lib/jsBridge'
|
||||||
import { Lang } from '@i18n'
|
import { Lang } from '@i18n'
|
||||||
@ -15,10 +15,11 @@ export default function Settings () {
|
|||||||
const { premium } = useVersion()
|
const { premium } = useVersion()
|
||||||
const { data: clashXData, update: fetchClashXData } = useClashXData()
|
const { data: clashXData, update: fetchClashXData } = useClashXData()
|
||||||
const { general, update: fetchGeneral } = useGeneral()
|
const { general, update: fetchGeneral } = useGeneral()
|
||||||
const { set: setIdentity } = useIdentity()
|
const setIdentity = useUpdateAtom(identityAtom)
|
||||||
const { data: apiInfo } = useAPIInfo()
|
const apiInfo = useAPIInfo()
|
||||||
const { translation, setLang, lang } = useI18n()
|
const { translation, setLang, lang } = useI18n()
|
||||||
const { t } = translation('Settings')
|
const { t } = translation('Settings')
|
||||||
|
const client = useClient()
|
||||||
const [info, set] = useObject({
|
const [info, set] = useObject({
|
||||||
socks5ProxyPort: 7891,
|
socks5ProxyPort: 7891,
|
||||||
httpProxyPort: 7890,
|
httpProxyPort: 7890,
|
||||||
@ -32,7 +33,7 @@ export default function Settings () {
|
|||||||
}, [general, set])
|
}, [general, set])
|
||||||
|
|
||||||
async function handleProxyModeChange (mode: string) {
|
async function handleProxyModeChange (mode: string) {
|
||||||
await updateConfig({ mode })
|
await client.updateConfig({ mode })
|
||||||
await fetchGeneral()
|
await fetchGeneral()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,22 +52,22 @@ export default function Settings () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleHttpPortSave () {
|
async function handleHttpPortSave () {
|
||||||
await updateConfig({ port: info.httpProxyPort })
|
await client.updateConfig({ port: info.httpProxyPort })
|
||||||
await fetchGeneral()
|
await fetchGeneral()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleSocksPortSave () {
|
async function handleSocksPortSave () {
|
||||||
await updateConfig({ 'socks-port': info.socks5ProxyPort })
|
await client.updateConfig({ 'socks-port': info.socks5ProxyPort })
|
||||||
await fetchGeneral()
|
await fetchGeneral()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleMixedPortSave () {
|
async function handleMixedPortSave () {
|
||||||
await updateConfig({ 'mixed-port': info.mixedProxyPort })
|
await client.updateConfig({ 'mixed-port': info.mixedProxyPort })
|
||||||
await fetchGeneral()
|
await fetchGeneral()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleAllowLanChange (state: boolean) {
|
async function handleAllowLanChange (state: boolean) {
|
||||||
await updateConfig({ 'allow-lan': state })
|
await client.updateConfig({ 'allow-lan': state })
|
||||||
await fetchGeneral()
|
await fetchGeneral()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,9 +3,8 @@ import { NavLink } from 'react-router-dom'
|
|||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import { useI18n, useVersion, useClashXData } from '@stores'
|
import { useI18n, useVersion, useClashXData } from '@stores'
|
||||||
|
|
||||||
import './style.scss'
|
|
||||||
import logo from '@assets/logo.png'
|
import logo from '@assets/logo.png'
|
||||||
import useSWR from 'swr'
|
import './style.scss'
|
||||||
|
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
routes: {
|
routes: {
|
||||||
@ -19,12 +18,10 @@ interface SidebarProps {
|
|||||||
export default function Sidebar (props: SidebarProps) {
|
export default function Sidebar (props: SidebarProps) {
|
||||||
const { routes } = props
|
const { routes } = props
|
||||||
const { translation } = useI18n()
|
const { translation } = useI18n()
|
||||||
const { version, premium, update } = useVersion()
|
const { version, premium } = useVersion()
|
||||||
const { data } = useClashXData()
|
const { data } = useClashXData()
|
||||||
const { t } = translation('SideBar')
|
const { t } = translation('SideBar')
|
||||||
|
|
||||||
useSWR('version', update)
|
|
||||||
|
|
||||||
const navlinks = routes.map(
|
const navlinks = routes.map(
|
||||||
({ path, name, exact, noMobile }) => (
|
({ path, name, exact, noMobile }) => (
|
||||||
<li className={classnames('item', { 'no-mobile': noMobile })} key={name}>
|
<li className={classnames('item', { 'no-mobile': noMobile })} key={name}>
|
||||||
|
@ -1,17 +1,5 @@
|
|||||||
export function getLocalStorageItem (key: string, defaultValue = '') {
|
|
||||||
return window.localStorage.getItem(key) || defaultValue
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setLocalStorageItem (key: string, value: string) {
|
|
||||||
return window.localStorage.setItem(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function noop () {}
|
export function noop () {}
|
||||||
|
|
||||||
export function getSearchParam(key: string) {
|
|
||||||
return new URLSearchParams(window.location.search).get(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function partition<T> (arr: T[], fn: (arg: T) => boolean): [T[], T[]] {
|
export function partition<T> (arr: T[], fn: (arg: T) => boolean): [T[], T[]] {
|
||||||
const left: T[] = []
|
const left: T[] = []
|
||||||
const right: T[] = []
|
const right: T[] = []
|
||||||
|
@ -1,10 +1,4 @@
|
|||||||
import axios, { AxiosError } from 'axios'
|
import axios, { AxiosInstance } from 'axios'
|
||||||
import { ResultAsync } from 'neverthrow'
|
|
||||||
import { getLocalStorageItem, getSearchParam } from '@lib/helper'
|
|
||||||
import { isClashX, jsBridge } from '@lib/jsBridge'
|
|
||||||
import { createAsyncSingleton } from '@lib/asyncSingleton'
|
|
||||||
import { Log } from '@models/Log'
|
|
||||||
import { StreamReader } from './streamer'
|
|
||||||
|
|
||||||
export interface Config {
|
export interface Config {
|
||||||
port: number
|
port: number
|
||||||
@ -99,128 +93,70 @@ export interface Connections {
|
|||||||
rulePayload: string
|
rulePayload: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getExternalControllerConfig () {
|
export class Client {
|
||||||
if (isClashX()) {
|
private axiosClient: AxiosInstance
|
||||||
const info = await jsBridge!.getAPIInfo()
|
constructor(url: string, secret?: string) {
|
||||||
|
this.axiosClient = axios.create({
|
||||||
return {
|
baseURL: url,
|
||||||
hostname: info.host,
|
|
||||||
port: info.port,
|
|
||||||
secret: info.secret,
|
|
||||||
protocol: 'http:'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let url: URL | undefined;
|
|
||||||
{
|
|
||||||
const meta = document.querySelector<HTMLMetaElement>('meta[name="external-controller"]')
|
|
||||||
if (meta?.content?.match(/^https?:/)) {
|
|
||||||
// [protocol]://[secret]@[hostname]:[port]
|
|
||||||
url = new URL(meta.content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const hostname = getSearchParam('host') ?? getLocalStorageItem('externalControllerAddr', url?.hostname ?? '127.0.0.1')
|
|
||||||
const port = getSearchParam('port') ?? getLocalStorageItem('externalControllerPort', url?.port ?? '9090')
|
|
||||||
const secret = getSearchParam('secret') ?? getLocalStorageItem('secret', url?.username ?? '')
|
|
||||||
const protocol = getSearchParam('protocol') ?? hostname === '127.0.0.1' ? 'http:' : (url?.protocol ?? window.location.protocol)
|
|
||||||
|
|
||||||
if (!hostname || !port) {
|
|
||||||
throw new Error('can\'t get hostname or port')
|
|
||||||
}
|
|
||||||
|
|
||||||
return { hostname, port, secret, protocol }
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getInstance = createAsyncSingleton(async () => {
|
|
||||||
const {
|
|
||||||
hostname,
|
|
||||||
port,
|
|
||||||
secret,
|
|
||||||
protocol
|
|
||||||
} = await getExternalControllerConfig()
|
|
||||||
|
|
||||||
return axios.create({
|
|
||||||
baseURL: `${protocol}//${hostname}:${port}`,
|
|
||||||
headers: secret ? { Authorization: `Bearer ${secret}` } : {}
|
headers: secret ? { Authorization: `Bearer ${secret}` } : {}
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
export async function getConfig () {
|
|
||||||
const req = await getInstance()
|
|
||||||
return req.get<Config>('configs')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateConfig (config: Partial<Config>) {
|
getConfig() {
|
||||||
const req = await getInstance()
|
return this.axiosClient.get<Config>('configs')
|
||||||
return req.patch<void>('configs', config)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getRules () {
|
updateConfig(config: Partial<Config>) {
|
||||||
const req = await getInstance()
|
return this.axiosClient.patch<void>('configs', config)
|
||||||
return req.get<Rules>('rules')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateRules () {
|
getRules() {
|
||||||
const req = await getInstance()
|
return this.axiosClient.get<Rules>('rules')
|
||||||
return req.put<void>('rules')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getProxyProviders () {
|
async getProxyProviders () {
|
||||||
const req = await getInstance()
|
const resp = await this.axiosClient.get<ProxyProviders>('providers/proxies', {
|
||||||
return req.get<ProxyProviders>('providers/proxies', {
|
|
||||||
validateStatus(status) {
|
validateStatus(status) {
|
||||||
// compatible old version
|
// compatible old version
|
||||||
return (status >= 200 && status < 300) || status === 404
|
return (status >= 200 && status < 300) || status === 404
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// compatible old version
|
|
||||||
.then(resp => {
|
|
||||||
if (resp.status === 404) {
|
if (resp.status === 404) {
|
||||||
resp.data = { providers: {} }
|
resp.data = { providers: {} }
|
||||||
}
|
}
|
||||||
return resp
|
return resp
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getRuleProviders () {
|
getRuleProviders () {
|
||||||
const req = await getInstance()
|
return this.axiosClient.get<RuleProviders>('providers/rules')
|
||||||
return req.get<RuleProviders>('providers/rules')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateProvider (name: string) {
|
updateProvider (name: string) {
|
||||||
const req = await getInstance()
|
return this.axiosClient.put<void>(`providers/proxies/${encodeURIComponent(name)}`)
|
||||||
return req.put<void>(`providers/proxies/${encodeURIComponent(name)}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateRuleProvider (name: string) {
|
updateRuleProvider (name: string) {
|
||||||
const req = await getInstance()
|
return this.axiosClient.put<void>(`providers/rules/${encodeURIComponent(name)}`)
|
||||||
return req.put<void>(`providers/rules/${encodeURIComponent(name)}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function healthCheckProvider (name: string) {
|
healthCheckProvider (name: string) {
|
||||||
const req = await getInstance()
|
return this.axiosClient.get<void>(`providers/proxies/${encodeURIComponent(name)}/healthcheck`)
|
||||||
return req.get<void>(`providers/proxies/${encodeURIComponent(name)}/healthcheck`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getProxies () {
|
getProxies () {
|
||||||
const req = await getInstance()
|
return this.axiosClient.get<Proxies>('proxies')
|
||||||
return req.get<Proxies>('proxies')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getProxy (name: string) {
|
getProxy (name: string) {
|
||||||
const req = await getInstance()
|
return this.axiosClient.get<Proxy>(`proxies/${encodeURIComponent(name)}`)
|
||||||
return req.get<Proxy>(`proxies/${encodeURIComponent(name)}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getVersion () {
|
getVersion () {
|
||||||
const req = await getInstance()
|
return this.axiosClient.get<{ version: string, premium?: boolean }>('version')
|
||||||
return req.get<{ version: string, premium?: boolean }>('version')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getProxyDelay (name: string) {
|
getProxyDelay (name: string) {
|
||||||
const req = await getInstance()
|
return this.axiosClient.get<{ delay: number }>(`proxies/${encodeURIComponent(name)}/delay`, {
|
||||||
return req.get<{ delay: number }>(`proxies/${encodeURIComponent(name)}/delay`, {
|
|
||||||
params: {
|
params: {
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
url: 'http://www.gstatic.com/generate_204'
|
url: 'http://www.gstatic.com/generate_204'
|
||||||
@ -228,43 +164,19 @@ export async function getProxyDelay (name: string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function closeAllConnections () {
|
closeAllConnections () {
|
||||||
const req = await getInstance()
|
return this.axiosClient.delete('connections')
|
||||||
return req.delete('connections')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function closeConnection (id: string) {
|
closeConnection (id: string) {
|
||||||
const req = await getInstance()
|
return this.axiosClient.delete(`connections/${id}`)
|
||||||
return req.delete(`connections/${id}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getConnections () {
|
getConnections () {
|
||||||
const req = await getInstance()
|
return this.axiosClient.get<Snapshot>('connections')
|
||||||
return req.get<Snapshot>('connections')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function changeProxySelected (name: string, select: string) {
|
changeProxySelected (name: string, select: string) {
|
||||||
const req = await getInstance()
|
return this.axiosClient.put<void>(`proxies/${encodeURIComponent(name)}`, { name: select })
|
||||||
return req.put<void>(`proxies/${encodeURIComponent(name)}`, { name: select })
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getLogsStreamReader = createAsyncSingleton(async function () {
|
|
||||||
const externalController = await getExternalControllerConfig()
|
|
||||||
const { data: config } = await getConfig()
|
|
||||||
const result = await ResultAsync.fromPromise(getVersion(), err => err as AxiosError)
|
|
||||||
const version = result.isErr() ? 'unkonwn version' : result.value.data.version
|
|
||||||
const useWebsocket = !!version || true
|
|
||||||
|
|
||||||
const logUrl = `${externalController.protocol}//${externalController.hostname}:${externalController.port}/logs?level=${config['log-level']}`
|
|
||||||
return new StreamReader<Log>({ url: logUrl, bufferLength: 200, token: externalController.secret, useWebsocket })
|
|
||||||
})
|
|
||||||
|
|
||||||
export const getConnectionStreamReader = createAsyncSingleton(async function () {
|
|
||||||
const externalController = await getExternalControllerConfig()
|
|
||||||
const result = await ResultAsync.fromPromise(getVersion(), err => err as AxiosError)
|
|
||||||
const version = result.isErr() ? 'unkonwn version' : result.value.data.version
|
|
||||||
|
|
||||||
const useWebsocket = !!version || true
|
|
||||||
const logUrl = `${externalController.protocol}//${externalController.hostname}:${externalController.port}/connections`
|
|
||||||
return new StreamReader<Snapshot>({ url: logUrl, bufferLength: 200, token: externalController.secret, useWebsocket })
|
|
||||||
})
|
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
import React from 'react'
|
import React, { Suspense } from 'react'
|
||||||
import { render } from 'react-dom'
|
import { render } from 'react-dom'
|
||||||
import { HashRouter } from 'react-router-dom'
|
import { HashRouter } from 'react-router-dom'
|
||||||
import App from '@containers/App'
|
import App from '@containers/App'
|
||||||
|
import { Loading } from '@components'
|
||||||
import 'virtual:windi.css'
|
import 'virtual:windi.css'
|
||||||
|
|
||||||
export default function renderApp () {
|
export default function renderApp () {
|
||||||
const rootEl = document.getElementById('root')
|
const rootEl = document.getElementById('root')
|
||||||
const AppInstance = (
|
const AppInstance = (
|
||||||
<HashRouter>
|
<HashRouter>
|
||||||
|
<Suspense fallback={<Loading visible />}>
|
||||||
<App />
|
<App />
|
||||||
|
</Suspense>
|
||||||
</HashRouter>
|
</HashRouter>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1 +1,2 @@
|
|||||||
export * from './jotai'
|
export * from './jotai'
|
||||||
|
export * from './request'
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { ResultAsync } from 'neverthrow'
|
import { ResultAsync } from 'neverthrow'
|
||||||
import { AxiosError } from 'axios'
|
import { AxiosError } from 'axios'
|
||||||
import { atom, useAtom } from 'jotai'
|
import { atom, useAtom } from 'jotai'
|
||||||
import { atomWithStorage } from 'jotai/utils'
|
import { atomWithStorage, useUpdateAtom } from 'jotai/utils'
|
||||||
import { atomWithImmer } from 'jotai/immer'
|
import { atomWithImmer } from 'jotai/immer'
|
||||||
import { useCallback, useEffect, useMemo } from 'react'
|
import { useCallback, useEffect, useMemo } from 'react'
|
||||||
import { get } from 'lodash-es'
|
import { get } from 'lodash-es'
|
||||||
@ -12,33 +12,14 @@ import { Language, locales, Lang, getDefaultLanguage } from '@i18n'
|
|||||||
import { useWarpImmerSetter, WritableDraft } from '@lib/jotai'
|
import { useWarpImmerSetter, WritableDraft } from '@lib/jotai'
|
||||||
import * as API from '@lib/request'
|
import * as API from '@lib/request'
|
||||||
import * as Models from '@models'
|
import * as Models from '@models'
|
||||||
import { partition, setLocalStorageItem } from '@lib/helper'
|
import { partition } from '@lib/helper'
|
||||||
import { isClashX, jsBridge } from '@lib/jsBridge'
|
import { isClashX, jsBridge } from '@lib/jsBridge'
|
||||||
|
import { useAPIInfo, useClient } from './request'
|
||||||
|
import { StreamReader } from '@lib/streamer'
|
||||||
|
import { Log } from '@models/Log'
|
||||||
|
import { Snapshot } from '@lib/request'
|
||||||
|
|
||||||
const identity = atom(true)
|
export const identityAtom = atom(true)
|
||||||
|
|
||||||
type AsyncFunction<A, O> = (...args: A[]) => Promise<O>
|
|
||||||
|
|
||||||
export function useIdentity () {
|
|
||||||
const [id, set] = useAtom(identity)
|
|
||||||
|
|
||||||
function wrapFetcher<A, O> (fn: AsyncFunction<A, O>) {
|
|
||||||
return async function (...args: A[]) {
|
|
||||||
const result = await ResultAsync.fromPromise(fn(...args), e => e as AxiosError)
|
|
||||||
if (result.isErr()) {
|
|
||||||
if (result.error.response?.status === 401) {
|
|
||||||
set(false)
|
|
||||||
}
|
|
||||||
throw result.error
|
|
||||||
}
|
|
||||||
|
|
||||||
set(true)
|
|
||||||
return result.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { identity: id, wrapFetcher, set }
|
|
||||||
}
|
|
||||||
|
|
||||||
export const languageAtom = atomWithStorage<Lang | undefined>('language', undefined)
|
export const languageAtom = atomWithStorage<Lang | undefined>('language', undefined)
|
||||||
|
|
||||||
@ -66,10 +47,11 @@ export const version = atom({
|
|||||||
|
|
||||||
export function useVersion () {
|
export function useVersion () {
|
||||||
const [data, set] = useAtom(version)
|
const [data, set] = useAtom(version)
|
||||||
const { set: setIdentity } = useIdentity()
|
const client = useClient()
|
||||||
|
const setIdentity = useUpdateAtom(identityAtom)
|
||||||
|
|
||||||
async function update () {
|
useSWR([client], async function () {
|
||||||
const result = await ResultAsync.fromPromise(API.getVersion(), e => e as AxiosError)
|
const result = await ResultAsync.fromPromise(client.getVersion(), e => e as AxiosError)
|
||||||
setIdentity(result.isOk())
|
setIdentity(result.isOk())
|
||||||
|
|
||||||
set(
|
set(
|
||||||
@ -77,20 +59,21 @@ export function useVersion () {
|
|||||||
? { version: '', premium: false }
|
? { version: '', premium: false }
|
||||||
: { version: result.value.data.version, premium: !!result.value.data.premium }
|
: { version: result.value.data.version, premium: !!result.value.data.premium }
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
|
||||||
return { version: data.version, premium: data.premium, update }
|
return { version: data.version, premium: data.premium }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useRuleProviders () {
|
export function useRuleProviders () {
|
||||||
const [{ premium }] = useAtom(version)
|
const [{ premium }] = useAtom(version)
|
||||||
|
const client = useClient()
|
||||||
|
|
||||||
const { data, mutate } = useSWR('/providers/rule', async () => {
|
const { data, mutate } = useSWR(['/providers/rule', client], async () => {
|
||||||
if (!premium) {
|
if (!premium) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
const ruleProviders = await API.getRuleProviders()
|
const ruleProviders = await client.getRuleProviders()
|
||||||
|
|
||||||
return Object.keys(ruleProviders.data.providers)
|
return Object.keys(ruleProviders.data.providers)
|
||||||
.map<API.RuleProvider>(name => ruleProviders.data.providers[name])
|
.map<API.RuleProvider>(name => ruleProviders.data.providers[name])
|
||||||
@ -117,9 +100,10 @@ export const proxyProvider = atom([] as API.Provider[])
|
|||||||
|
|
||||||
export function useProxyProviders () {
|
export function useProxyProviders () {
|
||||||
const [providers, set] = useAtom(proxyProvider)
|
const [providers, set] = useAtom(proxyProvider)
|
||||||
|
const client = useClient()
|
||||||
|
|
||||||
const { data, mutate } = useSWR('/providers/proxy', async () => {
|
const { data, mutate } = useSWR(['/providers/proxy', client], async () => {
|
||||||
const proxyProviders = await API.getProxyProviders()
|
const proxyProviders = await client.getProxyProviders()
|
||||||
|
|
||||||
return Object.keys(proxyProviders.data.providers)
|
return Object.keys(proxyProviders.data.providers)
|
||||||
.map<API.Provider>(name => proxyProviders.data.providers[name])
|
.map<API.Provider>(name => proxyProviders.data.providers[name])
|
||||||
@ -132,8 +116,10 @@ export function useProxyProviders () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useGeneral () {
|
export function useGeneral () {
|
||||||
const { data, mutate } = useSWR('/config', async () => {
|
const client = useClient()
|
||||||
const resp = await API.getConfig()
|
|
||||||
|
const { data, mutate } = useSWR(['/config', client], async () => {
|
||||||
|
const resp = await client.getConfig()
|
||||||
const data = resp.data
|
const data = resp.data
|
||||||
return {
|
return {
|
||||||
port: data.port,
|
port: data.port,
|
||||||
@ -164,9 +150,10 @@ export const proxies = atomWithImmer({
|
|||||||
export function useProxy () {
|
export function useProxy () {
|
||||||
const [allProxy, rawSet] = useAtom(proxies)
|
const [allProxy, rawSet] = useAtom(proxies)
|
||||||
const set = useWarpImmerSetter(rawSet)
|
const set = useWarpImmerSetter(rawSet)
|
||||||
|
const client = useClient()
|
||||||
|
|
||||||
const { mutate } = useSWR('/proxies', async () => {
|
const { mutate } = useSWR(['/proxies', client], async () => {
|
||||||
const allProxies = await API.getProxies()
|
const allProxies = await client.getProxies()
|
||||||
|
|
||||||
const global = allProxies.data.proxies.GLOBAL as API.Group
|
const global = allProxies.data.proxies.GLOBAL as API.Group
|
||||||
// fix missing name
|
// fix missing name
|
||||||
@ -240,42 +227,64 @@ export function useClashXData () {
|
|||||||
return { data, update: mutate }
|
return { data, update: mutate }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const apiData = atom({
|
|
||||||
hostname: '127.0.0.1',
|
|
||||||
port: '9090',
|
|
||||||
secret: ''
|
|
||||||
})
|
|
||||||
|
|
||||||
export function useAPIInfo () {
|
|
||||||
const [data, set] = useAtom(apiData)
|
|
||||||
|
|
||||||
const fetch = useCallback(async function fetch () {
|
|
||||||
const info = await API.getExternalControllerConfig()
|
|
||||||
set({ ...info })
|
|
||||||
}, [set])
|
|
||||||
|
|
||||||
async function update (info: typeof data) {
|
|
||||||
const { hostname, port, secret } = info
|
|
||||||
setLocalStorageItem('externalControllerAddr', hostname)
|
|
||||||
setLocalStorageItem('externalControllerPort', port)
|
|
||||||
setLocalStorageItem('secret', secret)
|
|
||||||
window.location.reload()
|
|
||||||
}
|
|
||||||
|
|
||||||
return { data, fetch, update }
|
|
||||||
}
|
|
||||||
|
|
||||||
export const rules = atomWithImmer([] as API.Rule[])
|
export const rules = atomWithImmer([] as API.Rule[])
|
||||||
|
|
||||||
export function useRule () {
|
export function useRule () {
|
||||||
const [data, rawSet] = useAtom(rules)
|
const [data, rawSet] = useAtom(rules)
|
||||||
const set = useWarpImmerSetter(rawSet)
|
const set = useWarpImmerSetter(rawSet)
|
||||||
|
const client = useClient()
|
||||||
|
|
||||||
async function update () {
|
async function update () {
|
||||||
const resp = await API.getRules()
|
const resp = await client.getRules()
|
||||||
set(resp.data.rules)
|
set(resp.data.rules)
|
||||||
}
|
}
|
||||||
|
|
||||||
return { rules: data, update }
|
return { rules: data, update }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const logsAtom = atom({
|
||||||
|
key: '',
|
||||||
|
instance: null as StreamReader<Log> | null
|
||||||
|
})
|
||||||
|
|
||||||
|
export function useLogsStreamReader () {
|
||||||
|
const apiInfo = useAPIInfo()
|
||||||
|
const { general } = useGeneral()
|
||||||
|
const version = useVersion()
|
||||||
|
const [item, setItem] = useAtom(logsAtom)
|
||||||
|
|
||||||
|
if (!version.version) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const useWebsocket = !!version.version || true
|
||||||
|
const key = `${apiInfo.protocol}//${apiInfo.hostname}:${apiInfo.port}/logs?level=${general.logLevel ?? ''}&useWebsocket=${useWebsocket}&secret=${apiInfo.secret}`
|
||||||
|
if (item.key === key) {
|
||||||
|
return item.instance!
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldInstance = item.instance
|
||||||
|
|
||||||
|
const logUrl = `${apiInfo.protocol}//${apiInfo.hostname}:${apiInfo.port}/logs?level=${general.logLevel ?? ''}`
|
||||||
|
const instance = new StreamReader<Log>({ url: logUrl, bufferLength: 200, token: apiInfo.secret, useWebsocket })
|
||||||
|
setItem({ key, instance })
|
||||||
|
|
||||||
|
if (oldInstance) {
|
||||||
|
oldInstance.destory()
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useConnectionStreamReader () {
|
||||||
|
const apiInfo = useAPIInfo()
|
||||||
|
const version = useVersion()
|
||||||
|
|
||||||
|
const useWebsocket = !!version.version || true
|
||||||
|
|
||||||
|
const url = `${apiInfo.protocol}//${apiInfo.hostname}:${apiInfo.port}/connections`
|
||||||
|
return useMemo(
|
||||||
|
() => version.version ? new StreamReader<Snapshot>({ url, bufferLength: 200, token: apiInfo.secret, useWebsocket }) : null,
|
||||||
|
[apiInfo.secret, url, useWebsocket, version.version]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
78
src/stores/request.ts
Normal file
78
src/stores/request.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import { atom, useAtom } from "jotai";
|
||||||
|
import { isClashX, jsBridge } from "@lib/jsBridge";
|
||||||
|
import { atomWithStorage, useAtomValue } from "jotai/utils";
|
||||||
|
import { useLocation } from "react-use";
|
||||||
|
import { Client } from "@lib/request";
|
||||||
|
|
||||||
|
const clashxConfigAtom = atom(async () => {
|
||||||
|
if (!isClashX()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const info = await jsBridge!.getAPIInfo()
|
||||||
|
return {
|
||||||
|
hostname: info.host,
|
||||||
|
port: info.port,
|
||||||
|
secret: info.secret,
|
||||||
|
protocol: 'http:'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const localStorageAtom = atomWithStorage<{
|
||||||
|
hostname: string;
|
||||||
|
port: string;
|
||||||
|
secret: string;
|
||||||
|
}[]>('externalControllers', [])
|
||||||
|
|
||||||
|
export function useAPIInfo() {
|
||||||
|
const clashx = useAtomValue(clashxConfigAtom)
|
||||||
|
const location = useLocation()
|
||||||
|
const localStorage = useAtomValue(localStorageAtom)
|
||||||
|
|
||||||
|
if (clashx) {
|
||||||
|
return clashx
|
||||||
|
}
|
||||||
|
|
||||||
|
let url: URL | undefined;
|
||||||
|
{
|
||||||
|
const meta = document.querySelector<HTMLMetaElement>('meta[name="external-controller"]')
|
||||||
|
if (meta?.content?.match(/^https?:/)) {
|
||||||
|
// [protocol]://[secret]@[hostname]:[port]
|
||||||
|
url = new URL(meta.content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const qs = new URLSearchParams(location.search)
|
||||||
|
|
||||||
|
const hostname = qs.get('host') ?? localStorage?.[0]?.hostname ?? url?.hostname ?? '127.0.0.1'
|
||||||
|
const port = qs.get('port') ?? localStorage?.[0]?.port ?? url?.port ?? '9090'
|
||||||
|
const secret = qs.get('secret') ?? localStorage?.[0]?.secret ?? url?.username ?? ''
|
||||||
|
const protocol = qs.get('protocol') ?? hostname === '127.0.0.1' ? 'http:' : (url?.protocol ?? window.location.protocol)
|
||||||
|
|
||||||
|
return { hostname, port, secret, protocol }
|
||||||
|
}
|
||||||
|
|
||||||
|
const clientAtom = atom({
|
||||||
|
key: '',
|
||||||
|
instance: null as Client | null
|
||||||
|
})
|
||||||
|
|
||||||
|
export function useClient() {
|
||||||
|
const {
|
||||||
|
hostname,
|
||||||
|
port,
|
||||||
|
secret,
|
||||||
|
protocol
|
||||||
|
} = useAPIInfo()
|
||||||
|
|
||||||
|
const [item, setItem] = useAtom(clientAtom)
|
||||||
|
const key = `${protocol}//${hostname}:${port}?secret=${secret}`
|
||||||
|
if (item.key === key) {
|
||||||
|
return item.instance!
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = new Client(`${protocol}//${hostname}:${port}`, secret)
|
||||||
|
setItem({ key, instance: client })
|
||||||
|
|
||||||
|
return client
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user