mirror of
https://github.com/woodchen-ink/clash-and-dashboard.git
synced 2025-07-18 22:11:56 +08:00
Add: proxy modify
This commit is contained in:
parent
49d256a51f
commit
aec5b72987
@ -9,6 +9,7 @@ interface InputProps extends BaseComponentProps {
|
|||||||
align?: 'left' | 'center' | 'right'
|
align?: 'left' | 'center' | 'right'
|
||||||
inside?: boolean
|
inside?: boolean
|
||||||
autoFocus?: boolean
|
autoFocus?: boolean
|
||||||
|
type?: string
|
||||||
onChange?: (value: string, event?: React.ChangeEvent<HTMLInputElement>) => void
|
onChange?: (value: string, event?: React.ChangeEvent<HTMLInputElement>) => void
|
||||||
onBlur?: (event?: React.FocusEvent<HTMLInputElement>) => void
|
onBlur?: (event?: React.FocusEvent<HTMLInputElement>) => void
|
||||||
}
|
}
|
||||||
@ -20,6 +21,7 @@ export class Input extends React.Component<InputProps, {}> {
|
|||||||
align: 'center',
|
align: 'center',
|
||||||
inside: false,
|
inside: false,
|
||||||
autoFocus: false,
|
autoFocus: false,
|
||||||
|
type: 'text',
|
||||||
onChange: () => {},
|
onChange: () => {},
|
||||||
onBlur: () => {}
|
onBlur: () => {}
|
||||||
}
|
}
|
||||||
@ -32,6 +34,7 @@ export class Input extends React.Component<InputProps, {}> {
|
|||||||
align,
|
align,
|
||||||
inside,
|
inside,
|
||||||
autoFocus,
|
autoFocus,
|
||||||
|
type,
|
||||||
onChange,
|
onChange,
|
||||||
onBlur
|
onBlur
|
||||||
} = this.props
|
} = this.props
|
||||||
@ -42,6 +45,7 @@ export class Input extends React.Component<InputProps, {}> {
|
|||||||
style={style}
|
style={style}
|
||||||
value={value}
|
value={value}
|
||||||
autoFocus={autoFocus}
|
autoFocus={autoFocus}
|
||||||
|
type={type}
|
||||||
onChange={event => onChange(event.target.value, event)}
|
onChange={event => onChange(event.target.value, event)}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
/>
|
/>
|
||||||
|
@ -49,6 +49,8 @@ export class Modal extends React.Component<ModalProps, {}> {
|
|||||||
|
|
||||||
$modal = React.createRef<HTMLDivElement>()
|
$modal = React.createRef<HTMLDivElement>()
|
||||||
|
|
||||||
|
$mask = React.createRef<HTMLDivElement>()
|
||||||
|
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
@ -64,9 +66,8 @@ export class Modal extends React.Component<ModalProps, {}> {
|
|||||||
|
|
||||||
private handleMaskClick = (e) => {
|
private handleMaskClick = (e) => {
|
||||||
const { onClose } = this.props
|
const { onClose } = this.props
|
||||||
const el = this.$modal.current
|
|
||||||
|
|
||||||
if (el && !el.contains(e.target)) {
|
if (e.target === this.$mask) {
|
||||||
onClose()
|
onClose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -76,6 +77,7 @@ export class Modal extends React.Component<ModalProps, {}> {
|
|||||||
const modal = (
|
const modal = (
|
||||||
<div
|
<div
|
||||||
className={classnames('modal-mask', { 'modal-show': show })}
|
className={classnames('modal-mask', { 'modal-show': show })}
|
||||||
|
ref={this.$mask}
|
||||||
onClick={this.handleMaskClick}
|
onClick={this.handleMaskClick}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
@ -44,12 +44,7 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
|||||||
super(props)
|
super(props)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate () {
|
|
||||||
console.log('update')
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
|
|
||||||
document.addEventListener('click', this.handleGlobalClick, true)
|
document.addEventListener('click', this.handleGlobalClick, true)
|
||||||
this.setState({ dropdownListStyles: this.calculateAttachmentPosition() })
|
this.setState({ dropdownListStyles: this.calculateAttachmentPosition() })
|
||||||
}
|
}
|
||||||
@ -60,6 +55,7 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
|||||||
}
|
}
|
||||||
document.removeEventListener('click', this.handleGlobalClick, true)
|
document.removeEventListener('click', this.handleGlobalClick, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate (nextProps, nextState) {
|
shouldComponentUpdate (nextProps, nextState) {
|
||||||
if (nextProps.value === this.props.value && nextState.showDropDownList === this.state.showDropDownList) {
|
if (nextProps.value === this.props.value && nextState.showDropDownList === this.state.showDropDownList) {
|
||||||
return false
|
return false
|
||||||
@ -96,7 +92,7 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
|||||||
return {
|
return {
|
||||||
top: Math.floor(targetRectInfo.top) - 10,
|
top: Math.floor(targetRectInfo.top) - 10,
|
||||||
left: Math.floor(targetRectInfo.left) - 10,
|
left: Math.floor(targetRectInfo.left) - 10,
|
||||||
width: Math.floor(targetRectInfo.width)
|
width: Math.floor(targetRectInfo.width) + 10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,7 +159,7 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
|||||||
ref={this.$target}
|
ref={this.$target}
|
||||||
onClick={this.handleShowDropList}
|
onClick={this.handleShowDropList}
|
||||||
>
|
>
|
||||||
{matchChild.props.children}
|
{matchChild && matchChild.props && matchChild.props.children}
|
||||||
<Icon type="triangle-down" />
|
<Icon type="triangle-down" />
|
||||||
</div>
|
</div>
|
||||||
{hasCreateDropList && createPortal(dropDownList, this.$container)}
|
{hasCreateDropList && createPortal(dropDownList, this.$container)}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
@import '~@styles/variables';
|
@import '~@styles/variables';
|
||||||
|
|
||||||
.select {
|
.select {
|
||||||
flex: 1;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
|
@ -0,0 +1,129 @@
|
|||||||
|
import * as React from 'react'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
import { Row, Col, Input, Icon, Select, Option } from '@components'
|
||||||
|
import { noop } from '@lib/helper'
|
||||||
|
|
||||||
|
// type selector
|
||||||
|
export function ProxyTypeSelector ({ types, label, value, onSelect = noop }: {
|
||||||
|
types: { [key: string]: string },
|
||||||
|
label: string,
|
||||||
|
value: string,
|
||||||
|
onSelect?: (type: string) => void
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Row gutter={24} className="proxy-editor-row">
|
||||||
|
<Col span={6} className="proxy-editor-label">{label}</Col>
|
||||||
|
<Col span={18}>
|
||||||
|
<Select value={value} onSelect={onSelect}>
|
||||||
|
{
|
||||||
|
Object.keys(types)
|
||||||
|
.map(typeName => {
|
||||||
|
const type = types[typeName]
|
||||||
|
return (
|
||||||
|
<Option value={type} key={type}>{typeName}</Option>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// color selector
|
||||||
|
export function ProxyColorSelector ({ colors, value, onSelect = noop }: {
|
||||||
|
colors: string[],
|
||||||
|
value: string,
|
||||||
|
onSelect?: (color: string) => void
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Row gutter={24} style={{ padding: '12px 0' }}>
|
||||||
|
<div className="proxy-editor-color-selector">
|
||||||
|
{
|
||||||
|
colors.map(color => (
|
||||||
|
<span
|
||||||
|
className={classnames('color-item', {
|
||||||
|
'color-item-active': value === color
|
||||||
|
})}
|
||||||
|
key={color}
|
||||||
|
style={{ background: color }}
|
||||||
|
onClick={() => onSelect(color)}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// input form
|
||||||
|
export function ProxyInputForm ({ label, value, onChange = noop }: {
|
||||||
|
label: string,
|
||||||
|
value: string,
|
||||||
|
onChange?: (value: string) => void
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Row gutter={24} className="proxy-editor-row">
|
||||||
|
<Col span={6} className="proxy-editor-label">{label}</Col>
|
||||||
|
<Col span={18}>
|
||||||
|
<Input value={value} onChange={onChange} align="left"/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// password form
|
||||||
|
export class ProxyPasswordForm extends React.Component<{
|
||||||
|
label: string,
|
||||||
|
value: string,
|
||||||
|
onChange?: (value: string) => void
|
||||||
|
}, { showPassword: boolean }> {
|
||||||
|
|
||||||
|
state = {
|
||||||
|
showPassword: false
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { label, value, onChange } = this.props
|
||||||
|
const { showPassword } = this.state
|
||||||
|
const type = showPassword ? 'text' : 'password'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row gutter={24} className="proxy-editor-row">
|
||||||
|
<Col span={6} className="proxy-editor-label">{label}</Col>
|
||||||
|
<Col span={18} className="proxy-editor-value">
|
||||||
|
<Input type={type} value={value} onChange={onChange} align="left"/>
|
||||||
|
<Icon
|
||||||
|
className="proxy-editor-passsword-icon"
|
||||||
|
type={showPassword ? 'hide' : 'show'}
|
||||||
|
size={20}
|
||||||
|
onClick={() => this.setState({ showPassword: !showPassword })}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cipher selector
|
||||||
|
export function ProxyCipherSelector ({ ciphers, label, value, onSelect = noop }: {
|
||||||
|
ciphers: string[],
|
||||||
|
label: string,
|
||||||
|
value: string,
|
||||||
|
onSelect?: (type: string) => void
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Row gutter={24} className="proxy-editor-row">
|
||||||
|
<Col span={6} className="proxy-editor-label">{label}</Col>
|
||||||
|
<Col span={18}>
|
||||||
|
<Select value={value} onSelect={onSelect}>
|
||||||
|
{
|
||||||
|
ciphers.map(cipher => (
|
||||||
|
<Option value={cipher} key={cipher}>{cipher}</Option>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
}
|
@ -1,11 +1,27 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { translate } from 'react-i18next'
|
import { translate } from 'react-i18next'
|
||||||
import classnames from 'classnames'
|
import { Modal } from '@components'
|
||||||
import { BaseComponentProps, Proxy as IProxy, I18nProps, TagColors } from '@models'
|
|
||||||
import { Modal, Row, Col } from '@components'
|
|
||||||
import { getLocalStorageItem, setLocalStorageItem } from '@lib/helper'
|
import { getLocalStorageItem, setLocalStorageItem } from '@lib/helper'
|
||||||
import './style.scss'
|
import './style.scss'
|
||||||
|
|
||||||
|
import {
|
||||||
|
BaseComponentProps,
|
||||||
|
Proxy as IProxy,
|
||||||
|
SsProxyConfigList, VmessProxyConfigList, Socks5ProxyConfigList,
|
||||||
|
I18nProps,
|
||||||
|
TagColors,
|
||||||
|
ProxyType,
|
||||||
|
SsCipher, VmessCipher, pickCipherWithAlias
|
||||||
|
} from '@models'
|
||||||
|
|
||||||
|
import {
|
||||||
|
ProxyInputForm,
|
||||||
|
ProxyColorSelector,
|
||||||
|
ProxyTypeSelector,
|
||||||
|
ProxyPasswordForm,
|
||||||
|
ProxyCipherSelector
|
||||||
|
} from './FormItems'
|
||||||
|
|
||||||
interface ModifyProxyDialogProps extends BaseComponentProps, I18nProps {
|
interface ModifyProxyDialogProps extends BaseComponentProps, I18nProps {
|
||||||
config: IProxy
|
config: IProxy
|
||||||
onOk?: (config: IProxy) => void
|
onOk?: (config: IProxy) => void
|
||||||
@ -40,9 +56,151 @@ class RawDialog extends React.Component<ModifyProxyDialogProps, ModifyProxyDialo
|
|||||||
onOk(config)
|
onOk(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleConfigChange = (key: string, value: any) => {
|
||||||
|
console.log(key, value)
|
||||||
|
const { config } = this.state
|
||||||
|
this.setState({ config: { ...config, [key]: value } })
|
||||||
|
}
|
||||||
|
|
||||||
|
getCipherFromType (type) {
|
||||||
|
switch (type) {
|
||||||
|
case 'ss':
|
||||||
|
return SsCipher
|
||||||
|
case 'vmess':
|
||||||
|
return VmessCipher
|
||||||
|
default:
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getConfigListFromType (type) {
|
||||||
|
switch (type) {
|
||||||
|
case 'ss':
|
||||||
|
return SsProxyConfigList
|
||||||
|
case 'vmess':
|
||||||
|
return VmessProxyConfigList
|
||||||
|
case 'socks5':
|
||||||
|
return Socks5ProxyConfigList
|
||||||
|
default:
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderFormItem (key) {
|
||||||
|
const { t } = this.props
|
||||||
|
const { config } = this.state
|
||||||
|
|
||||||
|
switch (key) {
|
||||||
|
case 'type':
|
||||||
|
return (
|
||||||
|
<ProxyTypeSelector
|
||||||
|
key={key}
|
||||||
|
types={ProxyType}
|
||||||
|
label={t('editDialog.type')}
|
||||||
|
value={config.type}
|
||||||
|
onSelect={value => this.handleConfigChange('type', value)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
case 'name':
|
||||||
|
return (
|
||||||
|
<ProxyInputForm
|
||||||
|
key={key}
|
||||||
|
label={t('editDialog.name')}
|
||||||
|
value={config.name}
|
||||||
|
onChange={value => this.handleConfigChange('name', value)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
case 'server':
|
||||||
|
return (
|
||||||
|
<ProxyInputForm
|
||||||
|
key={key}
|
||||||
|
label={t('editDialog.server')}
|
||||||
|
value={config.server}
|
||||||
|
onChange={value => this.handleConfigChange('server', value)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
case 'port':
|
||||||
|
return (
|
||||||
|
<ProxyInputForm
|
||||||
|
key={key}
|
||||||
|
label={t('editDialog.port')}
|
||||||
|
value={config.port ? config.port.toString() : ''}
|
||||||
|
onChange={value => this.handleConfigChange('port', +value)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
case 'password':
|
||||||
|
return (
|
||||||
|
<ProxyPasswordForm
|
||||||
|
key={key}
|
||||||
|
label={t('editDialog.password')}
|
||||||
|
value={config.password}
|
||||||
|
onChange={value => this.handleConfigChange('password', value)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
case 'cipher':
|
||||||
|
return (
|
||||||
|
<ProxyCipherSelector
|
||||||
|
key={key}
|
||||||
|
ciphers={this.getCipherFromType(config.type)}
|
||||||
|
label={t('editDialog.cipher')}
|
||||||
|
value={pickCipherWithAlias(config.cipher)}
|
||||||
|
onSelect={value => this.handleConfigChange('cipher', value)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
case 'obfs':
|
||||||
|
return (
|
||||||
|
<ProxyInputForm
|
||||||
|
label={t('editDialog.obfs')}
|
||||||
|
value={config.obfs}
|
||||||
|
onChange={value => this.handleConfigChange('obfs', value)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
case 'obfs-host':
|
||||||
|
return (
|
||||||
|
<ProxyInputForm
|
||||||
|
key={key}
|
||||||
|
label={t('editDialog.obfs-host')}
|
||||||
|
value={config['obfs-host']}
|
||||||
|
onChange={value => this.handleConfigChange('obfs-host', value)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
case 'uuid':
|
||||||
|
return (
|
||||||
|
<ProxyInputForm
|
||||||
|
key={key}
|
||||||
|
label={t('editDialog.uuid')}
|
||||||
|
value={config.uuid}
|
||||||
|
onChange={value => this.handleConfigChange('uuid', value)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
case 'alterid':
|
||||||
|
return (
|
||||||
|
<ProxyInputForm
|
||||||
|
key={key}
|
||||||
|
label={t('editDialog.alterid')}
|
||||||
|
value={config.alterid ? config.alterid.toString() : ''}
|
||||||
|
onChange={value => this.handleConfigChange('alterid', +value)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
case 'tls':
|
||||||
|
return (
|
||||||
|
<ProxyInputForm
|
||||||
|
key={key}
|
||||||
|
label={t('editDialog.tls')}
|
||||||
|
value={config.tls ? config.tls.toString() : ''}
|
||||||
|
onChange={value => this.handleConfigChange('tls', !!value)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { onCancel, t } = this.props
|
const { onCancel, t } = this.props
|
||||||
const { currentColor } = this.state
|
const { currentColor, config } = this.state
|
||||||
|
const { type } = config
|
||||||
|
const configList = this.getConfigListFromType(type)
|
||||||
|
|
||||||
return <Modal
|
return <Modal
|
||||||
className="proxy-editor"
|
className="proxy-editor"
|
||||||
@ -50,25 +208,14 @@ class RawDialog extends React.Component<ModifyProxyDialogProps, ModifyProxyDialo
|
|||||||
onOk={this.handleOk}
|
onOk={this.handleOk}
|
||||||
onClose={onCancel}
|
onClose={onCancel}
|
||||||
>
|
>
|
||||||
<Row gutter={24} style={{ padding: '12px 0' }}>
|
<ProxyColorSelector
|
||||||
<Col span={6} style={{ paddingLeft: 0 }}>{t('editDialog.color')}</Col>
|
colors={TagColors}
|
||||||
<Col span={18}>
|
value={currentColor}
|
||||||
<div className="proxy-editor-color-selector">
|
onSelect={color => this.setState({ currentColor: color })}
|
||||||
{
|
|
||||||
TagColors.map(color => (
|
|
||||||
<span
|
|
||||||
className={classnames('color-item', {
|
|
||||||
'color-item-active': currentColor === color
|
|
||||||
})}
|
|
||||||
key={color}
|
|
||||||
style={{ background: color }}
|
|
||||||
onClick={() => this.setState({ currentColor: color })}
|
|
||||||
/>
|
/>
|
||||||
))
|
{
|
||||||
|
configList.map(c => this.renderFormItem(c))
|
||||||
}
|
}
|
||||||
</div>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,35 @@
|
|||||||
@import '~@styles/variables';
|
@import '~@styles/variables';
|
||||||
|
|
||||||
.proxy-editor {
|
.proxy-editor {
|
||||||
|
.proxy-editor-row {
|
||||||
|
padding: 5px 0;
|
||||||
|
|
||||||
|
.proxy-editor-label {
|
||||||
|
padding-left: 0;
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-editor-value {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.proxy-editor-passsword-icon {
|
||||||
|
position: absolute;
|
||||||
|
right: 15px;
|
||||||
|
top: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: $color-primary-darken;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.proxy-editor-color-selector {
|
.proxy-editor-color-selector {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.color-item {
|
.color-item {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-right: 10px;
|
margin-right: 20px;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
@ -13,6 +13,7 @@ interface ProxiesProps extends BaseRouterProps, I18nProps {}
|
|||||||
interface ProxiesState {
|
interface ProxiesState {
|
||||||
showModifyProxyDialog: boolean
|
showModifyProxyDialog: boolean
|
||||||
activeConfig?: IProxy
|
activeConfig?: IProxy
|
||||||
|
activeConfigIndex?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@inject(...storeKeys)
|
@inject(...storeKeys)
|
||||||
@ -21,13 +22,19 @@ class Proxies extends React.Component<ProxiesProps, ProxiesState> {
|
|||||||
|
|
||||||
state = {
|
state = {
|
||||||
showModifyProxyDialog: false,
|
showModifyProxyDialog: false,
|
||||||
activeConfig: null
|
activeConfig: null,
|
||||||
|
activeConfigIndex: -1
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.props.config.fetchAndParseConfig()
|
this.props.config.fetchAndParseConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleConfigApply = async (config: IProxy) => {
|
||||||
|
await this.props.config.modifyProxyByIndexAndSave(this.state.activeConfigIndex, config)
|
||||||
|
this.setState({ showModifyProxyDialog: false, activeConfig: null })
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { t, config } = this.props
|
const { t, config } = this.props
|
||||||
const { showModifyProxyDialog, activeConfig } = this.state
|
const { showModifyProxyDialog, activeConfig } = this.state
|
||||||
@ -46,7 +53,8 @@ class Proxies extends React.Component<ProxiesProps, ProxiesState> {
|
|||||||
<li key={index}>
|
<li key={index}>
|
||||||
<Proxy config={p} onEdit={() => this.setState({
|
<Proxy config={p} onEdit={() => this.setState({
|
||||||
showModifyProxyDialog: true,
|
showModifyProxyDialog: true,
|
||||||
activeConfig: p
|
activeConfig: p,
|
||||||
|
activeConfigIndex: index
|
||||||
})} />
|
})} />
|
||||||
</li>
|
</li>
|
||||||
))
|
))
|
||||||
@ -61,11 +69,8 @@ class Proxies extends React.Component<ProxiesProps, ProxiesState> {
|
|||||||
{
|
{
|
||||||
showModifyProxyDialog && <ModifyProxyDialog
|
showModifyProxyDialog && <ModifyProxyDialog
|
||||||
config={activeConfig}
|
config={activeConfig}
|
||||||
onOk={config => {
|
onOk={this.handleConfigApply}
|
||||||
console.log(config)
|
onCancel={() => this.setState({ showModifyProxyDialog: false, activeConfig: null, activeConfigIndex: -1 })}
|
||||||
this.setState({ showModifyProxyDialog: false, activeConfig: null })
|
|
||||||
}}
|
|
||||||
onCancel={() => this.setState({ showModifyProxyDialog: false, activeConfig: null })}
|
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@ -144,7 +144,12 @@ class Rules extends React.Component<RulesProps, RulesState> {
|
|||||||
isFinal
|
isFinal
|
||||||
? rule.type
|
? rule.type
|
||||||
: (
|
: (
|
||||||
<Select key={index} value={rule.type} onSelect={type => this.handleModifyType(index, type)}>
|
<Select
|
||||||
|
key={index}
|
||||||
|
value={rule.type}
|
||||||
|
onSelect={type => this.handleModifyType(index, type)}
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
>
|
||||||
{
|
{
|
||||||
Object.keys(RuleType)
|
Object.keys(RuleType)
|
||||||
.filter(type => type !== 'FINAL')
|
.filter(type => type !== 'FINAL')
|
||||||
|
@ -66,7 +66,7 @@ export async function getProxyDelay (name: string) {
|
|||||||
const req = await getInstance()
|
const req = await getInstance()
|
||||||
return req.get<{ delay: number }>(`proxies/${name}/delay`, {
|
return req.get<{ delay: number }>(`proxies/${name}/delay`, {
|
||||||
params: {
|
params: {
|
||||||
timeout: 2000,
|
timeout: 20000,
|
||||||
url: 'http://www.gstatic.com/generate_204'
|
url: 'http://www.gstatic.com/generate_204'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
57
src/models/Cipher.ts
Normal file
57
src/models/Cipher.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* ss ciphers which clash supported
|
||||||
|
* @see https://github.com/Dreamacro/go-shadowsocks2/blob/master/core/cipher.go
|
||||||
|
*/
|
||||||
|
export const SsCipher = [
|
||||||
|
// AEAD ciphers
|
||||||
|
'AEAD_AES_128_GCM',
|
||||||
|
'AEAD_AES_192_GCM',
|
||||||
|
'AEAD_AES_256_GCM',
|
||||||
|
'AEAD_CHACHA20_POLY1305',
|
||||||
|
'AEAD_XCHACHA20_POLY1305',
|
||||||
|
|
||||||
|
// stream ciphers
|
||||||
|
'RC4-MD5',
|
||||||
|
'AES-128-CTR',
|
||||||
|
'AES-192-CTR',
|
||||||
|
'AES-256-CTR',
|
||||||
|
'AES-128-CFB',
|
||||||
|
'AES-192-CFB',
|
||||||
|
'AES-256-CFB',
|
||||||
|
'CHACHA20',
|
||||||
|
'CHACHA20-IETF',
|
||||||
|
'XCHACHA20'
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* vmess ciphers which clash supported
|
||||||
|
* @see https://github.com/Dreamacro/clash/blob/master/component/vmess/vmess.go#L34
|
||||||
|
*/
|
||||||
|
export const VmessCipher = [
|
||||||
|
'auto',
|
||||||
|
'none',
|
||||||
|
'aes-128-gcm',
|
||||||
|
'chacha20-poly1305'
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pickCipherWithAlias returns a cipher of the given name.
|
||||||
|
*/
|
||||||
|
export function pickCipherWithAlias (c: string) {
|
||||||
|
const cipher = c.toUpperCase()
|
||||||
|
|
||||||
|
switch (cipher) {
|
||||||
|
case 'CHACHA20-IETF-POLY1305':
|
||||||
|
return 'AEAD_CHACHA20_POLY1305'
|
||||||
|
case 'XCHACHA20-IETF-POLY1305':
|
||||||
|
return 'AEAD_XCHACHA20_POLY1305'
|
||||||
|
case 'AES-128-GCM':
|
||||||
|
return 'AEAD_AES_128_GCM'
|
||||||
|
case 'AES-196-GCM':
|
||||||
|
return 'AEAD_AES_196_GCM'
|
||||||
|
case 'AES-256-GCM':
|
||||||
|
return 'AEAD_AES_256_GCM'
|
||||||
|
}
|
||||||
|
|
||||||
|
return SsCipher.find(c => c === cipher) || ''
|
||||||
|
}
|
@ -2,14 +2,17 @@
|
|||||||
* proxy config interface
|
* proxy config interface
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export enum ProxyType {
|
export const ProxyType = {
|
||||||
Shadowsocks = 'Shadowsocks',
|
Shadowsocks: 'ss',
|
||||||
Vmess = 'Vmess',
|
Vmess: 'vmess',
|
||||||
Socks5 = 'Socks5'
|
Socks5: 'socks5'
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Proxy = ShadowsocksProxy | VmessProxy | Socks5Proxy
|
export type Proxy = ShadowsocksProxy & VmessProxy & Socks5Proxy
|
||||||
|
|
||||||
|
export const SsProxyConfigList = [
|
||||||
|
'name', 'type', 'server', 'port', 'cipher', 'password', 'obfs', 'obfs-host'
|
||||||
|
]
|
||||||
export interface ShadowsocksProxy {
|
export interface ShadowsocksProxy {
|
||||||
name?: string
|
name?: string
|
||||||
|
|
||||||
@ -29,6 +32,9 @@ export interface ShadowsocksProxy {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const VmessProxyConfigList = [
|
||||||
|
'name', 'type', 'server', 'port', 'uuid', 'alterid', 'cipher', 'tls'
|
||||||
|
]
|
||||||
export interface VmessProxy {
|
export interface VmessProxy {
|
||||||
name?: string
|
name?: string
|
||||||
|
|
||||||
@ -48,6 +54,7 @@ export interface VmessProxy {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const Socks5ProxyConfigList = ['name', 'type', 'server', 'port']
|
||||||
export interface Socks5Proxy {
|
export interface Socks5Proxy {
|
||||||
name?: string
|
name?: string
|
||||||
|
|
||||||
@ -59,7 +66,7 @@ export interface Socks5Proxy {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProxyGroup = SelectProxyGroup | UrlTestProxyGroup | FallbackProxyGroup
|
export type ProxyGroup = SelectProxyGroup & UrlTestProxyGroup & FallbackProxyGroup
|
||||||
|
|
||||||
export interface SelectProxyGroup {
|
export interface SelectProxyGroup {
|
||||||
name?: string
|
name?: string
|
||||||
|
@ -4,3 +4,4 @@ export * from './Proxy'
|
|||||||
export * from './Rule'
|
export * from './Rule'
|
||||||
export * from './I18n'
|
export * from './I18n'
|
||||||
export * from './TagColors'
|
export * from './TagColors'
|
||||||
|
export * from './Cipher'
|
||||||
|
@ -81,6 +81,7 @@ export class ConfigStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
async updateConfig () {
|
async updateConfig () {
|
||||||
const { general, proxy, proxyGroup, rules } = this.config
|
const { general, proxy, proxyGroup, rules } = this.config
|
||||||
@ -103,4 +104,32 @@ export class ConfigStore {
|
|||||||
// console.log(data)
|
// console.log(data)
|
||||||
jsBridge.writeConfigWithString(data)
|
jsBridge.writeConfigWithString(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
async modifyProxyByIndexAndSave (index: number, config: Models.Proxy) {
|
||||||
|
const { proxy } = this.config
|
||||||
|
const fomatedConfig: Models.Proxy = {}
|
||||||
|
const { type } = config
|
||||||
|
let configList: string[] = []
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'ss':
|
||||||
|
configList = Models.SsProxyConfigList
|
||||||
|
break
|
||||||
|
case 'vmess':
|
||||||
|
configList = Models.VmessProxyConfigList
|
||||||
|
break
|
||||||
|
case 'socks5':
|
||||||
|
configList = Models.Socks5ProxyConfigList
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const configKey of configList) {
|
||||||
|
fomatedConfig[configKey] = config[configKey]
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy[index] = fomatedConfig
|
||||||
|
await this.updateConfig()
|
||||||
|
await this.fetchAndParseConfig()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "clash-iconfont";
|
font-family: "clash-iconfont";
|
||||||
src: url('//at.alicdn.com/t/font_841708_7ge2gqse7qv.ttf') format('truetype');
|
src: url('//at.alicdn.com/t/font_841708_e9dax11p22i.ttf') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.clash-iconfont {
|
.clash-iconfont {
|
||||||
@ -39,3 +39,7 @@
|
|||||||
.icon-info-o::before { content: "\e60c"; }
|
.icon-info-o::before { content: "\e60c"; }
|
||||||
|
|
||||||
.icon-setting::before { content: "\e60d"; }
|
.icon-setting::before { content: "\e60d"; }
|
||||||
|
|
||||||
|
.icon-show::before { content: "\e60e"; }
|
||||||
|
|
||||||
|
.icon-hide::before { content: "\e60f"; }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user