summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHaishan <[email protected]>2018-10-26 01:02:59 +0800
committerHaishan <[email protected]>2018-10-27 14:28:25 +0800
commitabfab4f1adb40e3463fa9e90aff3e92bfdf03693 (patch)
tree46347cc195d59a2b0622752c71742b82476cce69 /src
parent4e7efe2accc67a63f9e3bc4ed4365acefb4fb883 (diff)
ui(proxy): new UI for proxy page
Diffstat (limited to 'src')
-rw-r--r--src/components/APIConfig.js23
-rw-r--r--src/components/APIConfig.module.scss4
-rw-r--r--src/components/Proxies.js73
-rw-r--r--src/components/Proxies.module.scss40
-rw-r--r--src/components/Proxy.js117
-rw-r--r--src/components/Proxy.module.scss32
-rw-r--r--src/components/ProxyGroup.js80
-rw-r--r--src/components/ProxyGroup.module.scss20
-rw-r--r--src/components/ProxyLatency.js44
-rw-r--r--src/components/ProxyLatency.module.scss8
-rw-r--r--src/components/StyleGuide.js11
-rw-r--r--src/ducks/proxies.js42
-rw-r--r--src/svg/auto.svg3
-rw-r--r--src/svg/fallback.svg3
-rw-r--r--src/svg/ss.svg5
-rw-r--r--src/svg/vmess.svg10
16 files changed, 324 insertions, 191 deletions
diff --git a/src/components/APIConfig.js b/src/components/APIConfig.js
index ce2b7fe..4e85a69 100644
--- a/src/components/APIConfig.js
+++ b/src/components/APIConfig.js
@@ -33,6 +33,10 @@ class APIConfig extends Component {
secret: this.props.apiConfig.secret
};
+ componentDidMount() {
+ this.content.focus();
+ }
+
handleInputOnChange = e => {
const target = e.target;
const { name } = target;
@@ -46,15 +50,30 @@ class APIConfig extends Component {
this.setState({ [name]: value });
};
- handleConfirmOnClick = () => {
+ updateClashAPIConfig() {
const { hostname, port, secret } = this.state;
this.props.updateClashAPIConfig({ hostname, port, secret });
+ }
+
+ handleConfirmOnClick = () => {
+ this.updateClashAPIConfig();
+ };
+
+ handleContentOnKeyDown = e => {
+ // enter keyCode is 13
+ if (e.keyCode !== 13) return;
+ this.updateClashAPIConfig();
};
render() {
const { hostname, port, secret } = this.state;
return (
- <div className={s0.root}>
+ <div
+ className={s0.root}
+ ref={e => (this.content = e)}
+ tabIndex="1"
+ onKeyDown={this.handleContentOnKeyDown}
+ >
<div className={s0.header}>RESTful API config for Clash</div>
<div className={s0.body}>
<div className={s0.group}>
diff --git a/src/components/APIConfig.module.scss b/src/components/APIConfig.module.scss
index d723016..0edf1b6 100644
--- a/src/components/APIConfig.module.scss
+++ b/src/components/APIConfig.module.scss
@@ -1,5 +1,7 @@
.root {
- //
+ &:focus {
+ outline: none;
+ }
}
.header {
diff --git a/src/components/Proxies.js b/src/components/Proxies.js
index f6d7caf..98876ae 100644
--- a/src/components/Proxies.js
+++ b/src/components/Proxies.js
@@ -2,22 +2,24 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import ContentHeader from 'c/ContentHeader';
-import Proxy from 'c/Proxy';
+import ProxyGroup from 'c/ProxyGroup';
import Button from 'c/Button';
-import cx from 'classnames';
import s0 from 'c/Proxies.module.scss';
-const th = cx(s0.row, s0.th, 'border-bottom');
-// const colItem = cx(s0.colItem, 'border-bottom');
-
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
-import { getUserProxies, fetchProxies, requestDelayAll } from 'd/proxies';
+import {
+ getUserProxies,
+ getProxyGroupNames,
+ fetchProxies,
+ requestDelayAll
+} from 'd/proxies';
function mapStateToProps(s) {
return {
- proxies: getUserProxies(s)
+ proxies: getUserProxies(s),
+ groupNames: getProxyGroupNames(s)
};
}
@@ -30,7 +32,7 @@ function mapDispatchToProps(dispatch) {
class Proxies extends Component {
static propTypes = {
- proxies: PropTypes.object.isRequired,
+ groupNames: PropTypes.array.isRequired,
fetchProxies: PropTypes.func.isRequired,
requestDelayAll: PropTypes.func.isRequired
};
@@ -40,64 +42,27 @@ class Proxies extends Component {
}
render() {
- const { proxies, requestDelayAll } = this.props;
-
+ const { groupNames, requestDelayAll } = this.props;
return (
<div>
<ContentHeader title="Proxies" />
-
- <div className={s0.root}>
+ <div>
<div className={s0.btnGroup}>
<Button label="Test Latency" onClick={requestDelayAll} />
</div>
- <div className={th}>
- <div className={s0.col1}>Name</div>
- <div className={s0.col2}>Type</div>
- <div className={s0.col3}>All</div>
- </div>
-
- <div>
- {Object.keys(proxies).map(k => {
- const o = proxies[k];
- return <ProxyRow name={k} key={k} {...o} />;
- })}
- </div>
+ {groupNames.map(groupName => {
+ return (
+ <div className={s0.group} key={groupName}>
+ <ProxyGroup name={groupName} />
+ </div>
+ );
+ })}
</div>
</div>
);
}
}
-class ProxyRow extends Component {
- static propTypes = {
- name: PropTypes.string.isRequired,
- type: PropTypes.string.isRequired,
- all: PropTypes.array,
- now: PropTypes.string
- };
-
- render() {
- const { name, type, all, now } = this.props;
- return (
- <div className={s0.row}>
- <div className={s0.col1}>{name}</div>
- <div className={s0.col2}>{type}</div>
- {all ? (
- <div className={s0.col3 + ' border-left'}>
- {all.map(p => {
- return (
- <div className={s0.colItem} key={p}>
- <Proxy name={p} parentName={name} checked={p === now} />
- </div>
- );
- })}
- </div>
- ) : null}
- </div>
- );
- }
-}
-
export default connect(
mapStateToProps,
mapDispatchToProps
diff --git a/src/components/Proxies.module.scss b/src/components/Proxies.module.scss
index 3b11056..9a73ee6 100644
--- a/src/components/Proxies.module.scss
+++ b/src/components/Proxies.module.scss
@@ -1,47 +1,13 @@
-$heightHeader: 76px;
-
.root {
- color: #eee;
- padding: 10px 40px;
- height: calc(100vh - #{$heightHeader});
- overflow: scroll;
}
-.row {
- display: flex;
-}
-
-.th {
- font-weight: bold;
-}
-
-.col1 {
- width: 150px;
- padding: 8px;
- display: flex;
- align-items: center;
-}
-
-.col2 {
- width: 150px;
- padding: 8px;
- display: flex;
- align-items: center;
-}
-
-.col3 {
- width: 350px;
- margin: 16px 8px;
- padding-left: 20px;
-}
-
-.colItem {
- padding: 6px;
+.group {
+ padding: 10px 40px;
}
.btnGroup {
color: #eee;
- // padding: 10px 40px;
+ padding: 0 40px;
display: flex;
justify-content: flex-end;
}
diff --git a/src/components/Proxy.js b/src/components/Proxy.js
index b3d071b..2bb9435 100644
--- a/src/components/Proxy.js
+++ b/src/components/Proxy.js
@@ -1,92 +1,71 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
-import s0 from 'c/Proxy.module.scss';
+import Icon from 'c/Icon';
+import ProxyLatency from 'c/ProxyLatency';
-import { connect } from 'react-redux';
-import { bindActionCreators } from 'redux';
-import { getDelay, switchProxy, requestDelayForProxy } from 'd/proxies';
+import globe from 's/globe.svg';
+import ss from 's/ss.svg';
+import vmess from 's/vmess.svg';
+import auto from 's/auto.svg';
+import fallback from 's/fallback.svg';
-const mapStateToProps = state => {
- const delay = getDelay(state);
- return { delay };
-};
+import s0 from './Proxy.module.scss';
-const mapDispatchToProps = dispatch => {
- return {
- switchProxy: bindActionCreators(switchProxy, dispatch),
- requestDelay: bindActionCreators(requestDelayForProxy, dispatch)
- };
+import { connect } from 'react-redux';
+import { getDelay, getUserProxies } from 'd/proxies';
+
+const colors = {
+ Vmess: '#ca3487',
+ Shadowsocks: '#1a7dc0',
+ Socks5: '#2a477a',
+ URLTest: '#3483e8',
+ Fallback: '#3483e8'
};
-const colorMap = {
- good: '#67C23A',
- normal: '#E6A23C',
- bad: '#F56C6C',
- na: '#909399'
+const icons = {
+ Vmess: vmess.id,
+ Shadowsocks: ss.id,
+ Socks5: globe.id,
+ URLTest: auto.id,
+ Fallback: fallback.id
};
-class Proxy extends Component {
- static propTypes = {
- name: PropTypes.string.isRequired,
- parentName: PropTypes.string,
- checked: PropTypes.bool,
- switchProxy: PropTypes.func,
- requestDelay: PropTypes.func,
- delay: PropTypes.object
- };
+// typeof Proxy = 'Shadowsocks' | 'Vmess' | 'Socks5';
- handleRadioOnChange = () => {
- const { name, parentName, checked, switchProxy } = this.props;
- if (checked) return;
- switchProxy(parentName, name);
+const mapStateToProps = s => {
+ return {
+ proxies: getUserProxies(s),
+ delay: getDelay(s)
};
+};
- render() {
- const { name, parentName, checked, delay } = this.props;
- const id = parentName + ':' + name;
- const latency = delay[name] || 0;
- return (
- <label className={s0.Proxy} htmlFor={id}>
- <input
- type="radio"
- id={id}
- checked={checked}
- value={name}
- onChange={this.handleRadioOnChange}
- />
- <div className={s0.name}>{name}</div>
- <LatencyLabel val={latency} />
- </label>
- );
- }
-}
+const mapDispatchToProps = null;
-class LatencyLabel extends Component {
+class Proxy extends Component {
static propTypes = {
- val: PropTypes.number
+ now: PropTypes.bool,
+ delay: PropTypes.object,
+ proxies: PropTypes.object,
+ name: PropTypes.string
};
render() {
- const { val } = this.props;
- let bg = colorMap.na;
+ const { name, proxies, delay, now } = this.props;
+ const latency = delay[name];
+ const proxy = proxies[name];
+ const color = now ? colors[proxy.type] : '#555';
+ const iconId = icons[proxy.type];
- if (val < 100) {
- bg = colorMap.good;
- } else if (val < 300) {
- bg = colorMap.normal;
- } else {
- bg = colorMap.bad;
- }
- const style = { background: bg };
- if (val === 0 || !val) {
- style.opacity = '0';
- style.visibility = 'hidden';
- }
return (
- <div className={s0.LatencyLabel} style={style}>
- <div>{val}</div>
- <div>ms</div>
+ <div className={s0.proxy}>
+ <div className={s0.left} style={{ color }}>
+ <Icon id={iconId} width={80} height={80} />
+ </div>
+ <div className={s0.right}>
+ <div className={s0.proxyName}>{name}</div>
+ {latency ? <ProxyLatency latency={latency} /> : null}
+ </div>
</div>
);
}
diff --git a/src/components/Proxy.module.scss b/src/components/Proxy.module.scss
index 735c348..20cb494 100644
--- a/src/components/Proxy.module.scss
+++ b/src/components/Proxy.module.scss
@@ -1,22 +1,28 @@
-.Proxy {
+.proxy {
display: flex;
- align-items: center;
+ cursor: pointer;
- .name {
- flex: 1;
- // width: 100px;
- padding-left: 10px;
+ svg {
+ transition: transform 0.4s ease, color 0.4s ease;
+ }
+ &:hover {
+ svg {
+ transform: scale(1.1);
+ color: #aaa;
+ }
}
}
-.LatencyLabel {
+.left {
display: flex;
align-items: center;
- padding: 5px;
- border-radius: 5px;
- background: #e6a23c;
+ justify-content: center;
+}
- div:nth-child(2) {
- padding-left: 4px;
- }
+.right {
+ padding-left: 20px;
+}
+
+.proxyName {
+ margin: 10px 0;
}
diff --git a/src/components/ProxyGroup.js b/src/components/ProxyGroup.js
new file mode 100644
index 0000000..b697b27
--- /dev/null
+++ b/src/components/ProxyGroup.js
@@ -0,0 +1,80 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import memoize from 'memoize-one';
+
+import Proxy from 'c/Proxy';
+
+import s0 from './ProxyGroup.module.scss';
+
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+import { getUserProxies, switchProxy } from 'd/proxies';
+
+const mapStateToProps = s => {
+ return {
+ proxies: getUserProxies(s)
+ };
+};
+
+const mapDispatchToProps = dispatch => {
+ return {
+ switchProxy: bindActionCreators(switchProxy, dispatch)
+ };
+};
+
+// should move this to sth like constants.js
+// const userProxyTypes = ['Shadowsocks', 'Vmess', 'Socks5'];
+
+class ProxyGroup extends Component {
+ static propTypes = {
+ // group name
+ name: PropTypes.string.isRequired,
+ proxies: PropTypes.object,
+ switchProxy: PropTypes.func
+ };
+
+ reOrderProxies = memoize((list, now) => {
+ const a = [now];
+ list.forEach(i => i !== now && a.push(i));
+ return a;
+ });
+
+ render() {
+ const { name, proxies, switchProxy } = this.props;
+ const group = proxies[name];
+ let list;
+ if (group.all) {
+ list = this.reOrderProxies(group.all, group.now);
+ } else {
+ list = [group.now];
+ }
+ return (
+ <div className={s0.group}>
+ <div className={s0.header}>
+ <h2>
+ <span>{name}</span>
+ <span>{group.type}</span>
+ </h2>
+ </div>
+ <div className={s0.list}>
+ {list.map(proxyName => {
+ return (
+ <div
+ className={s0.proxy}
+ key={proxyName}
+ onClick={() => switchProxy(name, proxyName)}
+ >
+ <Proxy name={proxyName} now={proxyName === group.now} />
+ </div>
+ );
+ })}
+ </div>
+ </div>
+ );
+ }
+}
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(ProxyGroup);
diff --git a/src/components/ProxyGroup.module.scss b/src/components/ProxyGroup.module.scss
new file mode 100644
index 0000000..43cbdaa
--- /dev/null
+++ b/src/components/ProxyGroup.module.scss
@@ -0,0 +1,20 @@
+.header {
+ h2 {
+ span:nth-child(2) {
+ font-size: 12px;
+ color: #777;
+ font-weight: normal;
+ margin: 0 0.3em;
+ }
+ }
+}
+
+.list {
+ display: flex;
+ flex-wrap: wrap;
+}
+
+.proxy {
+ width: 300px;
+ padding: 10px 5px;
+}
diff --git a/src/components/ProxyLatency.js b/src/components/ProxyLatency.js
new file mode 100644
index 0000000..5d1aaee
--- /dev/null
+++ b/src/components/ProxyLatency.js
@@ -0,0 +1,44 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+
+import s0 from './ProxyLatency.module.scss';
+
+const colorMap = {
+ good: '#67C23A',
+ normal: '#E6A23C',
+ bad: '#F56C6C',
+ na: '#909399'
+};
+
+class C extends Component {
+ static propTypes = {
+ latency: PropTypes.shape({
+ number: PropTypes.number,
+ error: PropTypes.string
+ })
+ };
+
+ render() {
+ const { latency } = this.props;
+ const { number, error } = latency;
+ let bg;
+
+ if (error !== '') {
+ bg = colorMap.na;
+ } else if (number < 200) {
+ bg = colorMap.good;
+ } else if (number < 400) {
+ bg = colorMap.normal;
+ } else {
+ bg = colorMap.bad;
+ }
+
+ return (
+ <span className={s0.proxyLatency} style={{ color: bg }}>
+ {error !== '' ? <span>{error}</span> : <span>{number} ms</span>}
+ </span>
+ );
+ }
+}
+
+export default C;
diff --git a/src/components/ProxyLatency.module.scss b/src/components/ProxyLatency.module.scss
new file mode 100644
index 0000000..61485bd
--- /dev/null
+++ b/src/components/ProxyLatency.module.scss
@@ -0,0 +1,8 @@
+.proxyLatency {
+ border-radius: 20px;
+ margin: 10px 0;
+ // padding: 3px 5px;
+ padding: 3px 0;
+ color: #eee;
+ // background: #ccc;
+}
diff --git a/src/components/StyleGuide.js b/src/components/StyleGuide.js
index 3fe142d..0bb3813 100644
--- a/src/components/StyleGuide.js
+++ b/src/components/StyleGuide.js
@@ -5,8 +5,9 @@ import ToggleSwitch from 'c/ToggleSwitch';
import Input from 'c/Input';
import Switch from 'c/Switch';
import Button from 'c/Button';
-import Modal from 'c/Modal';
-import APIConfig from 'c/APIConfig';
+// import Modal from 'c/Modal';
+// import APIConfig from 'c/APIConfig';
+import Proxy2 from 'c/Proxy2';
const paneStyle = {
padding: '20px 0'
@@ -37,6 +38,9 @@ class StyleGuide extends PureComponent {
return (
<div>
<Pane>
+ <Proxy2 />
+ </Pane>
+ <Pane>
<Switch />
</Pane>
<Pane>
@@ -53,9 +57,6 @@ class StyleGuide extends PureComponent {
<Pane>
<Button label="Test Latency" />
</Pane>
- <Modal isOpen={true} onRequestClose={() => {}}>
- <APIConfig />
- </Modal>
</div>
);
}
diff --git a/src/ducks/proxies.js b/src/ducks/proxies.js
index dc38e73..83f6b00 100644
--- a/src/ducks/proxies.js
+++ b/src/ducks/proxies.js
@@ -1,10 +1,15 @@
import { createSelector } from 'reselect';
import * as proxiesAPI from 'a/proxies';
+// see all types:
+// https://github.com/Dreamacro/clash/blob/master/constant/adapters.go
+
const ProxyTypeBuiltin = ['DIRECT', 'GLOBAL', 'REJECT'];
+const ProxyGroupTypes = ['Fallback', 'URLTest', 'Selector'];
export const getProxies = s => s.proxies.proxies;
export const getDelay = s => s.proxies.delay;
+export const getProxyGroupNames = s => s.proxies.groupNames;
export const getUserProxies = createSelector(getProxies, proxies => {
let o = {};
for (const prop in proxies) {
@@ -19,6 +24,21 @@ const CompletedFetchProxies = 'proxies/CompletedFetchProxies';
const OptimisticSwitchProxy = 'proxies/OptimisticSwitchProxy';
const CompletedRequestDelayForProxy = 'proxies/CompletedRequestDelayForProxy';
+function retrieveGroupNamesFrom(proxies) {
+ const groupNames = [];
+ for (const prop in proxies) {
+ // not builtin proxy
+ if (ProxyTypeBuiltin.indexOf(prop) < 0) {
+ const p = proxies[prop];
+ // is group
+ if (ProxyGroupTypes.indexOf(p.type) >= 0) {
+ groupNames.push(prop);
+ }
+ }
+ }
+ return groupNames;
+}
+
export function fetchProxies() {
return async (dispatch, getState) => {
// TODO handle errors
@@ -30,9 +50,12 @@ export function fetchProxies() {
// TODO show loading animation?
const json = await proxiesAPI.fetchProxies();
let { proxies = {} } = json;
+
+ const groupNames = retrieveGroupNamesFrom(proxies);
+
dispatch({
type: CompletedFetchProxies,
- payload: { proxies }
+ payload: { proxies, groupNames }
});
dispatch(requestDelayAll());
};
@@ -73,16 +96,19 @@ export function switchProxy(name1, name2) {
function requestDelayForProxyOnce(name) {
return async (dispatch, getState) => {
const res = await proxiesAPI.requestDelayForProxy(name);
+ let error = '';
if (res.ok === false) {
- console.log('Error', res.statusText);
- return;
+ error = res.statusText;
}
const { delay } = await res.json();
const delayPrev = getDelay(getState());
const delayNext = {
...delayPrev,
- [name]: delay
+ [name]: {
+ error,
+ number: delay
+ }
};
dispatch({
@@ -92,11 +118,6 @@ function requestDelayForProxyOnce(name) {
};
}
-// const proxyTypeListTo = [
-// 'Vmess',
-// 'Shadowsocks'
-// ];
-
export function requestDelayForProxy(name) {
return async dispatch => {
await dispatch(requestDelayForProxyOnce(name));
@@ -122,7 +143,8 @@ export function requestDelayAll() {
const initialState = {
proxies: {},
- delay: {}
+ delay: {},
+ groupNames: []
};
export default function reducer(state = initialState, { type, payload }) {
diff --git a/src/svg/auto.svg b/src/svg/auto.svg
new file mode 100644
index 0000000..e98f936
--- /dev/null
+++ b/src/svg/auto.svg
@@ -0,0 +1,3 @@
+<svg width="326" height="326" viewBox="0 0 326 326" xmlns="http://www.w3.org/2000/svg">
+ <path d="M127.773 130.955c-10.107 0-16.796-6.25-16.796-15.771 0-9.229 6.591-14.746 18.212-15.284l15.235-.683v-4.98c0-7.569-4.346-11.67-12.403-11.67-6.64 0-11.23 2.831-12.695 7.91h-6.347C113.955 82.615 121.865 77 132.02 77c11.915 0 18.848 6.25 18.848 16.943v36.133h-6.152v-8.3h-.684c-2.49 5.908-8.3 9.18-16.26 9.18zm1.563-5.664c8.496 0 15.088-5.86 15.088-13.428v-7.324l-14.795.635c-7.861.342-12.06 3.857-12.06 10.01 0 6.25 4.492 10.107 11.767 10.107zm85.693-47.852v52.588h-6.103v-9.472h-.635c-2.88 6.64-9.033 10.351-17.187 10.351-11.475 0-17.676-6.787-17.676-19.287V77.44h6.396v32.569c0 10.303 4.15 15.137 12.988 15.137 9.424 0 15.82-6.69 15.82-16.456V77.44h6.397zM124.16 182.768h6.397v13.671h22.021v5.42h-22.021v31.787c0 6.495 4.492 10.01 12.841 10.01 3.614 0 7.471-.146 9.18-.39v5.468c-1.562.293-6.494.586-9.521.586-12.647 0-18.897-5.127-18.897-15.478v-31.983H110v-5.42h14.16v-13.671zm70.069 67.138c-13.575 0-22.266-9.082-22.266-23.242v-7.812c0-14.16 8.691-23.243 22.266-23.243 13.574 0 22.265 9.082 22.265 23.243v7.812c0 14.16-8.691 23.242-22.265 23.242zm0-48.584c-9.766 0-15.87 6.983-15.87 18.36v6.152c0 11.377 6.104 18.36 15.87 18.36 9.765 0 15.869-6.983 15.869-18.36v-6.152c0-11.377-6.104-18.36-15.87-18.36z" fill="currentColor" fill-rule="evenodd"/>
+</svg>
diff --git a/src/svg/fallback.svg b/src/svg/fallback.svg
new file mode 100644
index 0000000..3ab822f
--- /dev/null
+++ b/src/svg/fallback.svg
@@ -0,0 +1,3 @@
+<svg width="326" height="326" viewBox="0 0 326 326" xmlns="http://www.w3.org/2000/svg">
+ <path d="M61.16 139.535V92.367H47v-5.42h14.16v-6.054c0-9.327 6.543-14.258 19.043-14.258 3.711 0 9.229.342 11.328.683v5.371c-2.783-.341-7.763-.585-11.328-.585-8.3 0-12.646 3.076-12.646 8.984v5.86H91.53v5.42H67.557v47.167H61.16zm64.453.928c-10.107 0-16.797-6.25-16.797-15.772 0-9.228 6.592-14.746 18.213-15.283l15.235-.683v-4.98c0-7.57-4.346-11.67-12.403-11.67-6.64 0-11.23 2.831-12.695 7.91h-6.348c.977-7.862 8.887-13.477 19.043-13.477 11.914 0 18.848 6.25 18.848 16.943v36.133h-6.152v-8.3h-.684c-2.49 5.907-8.3 9.179-16.26 9.179zm1.563-5.664c8.496 0 15.088-5.86 15.088-13.428v-7.324l-14.795.635c-7.862.341-12.06 3.857-12.06 10.01 0 6.25 4.491 10.107 11.767 10.107zm87.793 4.736h-42.92v-5.176h18.506V71.176H171.95V66h25v68.36h18.018v5.175zm61.816 0h-42.92v-5.176h18.506V71.176h-18.603V66h25v68.36h18.017v5.175zM70.78 259.415c-7.47 0-13.086-2.93-16.162-8.448h-.879v7.568h-6.103V185h6.396v28.71h.88c2.49-5.517 8.251-8.642 15.868-8.642 12.207 0 20.508 8.985 20.508 22.12v10.107c0 13.135-8.3 22.12-20.508 22.12zm-1.416-5.714c9.424 0 15.528-7.08 15.528-17.92v-7.031c0-10.84-6.104-17.92-15.528-17.92-9.375 0-15.478 7.031-15.478 17.92v7.031c0 10.889 6.103 17.92 15.478 17.92zm56.25 5.762c-10.107 0-16.797-6.25-16.797-15.772 0-9.228 6.592-14.746 18.213-15.283l15.235-.683v-4.98c0-7.57-4.346-11.67-12.403-11.67-6.64 0-11.23 2.831-12.695 7.91h-6.348c.977-7.862 8.887-13.477 19.043-13.477 11.914 0 18.848 6.25 18.848 16.943v36.133h-6.152v-8.3h-.684c-2.49 5.907-8.3 9.179-16.26 9.179zm1.563-5.664c8.496 0 15.088-5.86 15.088-13.428v-7.324l-14.795.635c-7.862.341-12.06 3.857-12.06 10.01 0 6.25 4.491 10.107 11.767 10.107zm86.084-32.373h-6.348c-1.074-6.25-6.885-10.596-14.062-10.596-10.01 0-15.577 6.69-15.577 18.75v5.322c0 12.06 5.567 18.75 15.577 18.75 7.177 0 12.988-4.345 14.062-10.595h6.348c-1.367 9.863-9.375 16.357-20.264 16.357-13.818 0-22.119-8.789-22.119-23.437v-7.471c0-14.649 8.3-23.438 22.12-23.438 10.888 0 18.896 6.494 20.263 16.358zm65.38 37.11h-7.714l-19.043-27.833-8.106 7.91v19.922h-6.396V185h6.396v45.703h.586l24.707-24.756h8.106l-20.801 20.362 22.266 32.226z" fill="currentColor" fill-rule="evenodd"/>
+</svg>
diff --git a/src/svg/ss.svg b/src/svg/ss.svg
new file mode 100644
index 0000000..4311956
--- /dev/null
+++ b/src/svg/ss.svg
@@ -0,0 +1,5 @@
+<svg width="326" height="326" viewBox="0 0 326 326" xmlns="http://www.w3.org/2000/svg">
+ <g fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round" stroke="currentColor" stroke-width="7">
+ <path d="M144.176 233.92l30.67 9.576-30.67 46.467zM145.414 209.789l99.04 30.2 48.29-202.404L29.589 171.717l80.11 27.613 121.73-96.816z"/>
+ </g>
+</svg>
diff --git a/src/svg/vmess.svg b/src/svg/vmess.svg
new file mode 100644
index 0000000..00f7709
--- /dev/null
+++ b/src/svg/vmess.svg
@@ -0,0 +1,10 @@
+<svg width="326" height="326" viewBox="0 0 326 326" xmlns="http://www.w3.org/2000/svg">
+ <g stroke="currentColor" stroke-width="8" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
+ <path d="M318.342 151.555L140.45 284.581l22.442 22.452z"/>
+ <path d="M80.768 224.894l32.993 33.003 204.581-106.34z"/>
+ <path d="M7.442 151.557h310.902L47.25 191.366z"/>
+ <g>
+ <path d="M318.337 151.439L47.52 111.555l33.483-33.59zM318.336 151.436L113.971 44.896l26.66-26.72z"/>
+ </g>
+ </g>
+</svg>