Feat: support web

This commit is contained in:
Dreamacro 2018-12-29 17:21:50 +08:00
parent 6ff226ce3f
commit 91f3c9e348
8 changed files with 107 additions and 70 deletions

View File

@ -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>
) )
} }

View File

@ -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)

View File

@ -1 +0,0 @@
export * from './ExternalControllerDrawer'

View File

@ -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>
) )
} }

View File

@ -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
} }

View File

@ -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?: {

View File

@ -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