mirror of
https://github.com/woodchen-ink/clash-and-dashboard.git
synced 2025-07-18 22:11:56 +08:00
Add: external controller setting dialog
This commit is contained in:
parent
b60ebfe75f
commit
44a1fdf449
16
package-lock.json
generated
16
package-lock.json
generated
@ -1762,7 +1762,7 @@
|
|||||||
},
|
},
|
||||||
"browserify-aes": {
|
"browserify-aes": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
|
"resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
|
||||||
"integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
|
"integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
@ -1799,7 +1799,7 @@
|
|||||||
},
|
},
|
||||||
"browserify-rsa": {
|
"browserify-rsa": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
|
"resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
|
||||||
"integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
|
"integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
@ -2531,7 +2531,7 @@
|
|||||||
},
|
},
|
||||||
"create-hash": {
|
"create-hash": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
|
"resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
|
||||||
"integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
|
"integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
@ -2544,7 +2544,7 @@
|
|||||||
},
|
},
|
||||||
"create-hmac": {
|
"create-hmac": {
|
||||||
"version": "1.1.7",
|
"version": "1.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
|
"resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
|
||||||
"integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
|
"integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
@ -3184,7 +3184,7 @@
|
|||||||
},
|
},
|
||||||
"diffie-hellman": {
|
"diffie-hellman": {
|
||||||
"version": "5.0.3",
|
"version": "5.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
|
"resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
|
||||||
"integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
|
"integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
@ -7761,7 +7761,7 @@
|
|||||||
},
|
},
|
||||||
"buffer": {
|
"buffer": {
|
||||||
"version": "4.9.1",
|
"version": "4.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
|
"resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
|
||||||
"integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
|
"integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
@ -8341,7 +8341,7 @@
|
|||||||
},
|
},
|
||||||
"parse-asn1": {
|
"parse-asn1": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz",
|
"resolved": "http://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz",
|
||||||
"integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==",
|
"integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
@ -10275,7 +10275,7 @@
|
|||||||
},
|
},
|
||||||
"sha.js": {
|
"sha.js": {
|
||||||
"version": "2.4.11",
|
"version": "2.4.11",
|
||||||
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
|
"resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
|
||||||
"integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
|
"integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
45
src/components/Alert/index.tsx
Normal file
45
src/components/Alert/index.tsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import * as React from 'react'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
import { Icon } from '@components'
|
||||||
|
import { BaseComponentProps } from '@models'
|
||||||
|
import './style.scss'
|
||||||
|
|
||||||
|
interface AlertProps extends BaseComponentProps {
|
||||||
|
message?: string
|
||||||
|
type?: 'success' | 'info' | 'warning' | 'error'
|
||||||
|
inside?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Alert extends React.Component<AlertProps, {}> {
|
||||||
|
|
||||||
|
static defaultProps: AlertProps = {
|
||||||
|
message: '',
|
||||||
|
type: 'info',
|
||||||
|
inside: false
|
||||||
|
}
|
||||||
|
|
||||||
|
iconMap = {
|
||||||
|
success: 'check',
|
||||||
|
info: 'info',
|
||||||
|
warning: 'info',
|
||||||
|
error: 'close'
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { message, type, inside, children, className, style } = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classnames('alert', `alert-${inside ? 'note' : 'box'}-${type}`, className)} style={style}>
|
||||||
|
<span className="alert-icon">
|
||||||
|
<Icon type={this.iconMap[type]} size={26} />
|
||||||
|
</span>
|
||||||
|
{
|
||||||
|
message
|
||||||
|
? <p className="alert-message">{message}</p>
|
||||||
|
: <div className="alert-message">{children}</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
97
src/components/Alert/style.scss
Normal file
97
src/components/Alert/style.scss
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
@import '~@styles/variables';
|
||||||
|
|
||||||
|
$iconSize: 20px;
|
||||||
|
$borderWidth: 4px;
|
||||||
|
|
||||||
|
@mixin box ($color) {
|
||||||
|
background: linear-gradient(135deg, darken($color, 5%), $color);
|
||||||
|
box-shadow: 0 2px 8px rgba($color: darken($color, 5%), $alpha: 0.3);
|
||||||
|
|
||||||
|
.alert-icon > i {
|
||||||
|
color: $color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin note ($color) {
|
||||||
|
background: rgba($color: $color, $alpha: 0.05);
|
||||||
|
border-radius: 1px 4px 4px 1px;
|
||||||
|
border-left: 2px solid $color;
|
||||||
|
box-shadow: 0 2px 8px rgba($color: darken($color, 5%), $alpha: 0.3);
|
||||||
|
|
||||||
|
.alert-icon {
|
||||||
|
background: $color;
|
||||||
|
|
||||||
|
> i {
|
||||||
|
color: $color-white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-message {
|
||||||
|
color: darken($color: $color, $amount: 20%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
padding: 15px;
|
||||||
|
background: $color-white;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 8px rgba($color: $color-primary-dark, $alpha: 0.3);
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.6;
|
||||||
|
text-align: justify;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.alert-icon {
|
||||||
|
margin-right: 10px;
|
||||||
|
width: $iconSize;
|
||||||
|
height: $iconSize;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background: $color-white;
|
||||||
|
|
||||||
|
> i {
|
||||||
|
transform: scale(0.5);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-message {
|
||||||
|
width: 100%;
|
||||||
|
color: $color-white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-box-success {
|
||||||
|
@include box($color-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-box-info {
|
||||||
|
@include box($color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-box-warning {
|
||||||
|
@include box($color-orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-box-error {
|
||||||
|
@include box($color-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-note-success {
|
||||||
|
@include note($color-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-note-info {
|
||||||
|
@include note($color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-note-warning {
|
||||||
|
@include note($color-orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-note-error {
|
||||||
|
@include note($color-red);
|
||||||
|
}
|
30
src/components/Button/index.tsx
Normal file
30
src/components/Button/index.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import * as React from 'react'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
import { BaseComponentProps } from '@models'
|
||||||
|
import './style.scss'
|
||||||
|
|
||||||
|
interface ButtonProps extends BaseComponentProps {
|
||||||
|
type?: 'primary' | 'normal' | 'danger' | 'success' | 'warning'
|
||||||
|
onClick?: React.MouseEventHandler<HTMLButtonElement>
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Button extends React.Component<ButtonProps, {}> {
|
||||||
|
|
||||||
|
static defaultProps: ButtonProps = {
|
||||||
|
type: 'normal',
|
||||||
|
onClick: () => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { type, onClick, children, className, style } = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={classnames('button', `button-${type}`, className)}
|
||||||
|
style={style}
|
||||||
|
onClick={onClick}
|
||||||
|
>{children}</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
88
src/components/Button/style.scss
Normal file
88
src/components/Button/style.scss
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
@import '~@styles/variables';
|
||||||
|
|
||||||
|
.button {
|
||||||
|
outline: 0;
|
||||||
|
padding: 0 15px;
|
||||||
|
height: 32px;
|
||||||
|
line-height: 32px;
|
||||||
|
border-radius: 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 150ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-primary {
|
||||||
|
color: $color-white;
|
||||||
|
border: none;
|
||||||
|
background: linear-gradient(135deg, $color-primary, $color-primary-dark);
|
||||||
|
box-shadow: 0 2px 8px rgba($color: $color-primary-dark, $alpha: 0.5);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
box-shadow: 0 0 2px rgba($color: $color-primary-dark, $alpha: 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-normal {
|
||||||
|
color: $color-gray-darken;
|
||||||
|
background: $color-white;
|
||||||
|
border: 1px solid rgba($color: $color-black, $alpha: 0.1);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: $color-gray-dark;
|
||||||
|
color: $color-primary-darken;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: darken($color-white, 2%);
|
||||||
|
color: $color-primary-darken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-danger {
|
||||||
|
color: $color-white;
|
||||||
|
border: none;
|
||||||
|
background: linear-gradient(135deg, $color-red, darken($color-red, 10%));
|
||||||
|
box-shadow: 0 2px 8px rgba($color: darken($color-red, 10%), $alpha: 0.5);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
box-shadow: 0 0 2px rgba($color: darken($color-red, 10%), $alpha: 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-success {
|
||||||
|
color: $color-white;
|
||||||
|
border: none;
|
||||||
|
background: linear-gradient(135deg, $color-green, darken($color-green, 5%));
|
||||||
|
box-shadow: 0 2px 8px rgba($color: darken($color-green, 5%), $alpha: 0.5);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
box-shadow: 0 0 2px rgba($color: darken($color-green, 5%), $alpha: 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-warning {
|
||||||
|
color: $color-white;
|
||||||
|
border: none;
|
||||||
|
background: linear-gradient(135deg, $color-orange, darken($color-orange, 5%));
|
||||||
|
box-shadow: 0 2px 8px rgba($color: darken($color-orange, 5%), $alpha: 0.5);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
box-shadow: 0 0 2px rgba($color: darken($color-orange, 5%), $alpha: 0.5);
|
||||||
|
}
|
||||||
|
}
|
105
src/components/Modal/index.tsx
Normal file
105
src/components/Modal/index.tsx
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import * as React from 'react'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
import { createPortal } from 'react-dom'
|
||||||
|
import { BaseComponentProps } from '@models'
|
||||||
|
import { Button } from '@components'
|
||||||
|
import './style.scss'
|
||||||
|
|
||||||
|
const noop = () => {}
|
||||||
|
|
||||||
|
interface ModalProps extends BaseComponentProps {
|
||||||
|
// show modal
|
||||||
|
show?: boolean
|
||||||
|
|
||||||
|
// modal title
|
||||||
|
title: string
|
||||||
|
|
||||||
|
// size
|
||||||
|
size?: 'small' | 'big'
|
||||||
|
|
||||||
|
// body className
|
||||||
|
bodyClassName?: string
|
||||||
|
|
||||||
|
// body style
|
||||||
|
bodyStyle?: React.CSSProperties
|
||||||
|
|
||||||
|
// show footer
|
||||||
|
footer?: boolean
|
||||||
|
|
||||||
|
// on click ok
|
||||||
|
onOk?: typeof noop
|
||||||
|
|
||||||
|
// on click close
|
||||||
|
onClose?: typeof noop
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Modal extends React.Component<ModalProps, {}> {
|
||||||
|
|
||||||
|
static defaultProps: ModalProps = {
|
||||||
|
show: true,
|
||||||
|
title: 'Modal',
|
||||||
|
size: 'small',
|
||||||
|
footer: true,
|
||||||
|
onOk: noop,
|
||||||
|
onClose: noop
|
||||||
|
}
|
||||||
|
|
||||||
|
// portal container
|
||||||
|
$container: Element
|
||||||
|
|
||||||
|
$modal = React.createRef<HTMLDivElement>()
|
||||||
|
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
// create container element
|
||||||
|
const container = document.createElement('div')
|
||||||
|
document.body.appendChild(container)
|
||||||
|
this.$container = container
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
document.body.removeChild(this.$container)
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleMaskClick = (e) => {
|
||||||
|
const { onClose } = this.props
|
||||||
|
const el = this.$modal.current
|
||||||
|
|
||||||
|
if (el && !el.contains(e.target)) {
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { show, size, title, footer, children, className, bodyClassName, style, bodyStyle, onOk, onClose } = this.props
|
||||||
|
const modal = (
|
||||||
|
<div
|
||||||
|
className={classnames('modal-mask', { 'modal-show': show })}
|
||||||
|
onClick={this.handleMaskClick}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classnames('modal', `modal-${size}`, className)}
|
||||||
|
style={style}
|
||||||
|
ref={this.$modal}
|
||||||
|
>
|
||||||
|
<div className="modal-title">{title}</div>
|
||||||
|
<div
|
||||||
|
className={classnames('modal-body', bodyClassName)}
|
||||||
|
style={bodyStyle}
|
||||||
|
>{children}</div>
|
||||||
|
{
|
||||||
|
footer && (
|
||||||
|
<div className="footer">
|
||||||
|
<Button onClick={() => onClose()}>取 消</Button>
|
||||||
|
<Button type="primary" onClick={() => onOk()}>确 定</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return createPortal(modal, this.$container)
|
||||||
|
}
|
||||||
|
}
|
78
src/components/Modal/style.scss
Normal file
78
src/components/Modal/style.scss
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
@import '~@styles/variables';
|
||||||
|
|
||||||
|
$width: 400px;
|
||||||
|
$bigWidth: 600px;
|
||||||
|
|
||||||
|
.modal-mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba($color: $color-black, $alpha: 0.15);
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: all 500ms ease;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
margin-top: -50px;
|
||||||
|
padding: 20px 30px;
|
||||||
|
background: $color-white;
|
||||||
|
box-shadow: 0 2px 16px rgba($color: $color-primary-darken, $alpha: 0.2);
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
transform: scale(0);
|
||||||
|
transition: all 300ms cubic-bezier(0.32, 0.26, 0.71, 1.29);
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
margin: 5px 0;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 18px;
|
||||||
|
color: $color-primary-dark;
|
||||||
|
text-shadow: 0 2px 6px rgba($color: $color-primary-dark, $alpha: 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
margin: 10px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
color: $color-primary-darken;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
width: 100%;
|
||||||
|
margin: 5px 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
.button {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-small {
|
||||||
|
width: $width;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-big {
|
||||||
|
width: $bigWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-show {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: visible;
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
@ -8,3 +8,6 @@ export * from './ButtonSelect'
|
|||||||
export * from './Tags'
|
export * from './Tags'
|
||||||
export * from './Input'
|
export * from './Input'
|
||||||
export * from './Select'
|
export * from './Select'
|
||||||
|
export * from './Modal'
|
||||||
|
export * from './Alert'
|
||||||
|
export * from './Button'
|
||||||
|
@ -0,0 +1,71 @@
|
|||||||
|
import * as React from 'react'
|
||||||
|
import { Modal, Input, Row, Col, Alert } from '@components'
|
||||||
|
import './style.scss'
|
||||||
|
|
||||||
|
interface ExternalControllerDrawerProps {
|
||||||
|
show: boolean
|
||||||
|
host: string
|
||||||
|
port: string
|
||||||
|
onConfirm: (host: string, port: string) => void
|
||||||
|
onCancel: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExternalControllerDrawerState {
|
||||||
|
host: string,
|
||||||
|
port: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ExternalControllerDrawer extends React.Component<ExternalControllerDrawerProps, ExternalControllerDrawerState> {
|
||||||
|
|
||||||
|
state = {
|
||||||
|
host: this.props.host,
|
||||||
|
port: this.props.port
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleOk = () => {
|
||||||
|
const { onConfirm } = this.props
|
||||||
|
const { host, port } = this.state
|
||||||
|
onConfirm(host, port)
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { show, onCancel } = this.props
|
||||||
|
const { host, port } = this.state
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
show={show}
|
||||||
|
title="编辑外部控制设置"
|
||||||
|
bodyClassName="external-controller"
|
||||||
|
onClose={onCancel}
|
||||||
|
onOk={this.handleOk}
|
||||||
|
>
|
||||||
|
<Alert type="info" inside={true}>
|
||||||
|
<p>请注意,修改该配置项并不会修改你的 Clash 配置文件,请确认修改后的外部控制地址和 Clash 配置文件内的地址一致,否则会导致 Dashboard 无法连接。</p>
|
||||||
|
</Alert>
|
||||||
|
<Row gutter={24} align="middle">
|
||||||
|
<Col span={4} className="title">Host</Col>
|
||||||
|
<Col span={20} className="form">
|
||||||
|
<Input
|
||||||
|
align="left"
|
||||||
|
inside={true}
|
||||||
|
value={host}
|
||||||
|
onChange={host => this.setState({ host })}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row gutter={24} align="middle">
|
||||||
|
<Col span={4} className="title">端口</Col>
|
||||||
|
<Col span={20} className="form">
|
||||||
|
<Input
|
||||||
|
align="left"
|
||||||
|
inside={true}
|
||||||
|
value={port}
|
||||||
|
onChange={port => this.setState({ port })}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
@import '~@styles/variables';
|
||||||
|
|
||||||
|
.external-controller {
|
||||||
|
.row {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title,
|
||||||
|
.form {
|
||||||
|
margin: 15px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-top: 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export * from './ExternalControllerDrawer'
|
@ -1,7 +1,8 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { Header, Card, Row, Col, Switch, ButtonSelect, ButtonSelectOptions, Input, Icon } from '@components'
|
|
||||||
import { translate } from 'react-i18next'
|
import { translate } from 'react-i18next'
|
||||||
import { changeLanguage } from 'i18next'
|
import { changeLanguage } from 'i18next'
|
||||||
|
import { Header, Card, Row, Col, Switch, ButtonSelect, ButtonSelectOptions, Input, Icon } from '@components'
|
||||||
|
import { ExternalControllerDrawer } from './components'
|
||||||
import { I18nProps } from '@models'
|
import { I18nProps } from '@models'
|
||||||
import './style.scss'
|
import './style.scss'
|
||||||
|
|
||||||
@ -14,7 +15,9 @@ class Settings extends React.Component<I18nProps, {}> {
|
|||||||
proxyMode: 'Rule',
|
proxyMode: 'Rule',
|
||||||
socks5ProxyPort: 7891,
|
socks5ProxyPort: 7891,
|
||||||
httpProxyPort: 7890,
|
httpProxyPort: 7890,
|
||||||
externalController: '127.0.0.1:7892'
|
externalControllerHost: '127.0.0.1',
|
||||||
|
externalControllerPort: '7892',
|
||||||
|
showEditDrawer: false
|
||||||
}
|
}
|
||||||
|
|
||||||
languageOptions: ButtonSelectOptions[] = [
|
languageOptions: ButtonSelectOptions[] = [
|
||||||
@ -35,7 +38,9 @@ class Settings extends React.Component<I18nProps, {}> {
|
|||||||
proxyMode,
|
proxyMode,
|
||||||
socks5ProxyPort,
|
socks5ProxyPort,
|
||||||
httpProxyPort,
|
httpProxyPort,
|
||||||
externalController
|
externalControllerHost,
|
||||||
|
externalControllerPort,
|
||||||
|
showEditDrawer
|
||||||
} = this.state
|
} = this.state
|
||||||
const proxyModeOptions: ButtonSelectOptions[] = [
|
const proxyModeOptions: ButtonSelectOptions[] = [
|
||||||
{ label: t('values.global'), value: 'Global' },
|
{ label: t('values.global'), value: 'Global' },
|
||||||
@ -116,11 +121,15 @@ class Settings extends React.Component<I18nProps, {}> {
|
|||||||
<Col span={3} offset={2}>
|
<Col span={3} offset={2}>
|
||||||
<Input value={httpProxyPort} onChange={httpProxyPort => this.setState({ httpProxyPort })}></Input>
|
<Input value={httpProxyPort} onChange={httpProxyPort => this.setState({ httpProxyPort })}></Input>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={5} offset={1}>
|
<Col span={4} offset={1}>
|
||||||
<span className="label">{t('labels.externalController')}</span>
|
<span className="label">{t('labels.externalController')}</span>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={5} offset={1}>
|
<Col className="external-controller" span={6} offset={1}>
|
||||||
<Input value={externalController} ></Input>
|
<span>{`${externalControllerHost}:${externalControllerPort}`}</span>
|
||||||
|
<span
|
||||||
|
className="modify-btn"
|
||||||
|
onClick={() => this.setState({ showEditDrawer: true })}
|
||||||
|
>修改</span>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Card>
|
</Card>
|
||||||
@ -132,6 +141,16 @@ class Settings extends React.Component<I18nProps, {}> {
|
|||||||
<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>
|
||||||
|
|
||||||
|
<ExternalControllerDrawer
|
||||||
|
show={showEditDrawer}
|
||||||
|
host={externalControllerHost}
|
||||||
|
port={externalControllerPort}
|
||||||
|
onConfirm={(host, port) => console.log(host, port)}
|
||||||
|
onCancel={() => this.setState({ showEditDrawer: false })}
|
||||||
|
>
|
||||||
|
<div>666666</div>
|
||||||
|
</ExternalControllerDrawer>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,22 @@
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: $color-primary-darken;
|
color: $color-primary-darken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.external-controller {
|
||||||
|
font-size: 14px;
|
||||||
|
color: $color-primary-darken;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
font-weight: normal;
|
||||||
|
line-height: 17px;
|
||||||
|
|
||||||
|
.modify-btn {
|
||||||
|
margin-left: 5px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: $color-primary-dark;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.clash-version {
|
.clash-version {
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
// styles initial
|
// styles initial
|
||||||
html {
|
html {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background: rgba($color: $color-white, $alpha: 0.9);
|
background: rgba($color: $color-white, $alpha: 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
*,
|
*,
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "clash-iconfont";
|
font-family: "clash-iconfont";
|
||||||
src: url('//at.alicdn.com/t/font_841708_2viqaiy9h37.ttf') format('truetype');
|
src: url('//at.alicdn.com/t/font_841708_h6oakuryxxb.ttf') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.clash-iconfont {
|
.clash-iconfont {
|
||||||
@ -20,8 +20,6 @@
|
|||||||
|
|
||||||
.icon-close::before { content: "\e602"; }
|
.icon-close::before { content: "\e602"; }
|
||||||
|
|
||||||
.icon-info::before { content: "\e603"; }
|
|
||||||
|
|
||||||
.icon-drag::before { content: "\e604"; }
|
.icon-drag::before { content: "\e604"; }
|
||||||
|
|
||||||
.icon-down-arrow-o::before { content: "\e605"; }
|
.icon-down-arrow-o::before { content: "\e605"; }
|
||||||
@ -35,3 +33,7 @@
|
|||||||
.icon-triangle-down::before { content: "\e609"; }
|
.icon-triangle-down::before { content: "\e609"; }
|
||||||
|
|
||||||
.icon-up-arrow-o::before { content: "\e60a"; }
|
.icon-up-arrow-o::before { content: "\e60a"; }
|
||||||
|
|
||||||
|
.icon-info::before { content: "\e60b"; }
|
||||||
|
|
||||||
|
.icon-info-o::before { content: "\e60c"; }
|
||||||
|
@ -18,3 +18,4 @@ $color-white: #fff;
|
|||||||
$color-green: #67c23a;
|
$color-green: #67c23a;
|
||||||
$color-orange: #e6a23c;
|
$color-orange: #e6a23c;
|
||||||
$color-red: #f56c6c;
|
$color-red: #f56c6c;
|
||||||
|
$color-black: #000;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user