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 Settings from '@containers/Settings'
import SlideBar from '@containers/Sidebar'
import ExternalControllerModal from '@containers/ExternalControllerDrawer'
import { getLogsStreamReader } from '@lib/request'
export interface AppProps extends I18nProps {
@ -42,6 +43,7 @@ export default class App extends React.Component<AppProps, {}> {
)
}
</div>
<ExternalControllerModal />
</div>
)
}

View File

@ -1,48 +1,55 @@
import * as React from 'react'
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 { I18nProps } from '@models'
import { BaseProps, I18nProps } from '@models'
import './style.scss'
interface ExternalControllerModalProps extends I18nProps {
show: boolean
host: string
port: string
secret?: string
onConfirm: (host: string, port: string, secret: string) => void
onCancel: () => void
}
interface ExternalControllerModalProps extends I18nProps, BaseProps {}
interface ExternalControllerModalState {
host: string
hostname: string
port: string
secret: string
}
@inject(...storeKeys)
@observer
class ExternalController extends React.Component<ExternalControllerModalProps, ExternalControllerModalState> {
state = {
host: this.props.host,
port: this.props.port,
secret: this.props.secret || ''
hostname: '',
port: '',
secret: ''
}
private handleOk = () => {
const { onConfirm } = this.props
const { host, port, secret } = this.state
onConfirm(host, port, secret)
const { hostname, port, secret } = this.state
this.props.store.updateAPIInfo({ hostname, 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 () {
const { show, onCancel, t } = this.props
const { host, port, secret } = this.state
const { t } = this.props
const { hostname, port, secret } = this.state
const show = this.props.store.showAPIModal
return (
<Modal
show={show}
title={t('externalControllerSetting.title')}
bodyClassName="external-controller"
onClose={onCancel}
onClose={this.handleCancel}
onOk={this.handleOk}
>
<Alert type="info" inside={true}>
@ -54,8 +61,8 @@ class ExternalController extends React.Component<ExternalControllerModalProps, E
<Input
align="left"
inside={true}
value={host}
onChange={host => this.setState({ host })}
value={hostname}
onChange={hostname => this.setState({ hostname })}
/>
</Col>
</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 { inject, observer } from 'mobx-react'
import { Header, Card, Row, Col, Switch, ButtonSelect, ButtonSelectOptions, Input, Icon } from '@components'
import { ExternalControllerModal } from './components'
import { I18nProps, BaseRouterProps } from '@models'
import { updateConfig } from '@lib/request'
import { setLocalStorageItem, to } from '@lib/helper'
import { to } from '@lib/helper'
import { rootStores, storeKeys } from '@lib/createStore'
import './style.scss'
import { isClashX, jsBridge } from '@lib/jsBridge'
@ -19,10 +18,6 @@ class Settings extends React.Component<SettingProps, {}> {
state = {
socks5ProxyPort: 7891,
httpProxyPort: 7890,
externalControllerHost: '127.0.0.1',
externalControllerPort: '8080',
externalControllerSecret: '',
showEditDrawer: false,
isClashX: false
}
@ -32,18 +27,6 @@ class Settings extends React.Component<SettingProps, {}> {
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) => {
const [, err] = await to(updateConfig({ mode }))
if (!err) {
@ -84,16 +67,12 @@ class Settings extends React.Component<SettingProps, {}> {
this.setState({ setAsSystemProxy: state })
}
async componentWillMount () {
async componentDidMount () {
await rootStores.store.fetchData()
if (isClashX()) {
await rootStores.store.fetchClashXData()
const apiInfo = await jsBridge.getAPIInfo()
this.setState({
isClashX: true,
externalControllerHost: apiInfo.host,
externalControllerPort: apiInfo.port,
externalControllerSecret: apiInfo.secret
isClashX: true
})
}
@ -108,14 +87,15 @@ class Settings extends React.Component<SettingProps, {}> {
const { t, lng, store } = this.props
const {
isClashX,
externalControllerHost,
externalControllerPort,
externalControllerSecret,
showEditDrawer,
socks5ProxyPort,
httpProxyPort
} = this.state
const {
hostname: externalControllerHost,
port: externalControllerPort
} = store.apiInfo
const { allowLan, mode } = store.data.general
const {
startAtLogin,
@ -208,7 +188,7 @@ class Settings extends React.Component<SettingProps, {}> {
</Col>
<Col className="external-controller" span={6} offset={1}>
<span>{`${externalControllerHost}:${externalControllerPort}`}</span>
<span className="modify-btn" onClick={() => this.setState({ showEditDrawer: true })}>
<span className="modify-btn" onClick={() => this.props.store.setShowAPIModal(true)}>
</span>
</Col>
@ -222,15 +202,6 @@ class Settings extends React.Component<SettingProps, {}> {
<p className="version-info">{t('versionString', { version: 'unknown' })}</p>
<span className="check-update-btn">{t('checkUpdate')}</span>
</Card>
<ExternalControllerModal
show={showEditDrawer}
host={externalControllerHost}
port={externalControllerPort}
secret={externalControllerSecret}
onConfirm={this.handleExternalControllerChange}
onCancel={() => this.setState({ showEditDrawer: false })}
/>
</div>
)
}

View File

@ -1,6 +1,6 @@
import axios, { AxiosInstance } from 'axios'
import { Partial, getLocalStorageItem } from '@lib/helper'
import { isClashX } from '@lib/jsBridge'
import { isClashX, jsBridge } from '@lib/jsBridge'
import { rootStores } from '@lib/createStore'
import { StreamReader } from './streamer'
@ -105,18 +105,27 @@ export async function getInstance () {
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
}
export async function getExternalControllerConfig () {
if (isClashX()) {
await rootStores.store.fetchAndParseConfig()
const general = rootStores.store.config.general
const info = await jsBridge.getAPIInfo()
return {
hostname: general.externalControllerAddr,
port: general.externalControllerPort,
secret: general.secret
hostname: info.host,
port: info.port,
secret: info.secret
}
}
@ -138,6 +147,7 @@ export async function getLogsStreamReader () {
const externalController = await getExternalControllerConfig()
const { data: config } = await getConfig()
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
}

View File

@ -69,6 +69,12 @@ export interface ClashXData {
systemProxy: boolean
}
export interface APIInfo {
hostname: string
port: string
secret?: string
}
export interface Data {
general?: {

View File

@ -1,9 +1,9 @@
import { observable, action, runInAction } from 'mobx'
import * as yaml from 'yaml'
import * as Models from '@models'
import { jsBridge } from '@lib/jsBridge'
import { jsBridge, isClashX } from '@lib/jsBridge'
import * as API from '@lib/request'
import { getLocalStorageItem, partition } from '@lib/helper'
import { getLocalStorageItem, setLocalStorageItem, partition } from '@lib/helper'
export class ConfigStore {
@ -22,12 +22,38 @@ export class ConfigStore {
rules: []
}
@observable
apiInfo: Models.APIInfo = {
hostname: '127.0.0.1',
port: '8080',
secret: ''
}
@observable
showAPIModal = false
@observable
clashxData: Models.ClashXData = {
startAtLogin: 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
async fetchData () {
const [{ data: general }, rawProxies, rules] = await Promise.all([API.getConfig(), API.getProxies(), API.getRules()])
@ -156,6 +182,22 @@ export class ConfigStore {
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
async modifyProxyByIndexAndSave (index: number, config: Models.Proxy) {
const { proxy } = this.config