mirror of
https://github.com/woodchen-ink/clash-and-dashboard.git
synced 2025-07-18 14:01:56 +08:00
Feat: support web
This commit is contained in:
parent
6ff226ce3f
commit
91f3c9e348
@ -12,6 +12,7 @@ import Logs from '@containers/Logs'
|
|||||||
import Rules from '@containers/Rules'
|
import Rules from '@containers/Rules'
|
||||||
import Settings from '@containers/Settings'
|
import Settings from '@containers/Settings'
|
||||||
import SlideBar from '@containers/Sidebar'
|
import SlideBar from '@containers/Sidebar'
|
||||||
|
import ExternalControllerModal from '@containers/ExternalControllerDrawer'
|
||||||
import { getLogsStreamReader } from '@lib/request'
|
import { getLogsStreamReader } from '@lib/request'
|
||||||
|
|
||||||
export interface AppProps extends I18nProps {
|
export interface AppProps extends I18nProps {
|
||||||
@ -42,6 +43,7 @@ export default class App extends React.Component<AppProps, {}> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
<ExternalControllerModal />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,48 +1,55 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { translate } from 'react-i18next'
|
import { translate } from 'react-i18next'
|
||||||
|
import { inject, observer } from 'mobx-react'
|
||||||
|
import { storeKeys } from '@lib/createStore'
|
||||||
import { Modal, Input, Row, Col, Alert } from '@components'
|
import { Modal, Input, Row, Col, Alert } from '@components'
|
||||||
import { I18nProps } from '@models'
|
import { BaseProps, I18nProps } from '@models'
|
||||||
import './style.scss'
|
import './style.scss'
|
||||||
|
|
||||||
interface ExternalControllerModalProps extends I18nProps {
|
interface ExternalControllerModalProps extends I18nProps, BaseProps {}
|
||||||
show: boolean
|
|
||||||
host: string
|
|
||||||
port: string
|
|
||||||
secret?: string
|
|
||||||
onConfirm: (host: string, port: string, secret: string) => void
|
|
||||||
onCancel: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ExternalControllerModalState {
|
interface ExternalControllerModalState {
|
||||||
host: string
|
hostname: string
|
||||||
port: string
|
port: string
|
||||||
secret: string
|
secret: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@inject(...storeKeys)
|
||||||
|
@observer
|
||||||
class ExternalController extends React.Component<ExternalControllerModalProps, ExternalControllerModalState> {
|
class ExternalController extends React.Component<ExternalControllerModalProps, ExternalControllerModalState> {
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
host: this.props.host,
|
hostname: '',
|
||||||
port: this.props.port,
|
port: '',
|
||||||
secret: this.props.secret || ''
|
secret: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleOk = () => {
|
private handleOk = () => {
|
||||||
const { onConfirm } = this.props
|
const { hostname, port, secret } = this.state
|
||||||
const { host, port, secret } = this.state
|
this.props.store.updateAPIInfo({ hostname, port, secret })
|
||||||
onConfirm(host, port, secret)
|
}
|
||||||
|
|
||||||
|
private handleCancel = () => {
|
||||||
|
this.props.store.setShowAPIModal(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentWillMount () {
|
||||||
|
await this.props.store.fetchAPIInfo()
|
||||||
|
const info = this.props.store.apiInfo
|
||||||
|
this.setState({ hostname: info.hostname, port: info.port, secret: info.secret })
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { show, onCancel, t } = this.props
|
const { t } = this.props
|
||||||
const { host, port, secret } = this.state
|
const { hostname, port, secret } = this.state
|
||||||
|
const show = this.props.store.showAPIModal
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
show={show}
|
show={show}
|
||||||
title={t('externalControllerSetting.title')}
|
title={t('externalControllerSetting.title')}
|
||||||
bodyClassName="external-controller"
|
bodyClassName="external-controller"
|
||||||
onClose={onCancel}
|
onClose={this.handleCancel}
|
||||||
onOk={this.handleOk}
|
onOk={this.handleOk}
|
||||||
>
|
>
|
||||||
<Alert type="info" inside={true}>
|
<Alert type="info" inside={true}>
|
||||||
@ -54,8 +61,8 @@ class ExternalController extends React.Component<ExternalControllerModalProps, E
|
|||||||
<Input
|
<Input
|
||||||
align="left"
|
align="left"
|
||||||
inside={true}
|
inside={true}
|
||||||
value={host}
|
value={hostname}
|
||||||
onChange={host => this.setState({ host })}
|
onChange={hostname => this.setState({ hostname })}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@ -86,4 +93,4 @@ class ExternalController extends React.Component<ExternalControllerModalProps, E
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ExternalControllerModal = translate(['Settings'])(ExternalController)
|
export default translate(['Settings'])(ExternalController)
|
@ -1 +0,0 @@
|
|||||||
export * from './ExternalControllerDrawer'
|
|
@ -3,10 +3,9 @@ import { translate } from 'react-i18next'
|
|||||||
import { changeLanguage } from 'i18next'
|
import { changeLanguage } from 'i18next'
|
||||||
import { inject, observer } from 'mobx-react'
|
import { inject, observer } from 'mobx-react'
|
||||||
import { Header, Card, Row, Col, Switch, ButtonSelect, ButtonSelectOptions, Input, Icon } from '@components'
|
import { Header, Card, Row, Col, Switch, ButtonSelect, ButtonSelectOptions, Input, Icon } from '@components'
|
||||||
import { ExternalControllerModal } from './components'
|
|
||||||
import { I18nProps, BaseRouterProps } from '@models'
|
import { I18nProps, BaseRouterProps } from '@models'
|
||||||
import { updateConfig } from '@lib/request'
|
import { updateConfig } from '@lib/request'
|
||||||
import { setLocalStorageItem, to } from '@lib/helper'
|
import { to } from '@lib/helper'
|
||||||
import { rootStores, storeKeys } from '@lib/createStore'
|
import { rootStores, storeKeys } from '@lib/createStore'
|
||||||
import './style.scss'
|
import './style.scss'
|
||||||
import { isClashX, jsBridge } from '@lib/jsBridge'
|
import { isClashX, jsBridge } from '@lib/jsBridge'
|
||||||
@ -19,10 +18,6 @@ class Settings extends React.Component<SettingProps, {}> {
|
|||||||
state = {
|
state = {
|
||||||
socks5ProxyPort: 7891,
|
socks5ProxyPort: 7891,
|
||||||
httpProxyPort: 7890,
|
httpProxyPort: 7890,
|
||||||
externalControllerHost: '127.0.0.1',
|
|
||||||
externalControllerPort: '8080',
|
|
||||||
externalControllerSecret: '',
|
|
||||||
showEditDrawer: false,
|
|
||||||
isClashX: false
|
isClashX: false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,18 +27,6 @@ class Settings extends React.Component<SettingProps, {}> {
|
|||||||
changeLanguage(language)
|
changeLanguage(language)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleExternalControllerChange = (host: string, port: string, secret: string) => {
|
|
||||||
setLocalStorageItem('externalControllerAddr', host)
|
|
||||||
setLocalStorageItem('externalControllerPort', port)
|
|
||||||
setLocalStorageItem('secret', secret)
|
|
||||||
this.setState({
|
|
||||||
showEditDrawer: false,
|
|
||||||
externalControllerHost: host,
|
|
||||||
externalControllerPort: port,
|
|
||||||
externalControllerSecret: secret
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleProxyModeChange = async (mode: string) => {
|
handleProxyModeChange = async (mode: string) => {
|
||||||
const [, err] = await to(updateConfig({ mode }))
|
const [, err] = await to(updateConfig({ mode }))
|
||||||
if (!err) {
|
if (!err) {
|
||||||
@ -84,16 +67,12 @@ class Settings extends React.Component<SettingProps, {}> {
|
|||||||
this.setState({ setAsSystemProxy: state })
|
this.setState({ setAsSystemProxy: state })
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentWillMount () {
|
async componentDidMount () {
|
||||||
await rootStores.store.fetchData()
|
await rootStores.store.fetchData()
|
||||||
if (isClashX()) {
|
if (isClashX()) {
|
||||||
await rootStores.store.fetchClashXData()
|
await rootStores.store.fetchClashXData()
|
||||||
const apiInfo = await jsBridge.getAPIInfo()
|
|
||||||
this.setState({
|
this.setState({
|
||||||
isClashX: true,
|
isClashX: true
|
||||||
externalControllerHost: apiInfo.host,
|
|
||||||
externalControllerPort: apiInfo.port,
|
|
||||||
externalControllerSecret: apiInfo.secret
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,14 +87,15 @@ class Settings extends React.Component<SettingProps, {}> {
|
|||||||
const { t, lng, store } = this.props
|
const { t, lng, store } = this.props
|
||||||
const {
|
const {
|
||||||
isClashX,
|
isClashX,
|
||||||
externalControllerHost,
|
|
||||||
externalControllerPort,
|
|
||||||
externalControllerSecret,
|
|
||||||
showEditDrawer,
|
|
||||||
socks5ProxyPort,
|
socks5ProxyPort,
|
||||||
httpProxyPort
|
httpProxyPort
|
||||||
} = this.state
|
} = this.state
|
||||||
|
|
||||||
|
const {
|
||||||
|
hostname: externalControllerHost,
|
||||||
|
port: externalControllerPort
|
||||||
|
} = store.apiInfo
|
||||||
|
|
||||||
const { allowLan, mode } = store.data.general
|
const { allowLan, mode } = store.data.general
|
||||||
const {
|
const {
|
||||||
startAtLogin,
|
startAtLogin,
|
||||||
@ -208,7 +188,7 @@ class Settings extends React.Component<SettingProps, {}> {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col className="external-controller" span={6} offset={1}>
|
<Col className="external-controller" span={6} offset={1}>
|
||||||
<span>{`${externalControllerHost}:${externalControllerPort}`}</span>
|
<span>{`${externalControllerHost}:${externalControllerPort}`}</span>
|
||||||
<span className="modify-btn" onClick={() => this.setState({ showEditDrawer: true })}>
|
<span className="modify-btn" onClick={() => this.props.store.setShowAPIModal(true)}>
|
||||||
修改
|
修改
|
||||||
</span>
|
</span>
|
||||||
</Col>
|
</Col>
|
||||||
@ -222,15 +202,6 @@ class Settings extends React.Component<SettingProps, {}> {
|
|||||||
<p className="version-info">{t('versionString', { version: 'unknown' })}</p>
|
<p className="version-info">{t('versionString', { version: 'unknown' })}</p>
|
||||||
<span className="check-update-btn">{t('checkUpdate')}</span>
|
<span className="check-update-btn">{t('checkUpdate')}</span>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<ExternalControllerModal
|
|
||||||
show={showEditDrawer}
|
|
||||||
host={externalControllerHost}
|
|
||||||
port={externalControllerPort}
|
|
||||||
secret={externalControllerSecret}
|
|
||||||
onConfirm={this.handleExternalControllerChange}
|
|
||||||
onCancel={() => this.setState({ showEditDrawer: false })}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import axios, { AxiosInstance } from 'axios'
|
import axios, { AxiosInstance } from 'axios'
|
||||||
import { Partial, getLocalStorageItem } from '@lib/helper'
|
import { Partial, getLocalStorageItem } from '@lib/helper'
|
||||||
import { isClashX } from '@lib/jsBridge'
|
import { isClashX, jsBridge } from '@lib/jsBridge'
|
||||||
import { rootStores } from '@lib/createStore'
|
import { rootStores } from '@lib/createStore'
|
||||||
import { StreamReader } from './streamer'
|
import { StreamReader } from './streamer'
|
||||||
|
|
||||||
@ -105,18 +105,27 @@ export async function getInstance () {
|
|||||||
headers: secret ? { Authorization: `Bearer ${secret}` } : {}
|
headers: secret ? { Authorization: `Bearer ${secret}` } : {}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
instance.interceptors.response.use(
|
||||||
|
resp => resp,
|
||||||
|
err => {
|
||||||
|
if (!err.response || err.response.status === 401) {
|
||||||
|
rootStores.store.setShowAPIModal(true)
|
||||||
|
}
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getExternalControllerConfig () {
|
export async function getExternalControllerConfig () {
|
||||||
if (isClashX()) {
|
if (isClashX()) {
|
||||||
await rootStores.store.fetchAndParseConfig()
|
const info = await jsBridge.getAPIInfo()
|
||||||
const general = rootStores.store.config.general
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hostname: general.externalControllerAddr,
|
hostname: info.host,
|
||||||
port: general.externalControllerPort,
|
port: info.port,
|
||||||
secret: general.secret
|
secret: info.secret
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,6 +147,7 @@ export async function getLogsStreamReader () {
|
|||||||
const externalController = await getExternalControllerConfig()
|
const externalController = await getExternalControllerConfig()
|
||||||
const { data: config } = await getConfig()
|
const { data: config } = await getConfig()
|
||||||
const logUrl = `http://${externalController.hostname}:${externalController.port}/logs?level=${config['log-level']}`
|
const logUrl = `http://${externalController.hostname}:${externalController.port}/logs?level=${config['log-level']}`
|
||||||
logsStreamReader = new StreamReader({ url: logUrl, bufferLength: 200 })
|
const auth = externalController.secret ? { Authorization: `Bearer ${externalController.secret}` } : {}
|
||||||
|
logsStreamReader = new StreamReader({ url: logUrl, bufferLength: 200, headers: auth })
|
||||||
return logsStreamReader
|
return logsStreamReader
|
||||||
}
|
}
|
||||||
|
@ -69,6 +69,12 @@ export interface ClashXData {
|
|||||||
systemProxy: boolean
|
systemProxy: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface APIInfo {
|
||||||
|
hostname: string
|
||||||
|
port: string
|
||||||
|
secret?: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface Data {
|
export interface Data {
|
||||||
|
|
||||||
general?: {
|
general?: {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { observable, action, runInAction } from 'mobx'
|
import { observable, action, runInAction } from 'mobx'
|
||||||
import * as yaml from 'yaml'
|
import * as yaml from 'yaml'
|
||||||
import * as Models from '@models'
|
import * as Models from '@models'
|
||||||
import { jsBridge } from '@lib/jsBridge'
|
import { jsBridge, isClashX } from '@lib/jsBridge'
|
||||||
import * as API from '@lib/request'
|
import * as API from '@lib/request'
|
||||||
import { getLocalStorageItem, partition } from '@lib/helper'
|
import { getLocalStorageItem, setLocalStorageItem, partition } from '@lib/helper'
|
||||||
|
|
||||||
export class ConfigStore {
|
export class ConfigStore {
|
||||||
|
|
||||||
@ -22,12 +22,38 @@ export class ConfigStore {
|
|||||||
rules: []
|
rules: []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@observable
|
||||||
|
apiInfo: Models.APIInfo = {
|
||||||
|
hostname: '127.0.0.1',
|
||||||
|
port: '8080',
|
||||||
|
secret: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
@observable
|
||||||
|
showAPIModal = false
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
clashxData: Models.ClashXData = {
|
clashxData: Models.ClashXData = {
|
||||||
startAtLogin: false,
|
startAtLogin: false,
|
||||||
systemProxy: false
|
systemProxy: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
async fetchAPIInfo () {
|
||||||
|
if (isClashX()) {
|
||||||
|
const apiInfo = await jsBridge.getAPIInfo()
|
||||||
|
runInAction(() => {
|
||||||
|
this.apiInfo = { hostname: apiInfo.host, port: apiInfo.port, secret: apiInfo.secret }
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const info = await API.getExternalControllerConfig()
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.apiInfo = { ...info }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
async fetchData () {
|
async fetchData () {
|
||||||
const [{ data: general }, rawProxies, rules] = await Promise.all([API.getConfig(), API.getProxies(), API.getRules()])
|
const [{ data: general }, rawProxies, rules] = await Promise.all([API.getConfig(), API.getProxies(), API.getRules()])
|
||||||
@ -156,6 +182,22 @@ export class ConfigStore {
|
|||||||
jsBridge.writeConfigWithString(data)
|
jsBridge.writeConfigWithString(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
async updateAPIInfo (info: Models.APIInfo) {
|
||||||
|
const { hostname, port, secret } = info
|
||||||
|
setLocalStorageItem('externalControllerAddr', hostname)
|
||||||
|
setLocalStorageItem('externalControllerPort', port)
|
||||||
|
setLocalStorageItem('secret', secret)
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
setShowAPIModal (visible: boolean) {
|
||||||
|
runInAction(() => {
|
||||||
|
this.showAPIModal = visible
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
async modifyProxyByIndexAndSave (index: number, config: Models.Proxy) {
|
async modifyProxyByIndexAndSave (index: number, config: Models.Proxy) {
|
||||||
const { proxy } = this.config
|
const { proxy } = this.config
|
||||||
|
Loading…
x
Reference in New Issue
Block a user